From 10c62304b6dfa922f15c56db2d357b643e0fd5e8 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 29 Aug 2022 18:23:53 +0300 Subject: [PATCH 01/60] Copy Uniques into Nfts --- frame/nfts/Cargo.toml | 48 + frame/nfts/README.md | 78 ++ frame/nfts/src/benchmarking.rs | 447 ++++++++ frame/nfts/src/functions.rs | 276 +++++ frame/nfts/src/impl_nonfungibles.rs | 188 ++++ frame/nfts/src/lib.rs | 1498 +++++++++++++++++++++++++++ frame/nfts/src/migration.rs | 57 + frame/nfts/src/mock.rs | 114 ++ frame/nfts/src/tests.rs | 872 ++++++++++++++++ frame/nfts/src/types.rs | 129 +++ frame/nfts/src/weights.rs | 510 +++++++++ 11 files changed, 4217 insertions(+) create mode 100644 frame/nfts/Cargo.toml create mode 100644 frame/nfts/README.md create mode 100644 frame/nfts/src/benchmarking.rs create mode 100644 frame/nfts/src/functions.rs create mode 100644 frame/nfts/src/impl_nonfungibles.rs create mode 100644 frame/nfts/src/lib.rs create mode 100644 frame/nfts/src/migration.rs create mode 100644 frame/nfts/src/mock.rs create mode 100644 frame/nfts/src/tests.rs create mode 100644 frame/nfts/src/types.rs create mode 100644 frame/nfts/src/weights.rs diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml new file mode 100644 index 0000000000000..19b0790947f84 --- /dev/null +++ b/frame/nfts/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-uniques" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME NFT asset management pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/nfts/README.md b/frame/nfts/README.md new file mode 100644 index 0000000000000..8a91a558b5b5f --- /dev/null +++ b/frame/nfts/README.md @@ -0,0 +1,78 @@ +# Uniques Module + +A simple, secure module for dealing with non-fungible assets. + +## Overview + +The Uniques module provides functionality for asset management of non-fungible asset classes, including: + +* Asset Issuance +* Asset Transfer +* Asset Destruction + +To use it in your runtime, you need to implement the assets [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). + +The supported dispatchable functions are documented in the [`uniques::Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum. + +### Terminology + +* **Asset issuance:** The creation of a new asset instance. +* **Asset transfer:** The action of transferring an asset instance from one account to another. +* **Asset burning:** The destruction of an asset instance. +* **Non-fungible asset:** An asset for which each unit has unique characteristics. There is exactly + one instance of such an asset in existence and there is exactly one owning account. + +### Goals + +The Uniques pallet in Substrate is designed to make the following possible: + +* Allow accounts to permissionlessly create asset classes (collections of asset instances). +* Allow a named (permissioned) account to mint and burn unique assets within a class. +* Move asset instances between accounts permissionlessly. +* Allow a named (permissioned) account to freeze and unfreeze unique assets within a + class or the entire class. +* Allow the owner of an asset instance to delegate the ability to transfer the asset to some + named third-party. + +## Interface + +### Permissionless dispatchables +* `create`: Create a new asset class by placing a deposit. +* `transfer`: Transfer an asset instance to a new owner. +* `redeposit`: Update the deposit amount of an asset instance, potentially freeing funds. +* `approve_transfer`: Name a delegate who may authorise a transfer. +* `cancel_approval`: Revert the effects of a previous `approve_transfer`. + +### Permissioned dispatchables +* `destroy`: Destroy an asset class. +* `mint`: Mint a new asset instance within an asset class. +* `burn`: Burn an asset instance within an asset class. +* `freeze`: Prevent an individual asset from being transferred. +* `thaw`: Revert the effects of a previous `freeze`. +* `freeze_class`: Prevent all asset within a class from being transferred. +* `thaw_class`: Revert the effects of a previous `freeze_class`. +* `transfer_ownership`: Alter the owner of an asset class, moving all associated deposits. +* `set_team`: Alter the permissioned accounts of an asset class. + +### Metadata (permissioned) dispatchables +* `set_attribute`: Set a metadata attribute of an asset instance or class. +* `clear_attribute`: Remove a metadata attribute of an asset instance or class. +* `set_metadata`: Set general metadata of an asset instance. +* `clear_metadata`: Remove general metadata of an asset instance. +* `set_class_metadata`: Set general metadata of an asset class. +* `clear_class_metadata`: Remove general metadata of an asset class. + +### Force (i.e. governance) dispatchables +* `force_create`: Create a new asset class. +* `force_asset_status`: Alter the underlying characteristics of an asset class. + +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum +and its associated variants for documentation on each function. + +## Related Modules + +* [`System`](https://docs.rs/frame-system/latest/frame_system/) +* [`Support`](https://docs.rs/frame-support/latest/frame_support/) +* [`Assets`](https://docs.rs/pallet-assets/latest/pallet_assets/) + +License: Apache-2.0 diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs new file mode 100644 index 0000000000000..3e3148b5b5fc2 --- /dev/null +++ b/frame/nfts/src/benchmarking.rs @@ -0,0 +1,447 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Uniques pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +}; +use frame_support::{ + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get}, + BoundedVec, +}; +use frame_system::RawOrigin as SystemOrigin; +use sp_runtime::traits::Bounded; +use sp_std::prelude::*; + +use crate::Pallet as Uniques; + +const SEED: u32 = 0; + +fn create_collection, I: 'static>( +) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) { + 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::::max_value()); + assert!(Uniques::::force_create( + SystemOrigin::Root.into(), + collection, + caller_lookup.clone(), + false, + ) + .is_ok()); + (collection, caller, caller_lookup) +} + +fn add_collection_metadata, I: 'static>() -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + assert!(Uniques::::set_collection_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + false, + ) + .is_ok()); + (caller, caller_lookup) +} + +fn mint_item, I: 'static>( + index: u16, +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().admin; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let item = T::Helper::item(index); + assert!(Uniques::::mint( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + caller_lookup.clone(), + ) + .is_ok()); + (item, caller, caller_lookup) +} + +fn add_item_metadata, I: 'static>( + item: T::ItemId, +) -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + assert!(Uniques::::set_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + false, + ) + .is_ok()); + (caller, caller_lookup) +} + +fn add_item_attribute, I: 'static>( + item: T::ItemId, +) -> (BoundedVec, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::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!(Uniques::::set_attribute( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + Some(item), + key.clone(), + vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + ) + .is_ok()); + (key, caller, caller_lookup) +} + +fn assert_last_event, I: 'static>(generic_event: >::Event) { + let events = frame_system::Pallet::::events(); + let system_event: ::Event = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +benchmarks_instance_pallet! { + create { + let collection = T::Helper::collection(0); + let origin = T::CreateOrigin::successful_origin(&collection); + 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::::max_value()); + let call = Call::::create { collection, admin }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); + } + + force_create { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + }: _(SystemOrigin::Root, T::Helper::collection(0), caller_lookup, true) + verify { + assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); + } + + destroy { + let n in 0 .. 1_000; + let m in 0 .. 1_000; + let a in 0 .. 1_000; + + let (collection, caller, caller_lookup) = create_collection::(); + add_collection_metadata::(); + for i in 0..n { + mint_item::(i as u16); + } + for i in 0..m { + add_item_metadata::(T::Helper::item(i as u16)); + } + for i in 0..a { + add_item_attribute::(T::Helper::item(i as u16)); + } + let witness = Collection::::get(collection).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection, witness) + verify { + assert_last_event::(Event::Destroyed { collection }.into()); + } + + mint { + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) + verify { + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + } + + burn { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) + verify { + assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); + } + + transfer { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) + verify { + assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); + } + + redeposit { + let i in 0 .. 5_000; + let (collection, caller, caller_lookup) = create_collection::(); + let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); + Uniques::::force_item_status( + SystemOrigin::Root.into(), + collection, + caller_lookup.clone(), + caller_lookup.clone(), + caller_lookup.clone(), + caller_lookup, + true, + false, + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) + verify { + assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); + } + + freeze { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) + verify { + assert_last_event::(Event::Frozen { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); + } + + thaw { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + Uniques::::freeze( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::Thawed { collection, item }.into()); + } + + freeze_collection { + let (collection, caller, caller_lookup) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection) + verify { + assert_last_event::(Event::CollectionFrozen { collection }.into()); + } + + thaw_collection { + let (collection, caller, caller_lookup) = create_collection::(); + let origin = SystemOrigin::Signed(caller.clone()).into(); + Uniques::::freeze_collection(origin, collection)?; + }: _(SystemOrigin::Signed(caller.clone()), collection) + verify { + assert_last_event::(Event::CollectionThawed { collection }.into()); + } + + transfer_ownership { + let (collection, caller, _) = create_collection::(); + 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(); + Uniques::::set_accept_ownership(origin, Some(collection))?; + }: _(SystemOrigin::Signed(caller), collection, target_lookup) + verify { + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); + } + + set_team { + let (collection, caller, _) = create_collection::(); + let target0 = T::Lookup::unlookup(account("target", 0, SEED)); + let target1 = T::Lookup::unlookup(account("target", 1, SEED)); + let target2 = T::Lookup::unlookup(account("target", 2, SEED)); + }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) + verify { + assert_last_event::(Event::TeamChanged{ + collection, + issuer: account("target", 0, SEED), + admin: account("target", 1, SEED), + freezer: account("target", 2, SEED), + }.into()); + } + + force_item_status { + let (collection, caller, caller_lookup) = create_collection::(); + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_item_status { + collection, + owner: caller_lookup.clone(), + issuer: caller_lookup.clone(), + admin: caller_lookup.clone(), + freezer: caller_lookup, + free_holding: true, + is_frozen: false, + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::ItemStatusChanged { collection }.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::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) + verify { + assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); + } + + clear_attribute { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + let (key, ..) = add_item_attribute::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) + verify { + assert_last_event::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); + } + + set_metadata { + let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) + verify { + assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); + } + + clear_metadata { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, item) + verify { + assert_last_event::(Event::MetadataCleared { collection, item }.into()); + } + + set_collection_metadata { + let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) + verify { + assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); + } + + clear_collection_metadata { + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + }: _(SystemOrigin::Signed(caller), collection) + verify { + assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); + } + + approve_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + verify { + assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate }.into()); + } + + cancel_approval { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let origin = SystemOrigin::Signed(caller.clone()).into(); + Uniques::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) + verify { + assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + } + + set_accept_ownership { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let collection = T::Helper::collection(0); + }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) + verify { + assert_last_event::(Event::OwnershipAcceptanceChanged { + who: caller, + maybe_collection: Some(collection), + }.into()); + } + + set_collection_max_supply { + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + verify { + assert_last_event::(Event::CollectionMaxSupplySet { + collection, + max_supply: u32::MAX, + }.into()); + } + + set_price { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let price = ItemPrice::::from(100u32); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + verify { + assert_last_event::(Event::ItemPriceSet { + collection, + item, + price, + whitelisted_buyer: Some(delegate), + }.into()); + } + + buy_item { + let (collection, seller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let buyer: T::AccountId = account("buyer", 0, SEED); + let buyer_lookup = T::Lookup::unlookup(buyer.clone()); + let price = ItemPrice::::from(0u32); + let origin = SystemOrigin::Signed(seller.clone()).into(); + Uniques::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + verify { + assert_last_event::(Event::ItemBought { + collection, + item, + price, + seller, + buyer, + }.into()); + } + + impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs new file mode 100644 index 0000000000000..107214558307f --- /dev/null +++ b/frame/nfts/src/functions.rs @@ -0,0 +1,276 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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 super::*; +use frame_support::{ + ensure, + traits::{ExistenceRequirement, Get}, +}; +use sp_runtime::{DispatchError, DispatchResult}; + +impl, I: 'static> Pallet { + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!collection_details.is_frozen, Error::::Frozen); + ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + ensure!(!details.is_frozen, Error::::Frozen); + with_details(&collection_details, &mut details)?; + + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); + let origin = details.owner; + details.owner = dest; + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + deposit: DepositBalanceOf, + free_holding: bool, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::InUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + issuer: admin.clone(), + admin: admin.clone(), + freezer: admin, + total_deposit: deposit, + free_holding, + items: 0, + item_metadatas: 0, + attributes: 0, + is_frozen: false, + }, + ); + + CollectionAccount::::insert(&owner, &collection, ()); + Self::deposit_event(event); + Ok(()) + } + + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + #[allow(deprecated)] + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionMaxSupply::::remove(&collection); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } + + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, + ) -> DispatchResult { + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + + with_details(collection_details)?; + + if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; + + let deposit = match collection_details.free_holding { + true => Zero::zero(), + false => T::ItemDeposit::get(), + }; + T::Currency::reserve(&collection_details.owner, deposit)?; + collection_details.total_deposit += deposit; + + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + let details = ItemDetails { owner, approved: None, is_frozen: false, deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner }); + Ok(()) + } + + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(collection_details, &details)?; + + // Return the deposit. + T::Currency::unreserve(&collection_details.owner, details.deposit); + collection_details.total_deposit.saturating_reduce(details.deposit); + collection_details.items.saturating_dec(); + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } + + pub fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::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(()) + } +} diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs new file mode 100644 index 0000000000000..cead6f562ab58 --- /dev/null +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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 frame_support::{ + traits::{tokens::nonfungibles::*, Get}, + BoundedSlice, +}; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::prelude::*; + +impl, I: 'static> Inspect<::AccountId> for Pallet { + type ItemId = T::ItemId; + type CollectionId = T::CollectionId; + + fn owner( + collection: &Self::CollectionId, + item: &Self::ItemId, + ) -> Option<::AccountId> { + Item::::get(collection, item).map(|a| a.owner) + } + + fn collection_owner(collection: &Self::CollectionId) -> Option<::AccountId> { + Collection::::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> { + if key.is_empty() { + // We make the empty key map to the item metadata value. + ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) + } else { + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), 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> { + if key.is_empty() { + // We make the empty key map to the item metadata value. + CollectionMetadataOf::::get(collection).map(|m| m.data.into()) + } else { + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Option::::None, 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 { + match (Collection::::get(collection), Item::::get(collection, item)) { + (Some(cd), Some(id)) if !cd.is_frozen && !id.is_frozen => true, + _ => false, + } + } +} + +impl, I: 'static> Create<::AccountId> for Pallet { + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, + who: &T::AccountId, + admin: &T::AccountId, + ) -> DispatchResult { + Self::do_create_collection( + *collection, + who.clone(), + admin.clone(), + T::CollectionDeposit::get(), + false, + Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, + ) + } +} + +impl, I: 'static> Destroy<::AccountId> for Pallet { + type DestroyWitness = DestroyWitness; + + fn get_destroy_witness(collection: &Self::CollectionId) -> Option { + Collection::::get(collection).map(|a| a.destroy_witness()) + } + + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Self::do_destroy_collection(collection, witness, maybe_check_owner) + } +} + +impl, I: 'static> Mutate<::AccountId> for Pallet { + fn mint_into( + collection: &Self::CollectionId, + item: &Self::ItemId, + who: &T::AccountId, + ) -> DispatchResult { + Self::do_mint(*collection, *item, who.clone(), |_| 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::::NoPermission.into()) + } + } + Ok(()) + }) + } +} + +impl, I: 'static> Transfer for Pallet { + fn transfer( + collection: &Self::CollectionId, + item: &Self::ItemId, + destination: &T::AccountId, + ) -> DispatchResult { + Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) + } +} + +impl, I: 'static> InspectEnumerable for Pallet { + /// Returns an iterator of the collections in existence. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn collections() -> Box> { + Box::new(CollectionMetadataOf::::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) -> Box> { + Box::new(ItemMetadataOf::::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) -> Box> { + Box::new(Account::::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, + ) -> Box> { + Box::new(Account::::iter_key_prefix((who, collection))) + } +} diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs new file mode 100644 index 0000000000000..70f10ca4f8b39 --- /dev/null +++ b/frame/nfts/src/lib.rs @@ -0,0 +1,1498 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Unique (Items) Module +//! +//! A simple, secure module for dealing with non-fungible items. +//! +//! ## Related Modules +//! +//! * [`System`](../frame_system/index.html) +//! * [`Support`](../frame_support/index.html) + +#![recursion_limit = "256"] +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; + +mod functions; +mod impl_nonfungibles; +mod types; + +pub mod migration; +pub mod weights; + +use codec::{Decode, Encode}; +use frame_support::{ + traits::{ + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, + }, + transactional, +}; +use frame_system::Config as SystemConfig; +use sp_runtime::{ + traits::{Saturating, StaticLookup, Zero}, + ArithmeticError, RuntimeDebug, +}; +use sp_std::prelude::*; + +pub use pallet::*; +pub use types::*; +pub use weights::WeightInfo; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn collection(i: u16) -> CollectionId; + fn item(i: u16) -> ItemId; + } + #[cfg(feature = "runtime-benchmarks")] + impl, ItemId: From> BenchmarkHelper for () { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> ItemId { + i.into() + } + } + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Identifier for the collection of item. + type CollectionId: Member + Parameter + MaxEncodedLen + Copy; + + /// The type used to identify a unique item within a collection. + type ItemId: Member + Parameter + MaxEncodedLen + Copy; + + /// The currency mechanism, used for paying for reserves. + type Currency: ReservableCurrency; + + /// The origin which may forcibly create or destroy an item or otherwise alter privileged + /// attributes. + type ForceOrigin: EnsureOrigin; + + /// Standard collection creation is only allowed if the origin attempting it and the + /// collection are in this set. + type CreateOrigin: EnsureOriginWithArg< + Success = Self::AccountId, + Self::Origin, + Self::CollectionId, + >; + + /// Locker trait to enable Locking mechanism downstream. + type Locker: Locker; + + /// The basic amount of funds that must be reserved for collection. + #[pallet::constant] + type CollectionDeposit: Get>; + + /// The basic amount of funds that must be reserved for an item. + #[pallet::constant] + type ItemDeposit: Get>; + + /// The basic amount of funds that must be reserved when adding metadata to your item. + #[pallet::constant] + type MetadataDepositBase: Get>; + + /// The basic amount of funds that must be reserved when adding an attribute to an item. + #[pallet::constant] + type AttributeDepositBase: Get>; + + /// The additional funds that must be reserved for the number of bytes store in metadata, + /// either "normal" metadata or attribute metadata. + #[pallet::constant] + type DepositPerByte: Get>; + + /// The maximum length of data stored on-chain. + #[pallet::constant] + type StringLimit: Get; + + /// The maximum length of an attribute key. + #[pallet::constant] + type KeyLimit: Get; + + /// The maximum length of an attribute value. + #[pallet::constant] + type ValueLimit: Get; + + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type Helper: BenchmarkHelper; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::storage] + #[pallet::storage_prefix = "Class"] + /// Details of a collection. + pub(super) type Collection, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionDetails>, + >; + + #[pallet::storage] + /// The collection, if any, of which an account is willing to take ownership. + pub(super) type OwnershipAcceptance, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; + + #[pallet::storage] + /// The items held by any given account; set out this way so that items owned by a single + /// account can be enumerated. + pub(super) type Account, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, // owner + NMapKey, + NMapKey, + ), + (), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::storage_prefix = "ClassAccount"] + /// The collections owned by any given account; set out this way so that collections owned by + /// a single account can be enumerated. + pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::CollectionId, + (), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::storage_prefix = "Asset"] + /// The items in existence and their ownership details. + pub(super) type Item, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemDetails>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::storage_prefix = "ClassMetadataOf"] + /// Metadata of a collection. + pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionMetadata, T::StringLimit>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::storage_prefix = "InstanceMetadataOf"] + /// Metadata of an item. + pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemMetadata, T::StringLimit>, + OptionQuery, + >; + + #[pallet::storage] + /// Attributes of a collection. + pub(super) type Attribute, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, + NMapKey>, + NMapKey>, + ), + (BoundedVec, DepositBalanceOf), + OptionQuery, + >; + + #[pallet::storage] + /// Price of an asset instance. + pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + (ItemPrice, Option), + OptionQuery, + >; + + #[pallet::storage] + /// Keeps track of the number of items a collection might have. + pub(super) type CollectionMaxSupply, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A `collection` was created. + Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId }, + /// A `collection` was force-created. + ForceCreated { collection: T::CollectionId, owner: T::AccountId }, + /// A `collection` was destroyed. + Destroyed { collection: T::CollectionId }, + /// An `item` was issued. + Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// An `item` was transferred. + Transferred { + collection: T::CollectionId, + item: T::ItemId, + from: T::AccountId, + to: T::AccountId, + }, + /// An `item` was destroyed. + Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// Some `item` was frozen. + Frozen { collection: T::CollectionId, item: T::ItemId }, + /// Some `item` was thawed. + Thawed { collection: T::CollectionId, item: T::ItemId }, + /// Some `collection` was frozen. + CollectionFrozen { collection: T::CollectionId }, + /// Some `collection` was thawed. + CollectionThawed { collection: T::CollectionId }, + /// The owner changed. + OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, + /// The management team changed. + TeamChanged { + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + }, + /// An `item` of a `collection` has been approved by the `owner` for transfer by + /// a `delegate`. + ApprovedTransfer { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + }, + /// An approval for a `delegate` account to transfer the `item` of an item + /// `collection` was cancelled by its `owner`. + ApprovalCancelled { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + }, + /// A `collection` has had its attributes changed by the `Force` origin. + ItemStatusChanged { collection: T::CollectionId }, + /// New metadata has been set for a `collection`. + CollectionMetadataSet { + collection: T::CollectionId, + data: BoundedVec, + is_frozen: bool, + }, + /// Metadata has been cleared for a `collection`. + CollectionMetadataCleared { collection: T::CollectionId }, + /// New metadata has been set for an item. + MetadataSet { + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + is_frozen: bool, + }, + /// Metadata has been cleared for an item. + MetadataCleared { collection: T::CollectionId, item: T::ItemId }, + /// Metadata has been cleared for an item. + Redeposited { collection: T::CollectionId, successful_items: Vec }, + /// New attribute metadata has been set for a `collection` or `item`. + AttributeSet { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + }, + /// Attribute metadata has been cleared for a `collection` or `item`. + AttributeCleared { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + }, + /// Ownership acceptance has changed for an account. + OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, + /// Max supply has been set for a collection. + CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// The price was set for the instance. + ItemPriceSet { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + whitelisted_buyer: Option, + }, + /// The price for the instance was removed. + ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, + /// An item was bought. + ItemBought { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + seller: T::AccountId, + buyer: T::AccountId, + }, + } + + #[pallet::error] + pub enum Error { + /// The signing account has no permission to do the operation. + NoPermission, + /// The given item ID is unknown. + UnknownCollection, + /// The item ID has already been used for an item. + AlreadyExists, + /// The owner turned out to be different to what was expected. + WrongOwner, + /// Invalid witness data given. + BadWitness, + /// The item ID is already taken. + InUse, + /// The item or collection is frozen. + Frozen, + /// The delegate turned out to be different to what was expected. + WrongDelegate, + /// There is no delegate approved. + NoDelegate, + /// No approval exists that would allow the transfer. + Unapproved, + /// The named owner has not signed ownership of the collection is acceptable. + Unaccepted, + /// The item is locked. + Locked, + /// All items have been minted. + MaxSupplyReached, + /// The max supply has already been set. + MaxSupplyAlreadySet, + /// The provided max supply is less to the amount of items a collection already has. + MaxSupplyTooSmall, + /// The given item ID is unknown. + UnknownItem, + /// Item is not for sale. + NotForSale, + /// The provided bid is too low. + BidTooLow, + } + + impl, I: 'static> Pallet { + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Issue a new collection of non-fungible items from a public origin. + /// + /// This new collection has no items initially and its owner is the origin. + /// + /// The origin must be Signed and the sender must have sufficient funds free. + /// + /// `ItemDeposit` funds of sender are reserved. + /// + /// Parameters: + /// - `collection`: The identifier of the new collection. This must not be currently in use. + /// - `admin`: The admin of this collection. The admin is the initial address of each + /// member of the collection's admin team. + /// + /// Emits `Created` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::create())] + pub fn create( + origin: OriginFor, + collection: T::CollectionId, + admin: AccountIdLookupOf, + ) -> DispatchResult { + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; + let admin = T::Lookup::lookup(admin)?; + + Self::do_create_collection( + collection, + owner.clone(), + admin.clone(), + T::CollectionDeposit::get(), + false, + Event::Created { collection, creator: owner, owner: admin }, + ) + } + + /// Issue a new collection of non-fungible items from a privileged origin. + /// + /// This new collection has no items initially. + /// + /// The origin must conform to `ForceOrigin`. + /// + /// Unlike `create`, no funds are reserved. + /// + /// - `collection`: The identifier of the new item. This must not be currently in use. + /// - `owner`: The owner of this collection of items. The owner has full superuser + /// permissions + /// over this item, but may later change and configure the permissions using + /// `transfer_ownership` and `set_team`. + /// + /// Emits `ForceCreated` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_create())] + pub fn force_create( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + free_holding: bool, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + + Self::do_create_collection( + collection, + owner.clone(), + owner.clone(), + Zero::zero(), + free_holding, + Event::ForceCreated { collection, owner }, + ) + } + + /// Destroy a collection of fungible items. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the + /// owner of the `collection`. + /// + /// - `collection`: The identifier of the collection to be destroyed. + /// - `witness`: Information on the items minted in the collection. This must be + /// correct. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(n + m)` where: + /// - `n = witness.items` + /// - `m = witness.item_metadatas` + /// - `a = witness.attributes` + #[pallet::weight(T::WeightInfo::destroy( + witness.items, + witness.item_metadatas, + witness.attributes, + ))] + pub fn destroy( + origin: OriginFor, + collection: T::CollectionId, + witness: DestroyWitness, + ) -> DispatchResultWithPostInfo { + let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { + Ok(_) => None, + Err(origin) => Some(ensure_signed(origin)?), + }; + let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; + + Ok(Some(T::WeightInfo::destroy( + details.items, + details.item_metadatas, + details.attributes, + )) + .into()) + } + + /// Mint an item of a particular collection. + /// + /// The origin must be Signed and the sender must be the Issuer of the `collection`. + /// + /// - `collection`: The collection of the item to be minted. + /// - `item`: The item value of the item to be minted. + /// - `beneficiary`: The initial owner of the minted item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::mint())] + pub fn mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + + Self::do_mint(collection, item, owner, |collection_details| { + ensure!(collection_details.issuer == origin, Error::::NoPermission); + Ok(()) + }) + } + + /// Destroy a single item. + /// + /// Origin must be Signed and the sender should be the Admin of the `collection`. + /// + /// - `collection`: The collection of the item to be burned. + /// - `item`: The item of the item to be burned. + /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the + /// item is owned by this value. + /// + /// Emits `Burned` with the actual amount burned. + /// + /// Weight: `O(1)` + /// Modes: `check_owner.is_some()`. + #[pallet::weight(T::WeightInfo::burn())] + pub fn burn( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + check_owner: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; + + Self::do_burn(collection, item, |collection_details, details| { + let is_permitted = collection_details.admin == origin || details.owner == origin; + ensure!(is_permitted, Error::::NoPermission); + ensure!( + check_owner.map_or(true, |o| o == details.owner), + Error::::WrongOwner + ); + Ok(()) + }) + } + + /// Move an item from the sender account to another. + /// + /// Origin must be Signed and the signing account must be either: + /// - the Admin of the `collection`; + /// - the Owner of the `item`; + /// - the approved delegate for the `item` (in this case, the approval is reset). + /// + /// Arguments: + /// - `collection`: The collection of the item to be transferred. + /// - `item`: The item of the item to be transferred. + /// - `dest`: The account to receive ownership of the item. + /// + /// Emits `Transferred`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::transfer())] + pub fn transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + dest: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + + Self::do_transfer(collection, item, dest, |collection_details, details| { + if details.owner != origin && collection_details.admin != origin { + let approved = details.approved.take().map_or(false, |i| i == origin); + ensure!(approved, Error::::NoPermission); + } + Ok(()) + }) + } + + /// Reevaluate the deposits on some items. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection to be frozen. + /// - `items`: The items of the collection whose deposits will be reevaluated. + /// + /// NOTE: This exists as a best-effort function. Any items which are unknown or + /// in the case that the owner account does not have reservable funds to pay for a + /// deposit increase are ignored. Generally the owner isn't going to call this on items + /// whose existing deposit is less than the refreshed deposit as it would only cost them, + /// so it's of little consequence. + /// + /// It will still return an error in the case that the collection is unknown of the signer + /// is not permitted to call it. + /// + /// Weight: `O(items.len())` + #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] + pub fn redeposit( + origin: OriginFor, + collection: T::CollectionId, + items: Vec, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.owner == origin, Error::::NoPermission); + let deposit = match collection_details.free_holding { + true => Zero::zero(), + false => T::ItemDeposit::get(), + }; + + let mut successful = Vec::with_capacity(items.len()); + for item in items.into_iter() { + let mut details = match Item::::get(&collection, &item) { + Some(x) => x, + None => continue, + }; + let old = details.deposit; + if old > deposit { + T::Currency::unreserve(&collection_details.owner, old - deposit); + } else if deposit > old { + if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() { + // NOTE: No alterations made to collection_details in this iteration so far, + // so this is OK to do. + continue + } + } else { + continue + } + collection_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_reduce(old); + details.deposit = deposit; + Item::::insert(&collection, &item, &details); + successful.push(item); + } + Collection::::insert(&collection, &collection_details); + + Self::deposit_event(Event::::Redeposited { + collection, + successful_items: successful, + }); + + Ok(()) + } + + /// Disallow further unprivileged transfer of an item. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection of the item to be frozen. + /// - `item`: The item of the item to be frozen. + /// + /// Emits `Frozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze())] + pub fn freeze( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + + details.is_frozen = true; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::::Frozen { collection, item }); + Ok(()) + } + + /// Re-allow unprivileged transfer of an item. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection of the item to be thawed. + /// - `item`: The item of the item to be thawed. + /// + /// Emits `Thawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw())] + pub fn thaw( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.admin == origin, Error::::NoPermission); + + details.is_frozen = false; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::::Thawed { collection, item }); + Ok(()) + } + + /// Disallow further unprivileged transfers for a whole collection. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection to be frozen. + /// + /// Emits `CollectionFrozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze_collection())] + pub fn freeze_collection( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); + + details.is_frozen = true; + + Self::deposit_event(Event::::CollectionFrozen { collection }); + Ok(()) + }) + } + + /// Re-allow unprivileged transfers for a whole collection. + /// + /// Origin must be Signed and the sender should be the Admin of the `collection`. + /// + /// - `collection`: The collection to be thawed. + /// + /// Emits `CollectionThawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw_collection())] + pub fn thaw_collection( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.admin, Error::::NoPermission); + + details.is_frozen = false; + + Self::deposit_event(Event::::CollectionThawed { collection }); + Ok(()) + }) + } + + /// Change the Owner of a collection. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection whose owner should be changed. + /// - `owner`: The new Owner of this collection. They must have called + /// `set_accept_ownership` with `collection` in order for this operation to succeed. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::transfer_ownership())] + pub fn transfer_ownership( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } + + /// Change the Issuer, Admin and Freezer of a collection. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection whose team should be changed. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. + /// + /// Emits `TeamChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_team())] + pub fn set_team( + origin: OriginFor, + collection: T::CollectionId, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + + details.issuer = issuer.clone(); + details.admin = admin.clone(); + details.freezer = freezer.clone(); + + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); + Ok(()) + }) + } + + /// Approve an item to be transferred by a delegated third-party account. + /// + /// Origin must be Signed and must be the owner of the `item`. + /// + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `item`: The item of the item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer the item. + /// + /// Emits `ApprovedTransfer` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub fn approve_transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let maybe_check: Option = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + + let delegate = T::Lookup::lookup(delegate)?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + if let Some(check) = maybe_check { + let permitted = check == collection_details.admin || check == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + details.approved = Some(delegate); + Item::::insert(&collection, &item, &details); + + let delegate = details.approved.expect("set as Some above; qed"); + Self::deposit_event(Event::ApprovedTransfer { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + /// Cancel the prior approval for the transfer of an item by a delegate. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; + /// + /// Arguments: + /// - `collection`: The collection of the item of whose approval will be cancelled. + /// - `item`: The item of the item of whose approval will be cancelled. + /// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to + /// which permission of transfer is delegated. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub fn cancel_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + maybe_check_delegate: Option>, + ) -> DispatchResult { + let maybe_check: Option = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + if let Some(check) = maybe_check { + let permitted = check == collection_details.admin || check == details.owner; + ensure!(permitted, Error::::NoPermission); + } + let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?; + let old = details.approved.take().ok_or(Error::::NoDelegate)?; + if let Some(check_delegate) = maybe_check_delegate { + ensure!(check_delegate == old, Error::::WrongDelegate); + } + + Item::::insert(&collection, &item, &details); + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate: old, + }); + + Ok(()) + } + + /// Alter the attributes of a given item. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the item. + /// - `owner`: The new Owner of this item. + /// - `issuer`: The new Issuer of this item. + /// - `admin`: The new Admin of this item. + /// - `freezer`: The new Freezer of this item. + /// - `free_holding`: Whether a deposit is taken for holding an item of this collection. + /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin + /// instructions. + /// + /// Emits `ItemStatusChanged` with the identity of the item. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_item_status())] + pub fn force_item_status( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, + free_holding: bool, + is_frozen: bool, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + + Collection::::try_mutate(collection, |maybe_item| { + let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; + let old_owner = item.owner; + let new_owner = T::Lookup::lookup(owner)?; + item.owner = new_owner.clone(); + item.issuer = T::Lookup::lookup(issuer)?; + item.admin = T::Lookup::lookup(admin)?; + item.freezer = T::Lookup::lookup(freezer)?; + item.free_holding = free_holding; + item.is_frozen = is_frozen; + *maybe_item = Some(item); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + + Self::deposit_event(Event::ItemStatusChanged { collection }); + Ok(()) + }) + } + + /// Set an attribute for a collection or item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// If the origin is Signed, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `key`: The key of the attribute. + /// - `value`: The value to which to set the attribute. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_attribute())] + pub fn set_attribute( + origin: OriginFor, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + }; + ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); + + let attribute = Attribute::::get((collection, maybe_item, &key)); + if attribute.is_none() { + collection_details.attributes.saturating_inc(); + } + let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if !collection_details.free_holding && maybe_check_owner.is_some() { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + collection_details.total_deposit.saturating_accrue(deposit); + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Ok(()) + } + + /// Clear an attribute for a collection or item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `maybe_item`: The identifier of the item whose metadata to clear. + /// - `key`: The key of the attribute. + /// + /// Emits `AttributeCleared`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::clear_attribute())] + pub fn clear_attribute( + origin: OriginFor, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + }; + ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); + + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + } + Ok(()) + } + + /// Set the metadata for an item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// If the origin is Signed, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * data.len` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `item`: The identifier of the item whose metadata to set. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. + /// - `is_frozen`: Whether the metadata should be frozen against further changes. + /// + /// Emits `MetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_metadata())] + pub fn set_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + is_frozen: bool, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); + ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + + if metadata.is_none() { + collection_details.item_metadatas.saturating_inc(); + } + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if !collection_details.free_holding && maybe_check_owner.is_some() { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + collection_details.total_deposit.saturating_accrue(deposit); + + *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen }); + Ok(()) + }) + } + + /// Clear the metadata for an item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `item`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `item`: The identifier of the item whose metadata to clear. + /// + /// Emits `MetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); + ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + + if metadata.is_some() { + collection_details.item_metadatas.saturating_dec(); + } + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); + Ok(()) + }) + } + + /// Set the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// If the origin is `Signed`, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * data.len` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the item whose metadata to update. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. + /// - `is_frozen`: Whether the metadata should be frozen against further changes. + /// + /// Emits `CollectionMetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_collection_metadata())] + pub fn set_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + data: BoundedVec, + is_frozen: bool, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); + ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if maybe_check_owner.is_some() && !details.free_holding { + 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.total_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen }); + Ok(()) + }) + } + + /// Clear the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose metadata to clear. + /// + /// Emits `CollectionMetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::clear_collection_metadata())] + pub fn clear_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); + ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&details.owner, deposit); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } + + /// Set (or reset) the acceptance of ownership for a particular account. + /// + /// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a + /// provider reference. + /// + /// - `maybe_collection`: The identifier of the collection whose ownership the signer is + /// willing to accept, or if `None`, an indication that the signer is willing to accept no + /// ownership transferal. + /// + /// Emits `OwnershipAcceptanceChanged`. + #[pallet::weight(T::WeightInfo::set_accept_ownership())] + pub fn set_accept_ownership( + origin: OriginFor, + maybe_collection: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } + + /// Set the maximum amount of items a collection could have. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// Note: This function can only succeed once per collection. + /// + /// - `collection`: The identifier of the collection to change. + /// - `max_supply`: The maximum amount of items a collection could have. + /// + /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn set_collection_max_supply( + origin: OriginFor, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + ensure!( + !CollectionMaxSupply::::contains_key(&collection), + Error::::MaxSupplyAlreadySet + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionMaxSupply::::insert(&collection, max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + } + + /// Set (or reset) the price for an item. + /// + /// Origin must be Signed and must be the owner of the asset `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item to set the price for. + /// - `price`: The price for the item. Pass `None`, to reset the price. + /// - `buyer`: Restricts the buy operation to a specific account. + /// + /// Emits `ItemPriceSet` on success if the price is not `None`. + /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::weight(T::WeightInfo::set_price())] + pub fn set_price( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + price: Option>, + whitelisted_buyer: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; + Self::do_set_price(collection, item, origin, price, whitelisted_buyer) + } + + /// Allows to buy an item if it's up for sale. + /// + /// Origin must be Signed and must not be the owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item the sender wants to buy. + /// - `bid_price`: The price the sender is willing to pay. + /// + /// Emits `ItemBought` on success. + #[pallet::weight(T::WeightInfo::buy_item())] + #[transactional] + pub fn buy_item( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + bid_price: ItemPrice, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_buy_item(collection, item, origin, bid_price) + } + } +} diff --git a/frame/nfts/src/migration.rs b/frame/nfts/src/migration.rs new file mode 100644 index 0000000000000..d301f0a3d1eb1 --- /dev/null +++ b/frame/nfts/src/migration.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 super::*; +use frame_support::{ + traits::{Get, GetStorageVersion, PalletInfoAccess, StorageVersion}, + weights::Weight, +}; + +/// Migrate the pallet storage to v1. +pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfoAccess>( +) -> frame_support::weights::Weight { + let on_chain_storage_version =

::on_chain_storage_version(); + log::info!( + target: "runtime::uniques", + "Running migration storage v1 for uniques with storage version {:?}", + on_chain_storage_version, + ); + + if on_chain_storage_version < 1 { + let mut count = 0; + for (collection, detail) in Collection::::iter() { + CollectionAccount::::insert(&detail.owner, &collection, ()); + count += 1; + } + StorageVersion::new(1).put::

(); + log::info!( + target: "runtime::uniques", + "Running migration storage v1 for uniques with storage version {:?} was complete", + on_chain_storage_version, + ); + // calculate and return migration weights + T::DbWeight::get().reads_writes(count as Weight + 1, count as Weight + 1) + } else { + log::warn!( + target: "runtime::uniques", + "Attempted to apply migration to v1 but failed because storage version is {:?}", + on_chain_storage_version, + ); + T::DbWeight::get().reads(1) + } +} diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs new file mode 100644 index 0000000000000..ff7b791de4950 --- /dev/null +++ b/frame/nfts/src/mock.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 Uniques pallet. + +use super::*; +use crate as pallet_uniques; + +use frame_support::{ + construct_runtime, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; +} + +impl Config for Test { + type Event = Event; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + 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 WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs new file mode 100644 index 0000000000000..8b1d00d7ba0c7 --- /dev/null +++ b/frame/nfts/src/tests.rs @@ -0,0 +1,872 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for Uniques pallet. + +use crate::{mock::*, Event, *}; +use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency}; +use pallet_balances::Error as BalancesError; +use sp_std::prelude::*; + +fn items() -> Vec<(u64, u32, u32)> { + let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); + r.sort(); + let mut s: Vec<_> = Item::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); + s.sort(); + assert_eq!(r, s); + for collection in Item::::iter() + .map(|x| x.0) + .scan(None, |s, item| { + if s.map_or(false, |last| last == item) { + *s = Some(item); + Some(None) + } else { + Some(Some(item)) + } + }) + .flatten() + { + let details = Collection::::get(collection).unwrap(); + let items = Item::::iter_prefix(collection).count() as u32; + assert_eq!(details.items, items); + } + r +} + +fn collections() -> Vec<(u64, u32)> { + let mut r: Vec<_> = CollectionAccount::::iter().map(|x| (x.0, x.1)).collect(); + r.sort(); + let mut s: Vec<_> = Collection::::iter().map(|x| (x.1.owner, x.0)).collect(); + s.sort(); + assert_eq!(r, s); + r +} + +macro_rules! bvec { + ($( $x:tt )*) => { + vec![$( $x )*].try_into().unwrap() + } +} + +fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { + let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) + .map(|(k, v)| (k.0, k.1.into(), v.0.into())) + .collect(); + s.sort(); + s +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let mock::Event::Uniques(inner) = e { Some(inner) } else { None }) + .collect::>(); + + System::reset_events(); + + result +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(items(), vec![]); + }); +} + +#[test] +fn basic_minting_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_eq!(items(), vec![(1, 0, 42)]); + + assert_ok!(Uniques::force_create(Origin::root(), 1, 2, true)); + assert_eq!(collections(), vec![(1, 0), (2, 1)]); + assert_ok!(Uniques::mint(Origin::signed(2), 1, 69, 1)); + assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); + }); +} + +#[test] +fn lifecycle_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_eq!(Balances::reserved_balance(&1), 2); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); + assert_eq!(Balances::reserved_balance(&1), 5); + assert!(CollectionMetadataOf::::contains_key(0)); + + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 10)); + assert_eq!(Balances::reserved_balance(&1), 6); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 20)); + assert_eq!(Balances::reserved_balance(&1), 7); + assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 2); + assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert!(ItemMetadataOf::::contains_key(0, 42)); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); + assert_eq!(Balances::reserved_balance(&1), 13); + assert!(ItemMetadataOf::::contains_key(0, 69)); + + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_eq!(w.items, 2); + assert_eq!(w.item_metadatas, 2); + assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert!(!Collection::::contains_key(0)); + assert!(!Item::::contains_key(0, 42)); + assert!(!Item::::contains_key(0, 69)); + assert!(!CollectionMetadataOf::::contains_key(0)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert_eq!(collections(), vec![]); + assert_eq!(items(), vec![]); + }); +} + +#[test] +fn destroy_with_bad_witness_should_not_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_noop!(Uniques::destroy(Origin::signed(1), 0, w), Error::::BadWitness); + }); +} + +#[test] +fn mint_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_eq!(Uniques::owner(0, 42).unwrap(), 1); + assert_eq!(collections(), vec![(1, 0)]); + assert_eq!(items(), vec![(1, 0, 42)]); + }); +} + +#[test] +fn transfer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + + assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 3)); + assert_eq!(items(), vec![(3, 0, 42)]); + assert_noop!(Uniques::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); + + assert_ok!(Uniques::approve_transfer(Origin::signed(3), 0, 42, 2)); + assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 4)); + }); +} + +#[test] +fn freezing_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::freeze(Origin::signed(1), 0, 42)); + assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + + assert_ok!(Uniques::thaw(Origin::signed(1), 0, 42)); + assert_ok!(Uniques::freeze_collection(Origin::signed(1), 0)); + assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + + assert_ok!(Uniques::thaw_collection(Origin::signed(1), 0)); + assert_ok!(Uniques::transfer(Origin::signed(1), 0, 42, 2)); + }); +} + +#[test] +fn origin_guards_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + + Balances::make_free_balance_be(&2, 100); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); + assert_noop!( + Uniques::transfer_ownership(Origin::signed(2), 0, 2), + Error::::NoPermission + ); + assert_noop!(Uniques::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); + assert_noop!(Uniques::freeze(Origin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Uniques::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Uniques::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); + assert_noop!(Uniques::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_noop!(Uniques::destroy(Origin::signed(2), 0, w), Error::::NoPermission); + }); +} + +#[test] +fn transfer_owner_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_eq!(collections(), vec![(1, 0)]); + assert_noop!( + Uniques::transfer_ownership(Origin::signed(1), 0, 2), + Error::::Unaccepted + ); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); + assert_ok!(Uniques::transfer_ownership(Origin::signed(1), 0, 2)); + + assert_eq!(collections(), vec![(2, 0)]); + assert_eq!(Balances::total_balance(&1), 98); + assert_eq!(Balances::total_balance(&2), 102); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&2), 2); + + assert_ok!(Uniques::set_accept_ownership(Origin::signed(1), Some(0))); + assert_noop!( + Uniques::transfer_ownership(Origin::signed(1), 0, 1), + Error::::NoPermission + ); + + // Mint and set metadata now and make sure that deposit gets transferred back. + assert_ok!(Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(3), Some(0))); + assert_ok!(Uniques::transfer_ownership(Origin::signed(2), 0, 3)); + assert_eq!(collections(), vec![(3, 0)]); + assert_eq!(Balances::total_balance(&2), 57); + assert_eq!(Balances::total_balance(&3), 145); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_eq!(Balances::reserved_balance(&3), 45); + + // 2's acceptence from before is reset when it became owner, so it cannot be transfered + // without a fresh acceptance. + assert_noop!( + Uniques::transfer_ownership(Origin::signed(3), 0, 2), + Error::::Unaccepted + ); + }); +} + +#[test] +fn set_team_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 2)); + assert_ok!(Uniques::freeze(Origin::signed(4), 0, 42)); + assert_ok!(Uniques::thaw(Origin::signed(3), 0, 42)); + assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 3)); + assert_ok!(Uniques::burn(Origin::signed(3), 0, 42, None)); + }); +} + +#[test] +fn set_collection_metadata_should_work() { + new_test_ext().execute_with(|| { + // Cannot add metadata to unknown item + assert_noop!( + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), + Error::::UnknownCollection, + ); + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + // Cannot add metadata to unowned item + assert_noop!( + Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + Balances::make_free_balance_be(&1, 30); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); + assert_eq!(Balances::free_balance(&1), 9); + assert!(CollectionMetadataOf::::contains_key(0)); + + // Force origin works, too. + assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 18], false)); + + // Update deposit + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); + assert_eq!(Balances::free_balance(&1), 14); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); + assert_eq!(Balances::free_balance(&1), 4); + + // Cannot over-reserve + assert_noop!( + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); + assert_noop!( + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), + Error::::Frozen, + ); + assert_noop!( + Uniques::clear_collection_metadata(Origin::signed(1), 0), + Error::::Frozen + ); + + // Clear Metadata + assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 15], false)); + assert_noop!( + Uniques::clear_collection_metadata(Origin::signed(2), 0), + Error::::NoPermission + ); + assert_noop!( + Uniques::clear_collection_metadata(Origin::signed(1), 1), + Error::::UnknownCollection + ); + assert_ok!(Uniques::clear_collection_metadata(Origin::signed(1), 0)); + assert!(!CollectionMetadataOf::::contains_key(0)); + }); +} + +#[test] +fn set_item_metadata_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 30); + + // Cannot add metadata to unknown item + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + // Cannot add metadata to unowned item + assert_noop!( + Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_eq!(Balances::free_balance(&1), 8); + assert!(ItemMetadataOf::::contains_key(0, 42)); + + // Force origin works, too. + assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); + + // Update deposit + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_eq!(Balances::free_balance(&1), 13); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_eq!(Balances::free_balance(&1), 3); + + // Cannot over-reserve + assert_noop!( + Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 40], false), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_noop!( + Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false), + Error::::Frozen, + ); + assert_noop!(Uniques::clear_metadata(Origin::signed(1), 0, 42), Error::::Frozen); + + // Clear Metadata + assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 15], false)); + assert_noop!( + Uniques::clear_metadata(Origin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Uniques::clear_metadata(Origin::signed(1), 1, 42), + Error::::UnknownCollection + ); + assert_ok!(Uniques::clear_metadata(Origin::signed(1), 0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + }); +} + +#[test] +fn set_attribute_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_eq!( + attributes(0), + vec![ + (None, bvec![0], bvec![0]), + (Some(0), bvec![0], bvec![0]), + (Some(0), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 9); + + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_eq!( + attributes(0), + vec![ + (None, bvec![0], bvec![0; 10]), + (Some(0), bvec![0], bvec![0]), + (Some(0), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 18); + + assert_ok!(Uniques::clear_attribute(Origin::signed(1), 0, Some(0), bvec![1])); + assert_eq!( + attributes(0), + vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] + ); + assert_eq!(Balances::reserved_balance(1), 15); + + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn set_attribute_should_respect_freeze() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_eq!( + attributes(0), + vec![ + (None, bvec![0], bvec![0]), + (Some(0), bvec![0], bvec![0]), + (Some(1), bvec![0], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 9); + + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![], true)); + let e = Error::::Frozen; + assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); + + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 0, bvec![], true)); + let e = Error::::Frozen; + assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1]), e); + assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![1])); + }); +} + +#[test] +fn force_item_status_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 2)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_eq!(Balances::reserved_balance(1), 65); + + // force item status to be free holding + assert_ok!(Uniques::force_item_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 142, 1)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 169, 2)); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 169, bvec![0; 20], false)); + assert_eq!(Balances::reserved_balance(1), 65); + + assert_ok!(Uniques::redeposit(Origin::signed(1), 0, bvec![0, 42, 50, 69, 100])); + assert_eq!(Balances::reserved_balance(1), 63); + + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_eq!(Balances::reserved_balance(1), 42); + + assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_eq!(Balances::reserved_balance(1), 21); + + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); + + assert_noop!( + Uniques::burn(Origin::signed(5), 0, 42, Some(5)), + Error::::UnknownCollection + ); + + assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 5)); + assert_ok!(Uniques::mint(Origin::signed(2), 0, 69, 5)); + assert_eq!(Balances::reserved_balance(1), 2); + + assert_noop!(Uniques::burn(Origin::signed(0), 0, 42, None), Error::::NoPermission); + assert_noop!(Uniques::burn(Origin::signed(5), 0, 42, Some(6)), Error::::WrongOwner); + + assert_ok!(Uniques::burn(Origin::signed(5), 0, 42, Some(5))); + assert_ok!(Uniques::burn(Origin::signed(3), 0, 69, Some(5))); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn approval_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 4)); + assert_noop!(Uniques::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); + assert!(Item::::get(0, 42).unwrap().approved.is_none()); + + assert_ok!(Uniques::approve_transfer(Origin::signed(4), 0, 42, 2)); + assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 2)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + + assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_noop!( + Uniques::cancel_approval(Origin::signed(2), 1, 42, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::signed(2), 0, 43, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::signed(3), 0, 42, None), + Error::::NoPermission + ); + assert_noop!( + Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(4)), + Error::::WrongDelegate + ); + + assert_ok!(Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(3))); + assert_noop!( + Uniques::cancel_approval(Origin::signed(2), 0, 42, None), + Error::::NoDelegate + ); + }); +} + +#[test] +fn cancel_approval_works_with_admin() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + + assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_noop!( + Uniques::cancel_approval(Origin::signed(1), 1, 42, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::signed(1), 0, 43, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(4)), + Error::::WrongDelegate + ); + + assert_ok!(Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(3))); + assert_noop!( + Uniques::cancel_approval(Origin::signed(1), 0, 42, None), + Error::::NoDelegate + ); + }); +} + +#[test] +fn cancel_approval_works_with_force() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + + assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_noop!( + Uniques::cancel_approval(Origin::root(), 1, 42, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::root(), 0, 43, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(Origin::root(), 0, 42, Some(4)), + Error::::WrongDelegate + ); + + assert_ok!(Uniques::cancel_approval(Origin::root(), 0, 42, Some(3))); + assert_noop!( + Uniques::cancel_approval(Origin::root(), 0, 42, None), + Error::::NoDelegate + ); + }); +} + +#[test] +fn max_supply_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let max_supply = 2; + + // validate set_collection_max_supply + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); + + assert_ok!(Uniques::set_collection_max_supply( + Origin::signed(user_id), + collection_id, + max_supply + )); + assert_eq!(CollectionMaxSupply::::get(collection_id).unwrap(), max_supply); + + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + + assert_noop!( + Uniques::set_collection_max_supply( + Origin::signed(user_id), + collection_id, + max_supply + 1 + ), + Error::::MaxSupplyAlreadySet + ); + + // validate we can't mint more to max supply + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 1, user_id)); + assert_noop!( + Uniques::mint(Origin::signed(user_id), collection_id, 2, user_id), + Error::::MaxSupplyReached + ); + + // validate we remove the CollectionMaxSupply record when we destroy the collection + assert_ok!(Uniques::destroy( + Origin::signed(user_id), + collection_id, + Collection::::get(collection_id).unwrap().destroy_witness() + )); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); + }); +} + +#[test] +fn set_price_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_2, user_id)); + + assert_ok!(Uniques::set_price( + Origin::signed(user_id), + collection_id, + item_1, + Some(1), + None, + )); + + assert_ok!(Uniques::set_price( + Origin::signed(user_id), + collection_id, + item_2, + Some(2), + Some(3) + )); + + let item = ItemPriceOf::::get(collection_id, item_1).unwrap(); + assert_eq!(item.0, 1); + assert_eq!(item.1, None); + + let item = ItemPriceOf::::get(collection_id, item_2).unwrap(); + assert_eq!(item.0, 2); + assert_eq!(item.1, Some(3)); + + assert!(events().contains(&Event::::ItemPriceSet { + collection: collection_id, + item: item_1, + price: 1, + whitelisted_buyer: None, + })); + + // validate we can unset the price + assert_ok!(Uniques::set_price(Origin::signed(user_id), collection_id, item_2, None, None)); + assert!(events().contains(&Event::::ItemPriceRemoved { + collection: collection_id, + item: item_2 + })); + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + }); +} + +#[test] +fn buy_item_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let price_1 = 20; + let price_2 = 30; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_1, true)); + + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_3, user_1)); + + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_1, + Some(price_1), + None, + )); + + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_2, + Some(price_2), + Some(user_3), + )); + + // can't buy for less + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, 1), + Error::::BidTooLow + ); + + // pass the higher price to validate it will still deduct correctly + assert_ok!(Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, price_1 + 1,)); + + // validate the new owner & balances + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + assert_eq!(Balances::total_balance(&user_1), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price_1); + + // can't buy from yourself + assert_noop!( + Uniques::buy_item(Origin::signed(user_1), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can't buy when the item is listed for a specific buyer + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can buy when I'm a whitelisted buyer + assert_ok!(Uniques::buy_item(Origin::signed(user_3), collection_id, item_2, price_2,)); + + assert!(events().contains(&Event::::ItemBought { + collection: collection_id, + item: item_2, + price: price_2, + seller: user_1, + buyer: user_3, + })); + + // ensure we reset the buyer field + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // can't buy when item is not for sale + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_3, price_2), + Error::::NotForSale + ); + + // ensure we can't buy an item when the collection or an item is frozen + { + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_3, + Some(price_1), + None, + )); + + // freeze collection + assert_ok!(Uniques::freeze_collection(Origin::signed(user_1), collection_id)); + + let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + + assert_ok!(Uniques::thaw_collection(Origin::signed(user_1), collection_id)); + + // freeze item + assert_ok!(Uniques::freeze(Origin::signed(user_1), collection_id, item_3)); + + let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + } + }); +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs new file mode 100644 index 0000000000000..98e056163d28d --- /dev/null +++ b/frame/nfts/src/types.rs @@ -0,0 +1,129 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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 basic types for use in the Uniques pallet. + +use super::*; +use frame_support::{ + pallet_prelude::{BoundedVec, MaxEncodedLen}, + traits::Get, +}; +use scale_info::TypeInfo; + +pub(super) type DepositBalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +pub(super) type CollectionDetailsFor = + CollectionDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemDetailsFor = + ItemDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemPrice = + <>::Currency as Currency<::AccountId>>::Balance; + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { + /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. + pub(super) owner: AccountId, + /// Can mint tokens. + pub(super) issuer: AccountId, + /// Can thaw tokens, force transfers and burn tokens from any account. + pub(super) admin: AccountId, + /// Can freeze tokens. + pub(super) freezer: AccountId, + /// The total balance deposited for the all storage associated with this collection. + /// Used by `destroy`. + pub(super) total_deposit: DepositBalance, + /// If `true`, then no deposit is needed to hold items of this collection. + pub(super) free_holding: bool, + /// The total number of outstanding items of this collection. + pub(super) items: u32, + /// The total number of outstanding item metadata of this collection. + pub(super) item_metadatas: u32, + /// The total number of attributes for this collection. + pub(super) attributes: u32, + /// Whether the collection is frozen for non-admin transfers. + pub(super) is_frozen: bool, +} + +/// Witness data for the destroy transactions. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct DestroyWitness { + /// The total number of outstanding items of this collection. + #[codec(compact)] + pub items: u32, + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + #[codec(compact)] + /// The total number of attributes for this collection. + pub attributes: u32, +} + +impl CollectionDetails { + pub fn destroy_witness(&self) -> DestroyWitness { + DestroyWitness { + items: self.items, + item_metadatas: self.item_metadatas, + attributes: self.attributes, + } + } +} + +/// Information concerning the ownership of a single unique item. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct ItemDetails { + /// The owner of this item. + pub(super) owner: AccountId, + /// The approved transferrer of this item, if one is set. + pub(super) approved: Option, + /// Whether the item can be transferred or not. + pub(super) is_frozen: bool, + /// The amount held in the pallet's default account for this item. Free-hold items will have + /// this as zero. + pub(super) deposit: DepositBalance, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(StringLimit))] +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct CollectionMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: DepositBalance, + /// 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(super) data: BoundedVec, + /// Whether the collection's metadata may be changed by a non Force origin. + pub(super) is_frozen: bool, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(StringLimit))] +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct ItemMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: DepositBalance, + /// 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 a + /// hash-addressable global publication system such as IPFS. + pub(super) data: BoundedVec, + /// Whether the item metadata may be changed by a non Force origin. + pub(super) is_frozen: bool, +} diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs new file mode 100644 index 0000000000000..7c8cb170b1b1d --- /dev/null +++ b/frame/nfts/src/weights.rs @@ -0,0 +1,510 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Autogenerated weights for pallet_uniques +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-07-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `test-bench-bot`, CPU: `Intel(R) Xeon(R) CPU @ 3.10GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --pallet=pallet_uniques +// --chain=dev +// --output=./frame/uniques/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_uniques. +pub trait WeightInfo { + fn create() -> Weight; + fn force_create() -> Weight; + fn destroy(n: u32, m: u32, a: u32, ) -> Weight; + fn mint() -> Weight; + fn burn() -> Weight; + fn transfer() -> Weight; + fn redeposit(i: u32, ) -> Weight; + fn freeze() -> Weight; + fn thaw() -> Weight; + fn freeze_collection() -> Weight; + fn thaw_collection() -> Weight; + fn transfer_ownership() -> Weight; + fn set_team() -> Weight; + fn force_item_status() -> Weight; + fn set_attribute() -> Weight; + fn clear_attribute() -> Weight; + fn set_metadata() -> Weight; + fn clear_metadata() -> Weight; + fn set_collection_metadata() -> Weight; + fn clear_collection_metadata() -> Weight; + fn approve_transfer() -> Weight; + fn cancel_approval() -> Weight; + fn set_accept_ownership() -> Weight; + fn set_collection_max_supply() -> Weight; + fn set_price() -> Weight; + fn buy_item() -> Weight; +} + +/// Weights for pallet_uniques using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn create() -> Weight { + (33_075_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_create() -> Weight { + (19_528_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Uniques Attribute (r:0 w:1000) + // Storage: Uniques ClassMetadataOf (r:0 w:1) + // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) + // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 25_000 + .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 25_000 + .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 25_000 + .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) + // Storage: Uniques Account (r:0 w:1) + fn mint() -> Weight { + (42_146_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn burn() -> Weight { + (42_960_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn transfer() -> Weight { + (33_025_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:100 w:100) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 24_000 + .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn freeze() -> Weight { + (25_194_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn thaw() -> Weight { + (25_397_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn freeze_collection() -> Weight { + (19_278_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn thaw_collection() -> Weight { + (19_304_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:2) + fn transfer_ownership() -> Weight { + (28_615_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn set_team() -> Weight { + (19_943_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_item_status() -> Weight { + (22_583_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:0) + // Storage: Uniques Attribute (r:1 w:1) + fn set_attribute() -> Weight { + (47_520_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:0) + // Storage: Uniques Attribute (r:1 w:1) + fn clear_attribute() -> Weight { + (45_316_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:1) + fn set_metadata() -> Weight { + (38_391_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:1) + fn clear_metadata() -> Weight { + (38_023_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassMetadataOf (r:1 w:1) + fn set_collection_metadata() -> Weight { + (37_398_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques ClassMetadataOf (r:1 w:1) + fn clear_collection_metadata() -> Weight { + (35_621_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn approve_transfer() -> Weight { + (25_856_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn cancel_approval() -> Weight { + (26_098_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + (24_076_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + (22_035_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + (22_534_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + (45_272_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn create() -> Weight { + (33_075_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_create() -> Weight { + (19_528_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Uniques Attribute (r:0 w:1000) + // Storage: Uniques ClassMetadataOf (r:0 w:1) + // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) + // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 25_000 + .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 25_000 + .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 25_000 + .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) + // Storage: Uniques Account (r:0 w:1) + fn mint() -> Weight { + (42_146_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn burn() -> Weight { + (42_960_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn transfer() -> Weight { + (33_025_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques Asset (r:100 w:100) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 24_000 + .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn freeze() -> Weight { + (25_194_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn thaw() -> Weight { + (25_397_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn freeze_collection() -> Weight { + (19_278_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn thaw_collection() -> Weight { + (19_304_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:2) + fn transfer_ownership() -> Weight { + (28_615_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + fn set_team() -> Weight { + (19_943_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_item_status() -> Weight { + (22_583_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:0) + // Storage: Uniques Attribute (r:1 w:1) + fn set_attribute() -> Weight { + (47_520_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:0) + // Storage: Uniques Attribute (r:1 w:1) + fn clear_attribute() -> Weight { + (45_316_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:1) + fn set_metadata() -> Weight { + (38_391_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques InstanceMetadataOf (r:1 w:1) + fn clear_metadata() -> Weight { + (38_023_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassMetadataOf (r:1 w:1) + fn set_collection_metadata() -> Weight { + (37_398_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques ClassMetadataOf (r:1 w:1) + fn clear_collection_metadata() -> Weight { + (35_621_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn approve_transfer() -> Weight { + (25_856_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn cancel_approval() -> Weight { + (26_098_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + (24_076_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + (22_035_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + (22_534_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + (45_272_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } +} From af8875d42ac45176a70a9b252b6334a6b62d2337 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 29 Aug 2022 18:44:35 +0300 Subject: [PATCH 02/60] Connect new pallet --- Cargo.lock | 18 ++ Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 25 +- frame/nfts/Cargo.toml | 4 +- frame/nfts/src/benchmarking.rs | 28 +-- frame/nfts/src/lib.rs | 1 - frame/nfts/src/migration.rs | 57 ----- frame/nfts/src/mock.rs | 6 +- frame/nfts/src/tests.rs | 421 ++++++++++++++++----------------- frame/nfts/src/types.rs | 2 +- frame/nfts/src/weights.rs | 10 +- 12 files changed, 270 insertions(+), 307 deletions(-) delete mode 100644 frame/nfts/src/migration.rs diff --git a/Cargo.lock b/Cargo.lock index 64e84fe4710af..945f547fcc0ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3375,6 +3375,7 @@ dependencies = [ "pallet-membership", "pallet-mmr", "pallet-multisig", + "pallet-nfts", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", @@ -5877,6 +5878,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nfts" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-nicks" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index e2907716ca9f2..dfa33c3edc75e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,7 @@ members = [ "frame/offences", "frame/preimage", "frame/proxy", + "frame/nfts", "frame/nomination-pools", "frame/nomination-pools/benchmarking", "frame/nomination-pools/test-staking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 10b15b6ec554d..e9cc7294bd18c 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -77,6 +77,7 @@ pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../. pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } +pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } @@ -185,6 +186,7 @@ std = [ "pallet-remark/std", "pallet-recovery/std", "pallet-uniques/std", + "pallet-nfts/std", "pallet-vesting/std", "log/std", "frame-try-runtime/std", @@ -239,6 +241,7 @@ runtime-benchmarks = [ "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-whitelist/runtime-benchmarks", "frame-system-benchmarking", @@ -289,6 +292,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-treasury/try-runtime", "pallet-uniques/try-runtime", + "pallet-nfts/try-runtime", "pallet-utility/try-runtime", "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0816fedff0347..28cc2452039ac 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -294,7 +294,7 @@ impl InstanceFilter for ProxyType { c, Call::Balances(..) | Call::Assets(..) | Call::Uniques(..) | - Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) | + Call::Nfts(..) | Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) | Call::Indices(pallet_indices::Call::transfer { .. }) ), ProxyType::Governance => matches!( @@ -1476,6 +1476,27 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +impl pallet_nfts::Config for Runtime { + type Event = Event; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; + type MetadataDepositBase = MetadataDepositBase; + type AttributeDepositBase = MetadataDepositBase; + type DepositPerByte = MetadataDepositPerByte; + type StringLimit = StringLimit; + type KeyLimit = KeyLimit; + type ValueLimit = ValueLimit; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Locker = (); +} + impl pallet_transaction_storage::Config for Runtime { type Event = Event; type Currency = Balances; @@ -1628,6 +1649,7 @@ construct_runtime!( Lottery: pallet_lottery, Gilt: pallet_gilt, Uniques: pallet_uniques, + Nfts: pallet_nfts, TransactionStorage: pallet_transaction_storage, BagsList: pallet_bags_list, StateTrieMigration: pallet_state_trie_migration, @@ -1743,6 +1765,7 @@ mod benches { [pallet_transaction_storage, TransactionStorage] [pallet_treasury, Treasury] [pallet_uniques, Uniques] + [pallet_nfts, Nfts] [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 19b0790947f84..7f1ce4ff416b0 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "pallet-uniques" +name = "pallet-nfts" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME NFT asset management pallet" +description = "FRAME NFTs pallet" readme = "README.md" [package.metadata.docs.rs] diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 3e3148b5b5fc2..e30b5ebbd3fe5 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Uniques pallet benchmarking. +//! Nfts pallet benchmarking. #![cfg(feature = "runtime-benchmarks")] @@ -32,7 +32,7 @@ use frame_system::RawOrigin as SystemOrigin; use sp_runtime::traits::Bounded; use sp_std::prelude::*; -use crate::Pallet as Uniques; +use crate::Pallet as Nfts; const SEED: u32 = 0; @@ -42,7 +42,7 @@ fn create_collection, I: 'static>( let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Uniques::::force_create( + assert!(Nfts::::force_create( SystemOrigin::Root.into(), collection, caller_lookup.clone(), @@ -58,7 +58,7 @@ fn add_collection_metadata, I: 'static>() -> (T::AccountId, Account whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Uniques::::set_collection_metadata( + assert!(Nfts::::set_collection_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), @@ -77,7 +77,7 @@ fn mint_item, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let item = T::Helper::item(index); - assert!(Uniques::::mint( + assert!(Nfts::::mint( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, @@ -95,7 +95,7 @@ fn add_item_metadata, I: 'static>( whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Uniques::::set_metadata( + assert!(Nfts::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, @@ -115,7 +115,7 @@ fn add_item_attribute, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); - assert!(Uniques::::set_attribute( + assert!(Nfts::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), Some(item), @@ -209,7 +209,7 @@ benchmarks_instance_pallet! { let i in 0 .. 5_000; let (collection, caller, caller_lookup) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); - Uniques::::force_item_status( + Nfts::::force_item_status( SystemOrigin::Root.into(), collection, caller_lookup.clone(), @@ -235,7 +235,7 @@ benchmarks_instance_pallet! { thaw { let (collection, caller, caller_lookup) = create_collection::(); let (item, ..) = mint_item::(0); - Uniques::::freeze( + Nfts::::freeze( SystemOrigin::Signed(caller.clone()).into(), collection, item, @@ -255,7 +255,7 @@ benchmarks_instance_pallet! { thaw_collection { let (collection, caller, caller_lookup) = create_collection::(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::freeze_collection(origin, collection)?; + Nfts::::freeze_collection(origin, collection)?; }: _(SystemOrigin::Signed(caller.clone()), collection) verify { assert_last_event::(Event::CollectionThawed { collection }.into()); @@ -267,7 +267,7 @@ benchmarks_instance_pallet! { 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(); - Uniques::::set_accept_ownership(origin, Some(collection))?; + Nfts::::set_accept_ownership(origin, Some(collection))?; }: _(SystemOrigin::Signed(caller), collection, target_lookup) verify { assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); @@ -379,7 +379,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; + Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) verify { assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); @@ -430,7 +430,7 @@ benchmarks_instance_pallet! { let buyer_lookup = T::Lookup::unlookup(buyer.clone()); let price = ItemPrice::::from(0u32); let origin = SystemOrigin::Signed(seller.clone()).into(); - Uniques::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + Nfts::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) verify { @@ -443,5 +443,5 @@ benchmarks_instance_pallet! { }.into()); } - impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 70f10ca4f8b39..cb96e8138ba5e 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -39,7 +39,6 @@ mod functions; mod impl_nonfungibles; mod types; -pub mod migration; pub mod weights; use codec::{Decode, Encode}; diff --git a/frame/nfts/src/migration.rs b/frame/nfts/src/migration.rs deleted file mode 100644 index d301f0a3d1eb1..0000000000000 --- a/frame/nfts/src/migration.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 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 super::*; -use frame_support::{ - traits::{Get, GetStorageVersion, PalletInfoAccess, StorageVersion}, - weights::Weight, -}; - -/// Migrate the pallet storage to v1. -pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfoAccess>( -) -> frame_support::weights::Weight { - let on_chain_storage_version =

::on_chain_storage_version(); - log::info!( - target: "runtime::uniques", - "Running migration storage v1 for uniques with storage version {:?}", - on_chain_storage_version, - ); - - if on_chain_storage_version < 1 { - let mut count = 0; - for (collection, detail) in Collection::::iter() { - CollectionAccount::::insert(&detail.owner, &collection, ()); - count += 1; - } - StorageVersion::new(1).put::

(); - log::info!( - target: "runtime::uniques", - "Running migration storage v1 for uniques with storage version {:?} was complete", - on_chain_storage_version, - ); - // calculate and return migration weights - T::DbWeight::get().reads_writes(count as Weight + 1, count as Weight + 1) - } else { - log::warn!( - target: "runtime::uniques", - "Attempted to apply migration to v1 but failed because storage version is {:?}", - on_chain_storage_version, - ); - T::DbWeight::get().reads(1) - } -} diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index ff7b791de4950..f3040faac5f40 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -15,10 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test environment for Uniques pallet. +//! Test environment for Nfts pallet. use super::*; -use crate as pallet_uniques; +use crate as pallet_nfts; use frame_support::{ construct_runtime, @@ -41,7 +41,7 @@ construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, + Nfts: pallet_nfts::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 8b1d00d7ba0c7..2b20d124bd9ae 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for Uniques pallet. +//! Tests for Nfts pallet. use crate::{mock::*, Event, *}; use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency}; @@ -74,7 +74,7 @@ fn events() -> Vec> { let result = System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let mock::Event::Uniques(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let mock::Event::Nfts(inner) = e { Some(inner) } else { None }) .collect::>(); System::reset_events(); @@ -92,14 +92,14 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Uniques::force_create(Origin::root(), 1, 2, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, 2, true)); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Uniques::mint(Origin::signed(2), 1, 69, 1)); + assert_ok!(Nfts::mint(Origin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -108,32 +108,32 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 10)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 10)); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 20)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 20)); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); assert_eq!(Balances::reserved_balance(&1), 13); assert!(ItemMetadataOf::::contains_key(0, 69)); let w = Collection::::get(0).unwrap().destroy_witness(); assert_eq!(w.items, 2); assert_eq!(w.item_metadatas, 2); - assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + assert_ok!(Nfts::destroy(Origin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Collection::::contains_key(0)); @@ -151,20 +151,20 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_noop!(Uniques::destroy(Origin::signed(1), 0, w), Error::::BadWitness); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_noop!(Nfts::destroy(Origin::signed(1), 0, w), Error::::BadWitness); }); } #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_eq!(Uniques::owner(0, 42).unwrap(), 1); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); }); @@ -173,54 +173,54 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); - assert_noop!(Uniques::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); + assert_noop!(Nfts::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); - assert_ok!(Uniques::approve_transfer(Origin::signed(3), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 4)); + assert_ok!(Nfts::approve_transfer(Origin::signed(3), 0, 42, 2)); + assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 4)); }); } #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::freeze(Origin::signed(1), 0, 42)); - assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::freeze(Origin::signed(1), 0, 42)); + assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Uniques::thaw(Origin::signed(1), 0, 42)); - assert_ok!(Uniques::freeze_collection(Origin::signed(1), 0)); - assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::thaw(Origin::signed(1), 0, 42)); + assert_ok!(Nfts::freeze_collection(Origin::signed(1), 0)); + assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Uniques::thaw_collection(Origin::signed(1), 0)); - assert_ok!(Uniques::transfer(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::thaw_collection(Origin::signed(1), 0)); + assert_ok!(Nfts::transfer(Origin::signed(1), 0, 42, 2)); }); } #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); - assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); + assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); assert_noop!( - Uniques::transfer_ownership(Origin::signed(2), 0, 2), + Nfts::transfer_ownership(Origin::signed(2), 0, 2), Error::::NoPermission ); - assert_noop!(Uniques::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); - assert_noop!(Uniques::freeze(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Uniques::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Uniques::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); - assert_noop!(Uniques::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); + assert_noop!(Nfts::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); + assert_noop!(Nfts::freeze(Origin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Nfts::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Nfts::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); + assert_noop!(Nfts::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_noop!(Uniques::destroy(Origin::signed(2), 0, w), Error::::NoPermission); + assert_noop!(Nfts::destroy(Origin::signed(2), 0, w), Error::::NoPermission); }); } @@ -230,14 +230,11 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); assert_eq!(collections(), vec![(1, 0)]); - assert_noop!( - Uniques::transfer_ownership(Origin::signed(1), 0, 2), - Error::::Unaccepted - ); - assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); - assert_ok!(Uniques::transfer_ownership(Origin::signed(1), 0, 2)); + assert_noop!(Nfts::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted); + assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); + assert_ok!(Nfts::transfer_ownership(Origin::signed(1), 0, 2)); assert_eq!(collections(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); @@ -245,18 +242,18 @@ fn transfer_owner_should_work() { assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 2); - assert_ok!(Uniques::set_accept_ownership(Origin::signed(1), Some(0))); + assert_ok!(Nfts::set_accept_ownership(Origin::signed(1), Some(0))); assert_noop!( - Uniques::transfer_ownership(Origin::signed(1), 0, 1), + Nfts::transfer_ownership(Origin::signed(1), 0, 1), Error::::NoPermission ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); - assert_ok!(Uniques::set_accept_ownership(Origin::signed(3), Some(0))); - assert_ok!(Uniques::transfer_ownership(Origin::signed(2), 0, 3)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_accept_ownership(Origin::signed(3), Some(0))); + assert_ok!(Nfts::transfer_ownership(Origin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); assert_eq!(Balances::total_balance(&2), 57); assert_eq!(Balances::total_balance(&3), 145); @@ -265,24 +262,21 @@ fn transfer_owner_should_work() { // 2's acceptence from before is reset when it became owner, so it cannot be transfered // without a fresh acceptance. - assert_noop!( - Uniques::transfer_ownership(Origin::signed(3), 0, 2), - Error::::Unaccepted - ); + assert_noop!(Nfts::transfer_ownership(Origin::signed(3), 0, 2), Error::::Unaccepted); }); } #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 2)); - assert_ok!(Uniques::freeze(Origin::signed(4), 0, 42)); - assert_ok!(Uniques::thaw(Origin::signed(3), 0, 42)); - assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 3)); - assert_ok!(Uniques::burn(Origin::signed(3), 0, 42, None)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::freeze(Origin::signed(4), 0, 42)); + assert_ok!(Nfts::thaw(Origin::signed(3), 0, 42)); + assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 3)); + assert_ok!(Nfts::burn(Origin::signed(3), 0, 42, None)); }); } @@ -291,59 +285,56 @@ fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown item assert_noop!( - Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); // Cannot add metadata to unowned item assert_noop!( - Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 9); assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::root(), 0, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), + Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); assert_noop!( - Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), + Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!( - Uniques::clear_collection_metadata(Origin::signed(1), 0), - Error::::Frozen - ); + assert_noop!(Nfts::clear_collection_metadata(Origin::signed(1), 0), Error::::Frozen); // Clear Metadata - assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::root(), 0, bvec![0u8; 15], false)); assert_noop!( - Uniques::clear_collection_metadata(Origin::signed(2), 0), + Nfts::clear_collection_metadata(Origin::signed(2), 0), Error::::NoPermission ); assert_noop!( - Uniques::clear_collection_metadata(Origin::signed(1), 1), + Nfts::clear_collection_metadata(Origin::signed(1), 1), Error::::UnknownCollection ); - assert_ok!(Uniques::clear_collection_metadata(Origin::signed(1), 0)); + assert_ok!(Nfts::clear_collection_metadata(Origin::signed(1), 0)); assert!(!CollectionMetadataOf::::contains_key(0)); }); } @@ -354,53 +345,50 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( - Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), + Nfts::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 8); assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. - assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false)); assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 25], false)); assert_eq!(Balances::free_balance(&1), 3); // Cannot over-reserve assert_noop!( - Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 40], false), + Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], true)); assert_noop!( - Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false), + Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Uniques::clear_metadata(Origin::signed(1), 0, 42), Error::::Frozen); + assert_noop!(Nfts::clear_metadata(Origin::signed(1), 0, 42), Error::::Frozen); // Clear Metadata - assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 15], false)); - assert_noop!( - Uniques::clear_metadata(Origin::signed(2), 0, 42), - Error::::NoPermission - ); + assert_ok!(Nfts::set_metadata(Origin::root(), 0, 42, bvec![0u8; 15], false)); + assert_noop!(Nfts::clear_metadata(Origin::signed(2), 0, 42), Error::::NoPermission); assert_noop!( - Uniques::clear_metadata(Origin::signed(1), 1, 42), + Nfts::clear_metadata(Origin::signed(1), 1, 42), Error::::UnknownCollection ); - assert_ok!(Uniques::clear_metadata(Origin::signed(1), 0, 42)); + assert_ok!(Nfts::clear_metadata(Origin::signed(1), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); }); } @@ -410,11 +398,11 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![1], bvec![0])); assert_eq!( attributes(0), vec![ @@ -425,7 +413,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0; 10])); assert_eq!( attributes(0), vec![ @@ -436,7 +424,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 18); - assert_ok!(Uniques::clear_attribute(Origin::signed(1), 0, Some(0), bvec![1])); + assert_ok!(Nfts::clear_attribute(Origin::signed(1), 0, Some(0), bvec![1])); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] @@ -444,7 +432,7 @@ fn set_attribute_should_work() { assert_eq!(Balances::reserved_balance(1), 15); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + assert_ok!(Nfts::destroy(Origin::signed(1), 0, w)); assert_eq!(attributes(0), vec![]); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -455,11 +443,11 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![0])); assert_eq!( attributes(0), vec![ @@ -470,15 +458,15 @@ fn set_attribute_should_respect_freeze() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![], true)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); + assert_noop!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 0, bvec![], true)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1]), e); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_noop!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1]), e); + assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![1])); }); } @@ -487,32 +475,32 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 2)); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Uniques::force_item_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 142, 1)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 169, 2)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 169, bvec![0; 20], false)); + assert_ok!(Nfts::force_item_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 142, 1)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 169, 2)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 169, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); - assert_ok!(Uniques::redeposit(Origin::signed(1), 0, bvec![0, 42, 50, 69, 100])); + assert_ok!(Nfts::redeposit(Origin::signed(1), 0, bvec![0, 42, 50, 69, 100])); assert_eq!(Balances::reserved_balance(1), 63); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 42); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -521,23 +509,23 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_noop!( - Uniques::burn(Origin::signed(5), 0, 42, Some(5)), + Nfts::burn(Origin::signed(5), 0, 42, Some(5)), Error::::UnknownCollection ); - assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 5)); - assert_ok!(Uniques::mint(Origin::signed(2), 0, 69, 5)); + assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 5)); + assert_ok!(Nfts::mint(Origin::signed(2), 0, 69, 5)); assert_eq!(Balances::reserved_balance(1), 2); - assert_noop!(Uniques::burn(Origin::signed(0), 0, 42, None), Error::::NoPermission); - assert_noop!(Uniques::burn(Origin::signed(5), 0, 42, Some(6)), Error::::WrongOwner); + assert_noop!(Nfts::burn(Origin::signed(0), 0, 42, None), Error::::NoPermission); + assert_noop!(Nfts::burn(Origin::signed(5), 0, 42, Some(6)), Error::::WrongOwner); - assert_ok!(Uniques::burn(Origin::signed(5), 0, 42, Some(5))); - assert_ok!(Uniques::burn(Origin::signed(3), 0, 69, Some(5))); + assert_ok!(Nfts::burn(Origin::signed(5), 0, 42, Some(5))); + assert_ok!(Nfts::burn(Origin::signed(3), 0, 69, Some(5))); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -545,45 +533,45 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); - assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 4)); - assert_noop!(Uniques::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); + assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); assert!(Item::::get(0, 42).unwrap().approved.is_none()); - assert_ok!(Uniques::approve_transfer(Origin::signed(4), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 2)); + assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 2)); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 1, 42, None), + Nfts::cancel_approval(Origin::signed(2), 1, 42, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 43, None), + Nfts::cancel_approval(Origin::signed(2), 0, 43, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(3), 0, 42, None), + Nfts::cancel_approval(Origin::signed(3), 0, 42, None), Error::::NoPermission ); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(4)), + Nfts::cancel_approval(Origin::signed(2), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(3))); + assert_ok!(Nfts::cancel_approval(Origin::signed(2), 0, 42, Some(3))); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 42, None), + Nfts::cancel_approval(Origin::signed(2), 0, 42, None), Error::::NoDelegate ); }); @@ -592,26 +580,26 @@ fn cancel_approval_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 1, 42, None), + Nfts::cancel_approval(Origin::signed(1), 1, 42, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 43, None), + Nfts::cancel_approval(Origin::signed(1), 0, 43, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(4)), + Nfts::cancel_approval(Origin::signed(1), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(3))); + assert_ok!(Nfts::cancel_approval(Origin::signed(1), 0, 42, Some(3))); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 42, None), + Nfts::cancel_approval(Origin::signed(1), 0, 42, None), Error::::NoDelegate ); }); @@ -620,28 +608,25 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( - Uniques::cancel_approval(Origin::root(), 1, 42, None), + Nfts::cancel_approval(Origin::root(), 1, 42, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::root(), 0, 43, None), + Nfts::cancel_approval(Origin::root(), 0, 43, None), Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::root(), 0, 42, Some(4)), + Nfts::cancel_approval(Origin::root(), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::root(), 0, 42, Some(3))); - assert_noop!( - Uniques::cancel_approval(Origin::root(), 0, 42, None), - Error::::NoDelegate - ); + assert_ok!(Nfts::cancel_approval(Origin::root(), 0, 42, Some(3))); + assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, None), Error::::NoDelegate); }); } @@ -653,10 +638,10 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); assert!(!CollectionMaxSupply::::contains_key(collection_id)); - assert_ok!(Uniques::set_collection_max_supply( + assert_ok!(Nfts::set_collection_max_supply( Origin::signed(user_id), collection_id, max_supply @@ -669,24 +654,20 @@ fn max_supply_should_work() { })); assert_noop!( - Uniques::set_collection_max_supply( - Origin::signed(user_id), - collection_id, - max_supply + 1 - ), + Nfts::set_collection_max_supply(Origin::signed(user_id), collection_id, max_supply + 1), Error::::MaxSupplyAlreadySet ); // validate we can't mint more to max supply - assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 0, user_id)); - assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 1, user_id)); + assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, 1, user_id)); assert_noop!( - Uniques::mint(Origin::signed(user_id), collection_id, 2, user_id), + Nfts::mint(Origin::signed(user_id), collection_id, 2, user_id), Error::::MaxSupplyReached ); // validate we remove the CollectionMaxSupply record when we destroy the collection - assert_ok!(Uniques::destroy( + assert_ok!(Nfts::destroy( Origin::signed(user_id), collection_id, Collection::::get(collection_id).unwrap().destroy_witness() @@ -703,20 +684,14 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); - assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_2, user_id)); - assert_ok!(Uniques::set_price( - Origin::signed(user_id), - collection_id, - item_1, - Some(1), - None, - )); + assert_ok!(Nfts::set_price(Origin::signed(user_id), collection_id, item_1, Some(1), None,)); - assert_ok!(Uniques::set_price( + assert_ok!(Nfts::set_price( Origin::signed(user_id), collection_id, item_2, @@ -740,7 +715,7 @@ fn set_price_should_work() { })); // validate we can unset the price - assert_ok!(Uniques::set_price(Origin::signed(user_id), collection_id, item_2, None, None)); + assert_ok!(Nfts::set_price(Origin::signed(user_id), collection_id, item_2, None, None)); assert!(events().contains(&Event::::ItemPriceRemoved { collection: collection_id, item: item_2 @@ -767,13 +742,13 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_1, true)); + assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_1, true)); - assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_2, user_1)); - assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_3, user_1)); + assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_3, user_1)); - assert_ok!(Uniques::set_price( + assert_ok!(Nfts::set_price( Origin::signed(user_1), collection_id, item_1, @@ -781,7 +756,7 @@ fn buy_item_should_work() { None, )); - assert_ok!(Uniques::set_price( + assert_ok!(Nfts::set_price( Origin::signed(user_1), collection_id, item_2, @@ -791,12 +766,12 @@ fn buy_item_should_work() { // can't buy for less assert_noop!( - Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, 1), + Nfts::buy_item(Origin::signed(user_2), collection_id, item_1, 1), Error::::BidTooLow ); // pass the higher price to validate it will still deduct correctly - assert_ok!(Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, price_1 + 1,)); + assert_ok!(Nfts::buy_item(Origin::signed(user_2), collection_id, item_1, price_1 + 1,)); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -806,18 +781,18 @@ fn buy_item_should_work() { // can't buy from yourself assert_noop!( - Uniques::buy_item(Origin::signed(user_1), collection_id, item_2, price_2), + Nfts::buy_item(Origin::signed(user_1), collection_id, item_2, price_2), Error::::NoPermission ); // can't buy when the item is listed for a specific buyer assert_noop!( - Uniques::buy_item(Origin::signed(user_2), collection_id, item_2, price_2), + Nfts::buy_item(Origin::signed(user_2), collection_id, item_2, price_2), Error::::NoPermission ); // can buy when I'm a whitelisted buyer - assert_ok!(Uniques::buy_item(Origin::signed(user_3), collection_id, item_2, price_2,)); + assert_ok!(Nfts::buy_item(Origin::signed(user_3), collection_id, item_2, price_2,)); assert!(events().contains(&Event::::ItemBought { collection: collection_id, @@ -832,13 +807,13 @@ fn buy_item_should_work() { // can't buy when item is not for sale assert_noop!( - Uniques::buy_item(Origin::signed(user_2), collection_id, item_3, price_2), + Nfts::buy_item(Origin::signed(user_2), collection_id, item_3, price_2), Error::::NotForSale ); // ensure we can't buy an item when the collection or an item is frozen { - assert_ok!(Uniques::set_price( + assert_ok!(Nfts::set_price( Origin::signed(user_1), collection_id, item_3, @@ -847,21 +822,21 @@ fn buy_item_should_work() { )); // freeze collection - assert_ok!(Uniques::freeze_collection(Origin::signed(user_1), collection_id)); + assert_ok!(Nfts::freeze_collection(Origin::signed(user_1), collection_id)); - let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + let buy_item_call = mock::Call::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, }); assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); - assert_ok!(Uniques::thaw_collection(Origin::signed(user_1), collection_id)); + assert_ok!(Nfts::thaw_collection(Origin::signed(user_1), collection_id)); // freeze item - assert_ok!(Uniques::freeze(Origin::signed(user_1), collection_id, item_3)); + assert_ok!(Nfts::freeze(Origin::signed(user_1), collection_id, item_3)); - let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + let buy_item_call = mock::Call::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 98e056163d28d..1081ec8110288 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Various basic types for use in the Uniques pallet. +//! Various basic types for use in the Nfts pallet. use super::*; use frame_support::{ diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 7c8cb170b1b1d..7537cd3b03e6b 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_uniques +//! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: 2022-07-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` @@ -32,9 +32,9 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_uniques +// --pallet=pallet_nfts // --chain=dev -// --output=./frame/uniques/src/weights.rs +// --output=./frame/nfts/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -44,7 +44,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_uniques. +/// Weight functions needed for pallet_nfts. pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; @@ -74,7 +74,7 @@ pub trait WeightInfo { fn buy_item() -> Weight; } -/// Weights for pallet_uniques using the Substrate node and recommended hardware. +/// Weights for pallet_nfts using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) From 69edfb29d7d34aea045e7a2379560b95ff6f4157 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 9 Sep 2022 18:03:07 +0300 Subject: [PATCH 03/60] Update weights --- frame/nfts/src/weights.rs | 352 +++++++++++++++++++------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 7537cd3b03e6b..de518b8286665 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -80,16 +80,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (33_075_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(33_075_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (19_528_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(19_528_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) @@ -103,192 +103,192 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - (0 as Weight) + Weight::from_ref_time(0 as u64) // Standard Error: 25_000 - .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(Weight::from_ref_time(13_639_000 as u64).saturating_mul(n as u64)) // Standard Error: 25_000 - .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + .saturating_add(Weight::from_ref_time(2_393_000 as u64).saturating_mul(m as u64)) // Standard Error: 25_000 - .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + .saturating_add(Weight::from_ref_time(2_217_000 as u64).saturating_mul(a as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (42_146_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + Weight::from_ref_time(42_146_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (42_960_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(42_960_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (33_025_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(33_025_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - (0 as Weight) + Weight::from_ref_time(0 as u64) // Standard Error: 24_000 - .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(Weight::from_ref_time(15_540_000 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (25_194_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_194_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (25_397_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_397_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (19_278_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_278_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (19_304_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_304_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (28_615_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(28_615_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (19_943_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_943_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (22_583_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(22_583_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (47_520_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(47_520_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (45_316_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(45_316_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (38_391_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(38_391_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (38_023_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(38_023_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (37_398_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(37_398_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (35_621_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(35_621_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (25_856_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_856_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (26_098_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(26_098_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (24_076_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(24_076_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (22_035_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(22_035_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:0) // Storage: Uniques ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - (22_534_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(22_534_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques ItemPriceOf (r:1 w:1) // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - (45_272_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(45_272_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } } @@ -297,16 +297,16 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (33_075_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(33_075_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (19_528_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(19_528_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) @@ -320,191 +320,191 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - (0 as Weight) + Weight::from_ref_time(0 as u64) // Standard Error: 25_000 - .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(Weight::from_ref_time(13_639_000 as u64).saturating_mul(n as u64)) // Standard Error: 25_000 - .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + .saturating_add(Weight::from_ref_time(2_393_000 as u64).saturating_mul(m as u64)) // Standard Error: 25_000 - .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + .saturating_add(Weight::from_ref_time(2_217_000 as u64).saturating_mul(a as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(m as u64))) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (42_146_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + Weight::from_ref_time(42_146_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (42_960_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(42_960_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (33_025_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(33_025_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - (0 as Weight) + Weight::from_ref_time(0 as u64) // Standard Error: 24_000 - .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(Weight::from_ref_time(15_540_000 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (25_194_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_194_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (25_397_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_397_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (19_278_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_278_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (19_304_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_304_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (28_615_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(28_615_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (19_943_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(19_943_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (22_583_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(22_583_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (47_520_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(47_520_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (45_316_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(45_316_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (38_391_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(38_391_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (38_023_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(38_023_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (37_398_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::from_ref_time(37_398_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (35_621_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(35_621_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (25_856_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(25_856_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (26_098_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(26_098_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (24_076_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(24_076_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (22_035_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(22_035_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:0) // Storage: Uniques ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - (22_534_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::from_ref_time(22_534_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques ItemPriceOf (r:1 w:1) // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - (45_272_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + Weight::from_ref_time(45_272_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } } From 028ea383058fae970cc6b6e2fd51d597f236ac25 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:34:28 +0200 Subject: [PATCH 04/60] Nfts: Multiple approvals (#12178) * multiple approvals * clear * tests & clean up * fix in logic & fmt * fix benchmarks * deadline * test deadline * current_block + deadline * update ApprovedTransfer event * benchmark * docs * Update frame/nfts/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * fmt fix * Update frame/nfts/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * update tests * anyone can cancel * Update frame/nfts/src/tests.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * fmt * fix logic * unnecessary line * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update frame/nfts/src/lib.rs * Update lib.rs * fmt * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * fmt * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * suggestion * new line * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Co-authored-by: command-bot <> Co-authored-by: Squirrel --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/src/benchmarking.rs | 23 +- frame/nfts/src/functions.rs | 13 +- frame/nfts/src/lib.rs | 126 ++++++++-- frame/nfts/src/mock.rs | 1 + frame/nfts/src/tests.rs | 165 ++++++++++--- frame/nfts/src/types.rs | 6 +- frame/nfts/src/weights.rs | 409 +++++++++++++++++---------------- 8 files changed, 491 insertions(+), 254 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7b83ef98d7d50..c34c46b04c0dc 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1453,6 +1453,7 @@ parameter_types! { pub const ItemDeposit: Balance = 1 * DOLLARS; pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; + pub const ApprovalsLimit: u32 = 20; } impl pallet_uniques::Config for Runtime { @@ -1490,6 +1491,7 @@ impl pallet_nfts::Config for Runtime { type StringLimit = StringLimit; type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; + type ApprovalsLimit = ApprovalsLimit; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index e30b5ebbd3fe5..ca38851222e8d 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -368,9 +368,10 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + let deadline = T::BlockNumber::max_value(); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { @@ -379,12 +380,26 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) + let deadline = T::BlockNumber::max_value(); + Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) verify { assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); } + clear_all_transfer_approvals { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(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 = T::BlockNumber::max_value(); + Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); + } + set_accept_ownership { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 107214558307f..27ab752dbabf6 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -48,6 +48,12 @@ impl, I: 'static> Pallet { Account::::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 his second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + Item::::insert(&collection, &item, &details); ItemPriceOf::::remove(&collection, &item); @@ -168,7 +174,12 @@ impl, I: 'static> Pallet { let owner = owner.clone(); Account::::insert((&owner, &collection, &item), ()); - let details = ItemDetails { owner, approved: None, is_frozen: false, deposit }; + let details = ItemDetails { + owner, + approvals: ApprovalsOf::::default(), + is_frozen: false, + deposit, + }; Item::::insert(&collection, &item, details); Ok(()) }, diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index cb96e8138ba5e..14691c21a0ef2 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -46,7 +46,7 @@ use frame_support::{ traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, }, - transactional, + transactional, BoundedBTreeMap, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -59,7 +59,7 @@ pub use pallet::*; pub use types::*; pub use weights::WeightInfo; -type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { @@ -149,6 +149,10 @@ pub mod pallet { #[pallet::constant] type ValueLimit: Get; + /// The maximum approvals an item could have. + #[pallet::constant] + type ApprovalsLimit: Get; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -157,6 +161,12 @@ pub mod pallet { type WeightInfo: WeightInfo; } + pub type ApprovalsOf = BoundedBTreeMap< + ::AccountId, + Option<::BlockNumber>, + >::ApprovalsLimit, + >; + #[pallet::storage] #[pallet::storage_prefix = "Class"] /// Details of a collection. @@ -209,7 +219,7 @@ pub mod pallet { T::CollectionId, Blake2_128Concat, T::ItemId, - ItemDetails>, + ItemDetails, ApprovalsOf>, OptionQuery, >; @@ -311,6 +321,7 @@ pub mod pallet { item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, + deadline: Option<::BlockNumber>, }, /// An approval for a `delegate` account to transfer the `item` of an item /// `collection` was cancelled by its `owner`. @@ -320,6 +331,8 @@ pub mod pallet { owner: T::AccountId, delegate: T::AccountId, }, + /// All approvals of an item got cancelled. + AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// A `collection` has had its attributes changed by the `Force` origin. ItemStatusChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. @@ -385,6 +398,8 @@ pub mod pallet { UnknownCollection, /// The item ID has already been used for an item. AlreadyExists, + /// The approval had a deadline that expired, so the approval isn't valid anymore. + ApprovalExpired, /// The owner turned out to be different to what was expected. WrongOwner, /// Invalid witness data given. @@ -393,10 +408,10 @@ pub mod pallet { InUse, /// The item or collection is frozen. Frozen, + /// The provided account is not a delegate. + NotDelegate, /// The delegate turned out to be different to what was expected. WrongDelegate, - /// There is no delegate approved. - NoDelegate, /// No approval exists that would allow the transfer. Unapproved, /// The named owner has not signed ownership of the collection is acceptable. @@ -415,6 +430,8 @@ pub mod pallet { NotForSale, /// The provided bid is too low. BidTooLow, + /// The item has reached its approval limit. + ReachedApprovalLimit, } impl, I: 'static> Pallet { @@ -630,8 +647,12 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |collection_details, details| { if details.owner != origin && collection_details.admin != origin { - let approved = details.approved.take().map_or(false, |i| i == origin); - ensure!(approved, Error::::NoPermission); + let deadline = + details.approvals.get(&origin).ok_or(Error::::NoPermission)?; + if let Some(d) = deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *d, Error::::ApprovalExpired); + } } Ok(()) }) @@ -912,6 +933,8 @@ pub mod pallet { /// - `collection`: The collection of the item to be approved for delegated transfer. /// - `item`: The item of the item to be approved for delegated transfer. /// - `delegate`: The account to delegate permission to transfer the item. + /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the + /// number of blocks after which the approval will expire /// /// Emits `ApprovedTransfer` on success. /// @@ -922,6 +945,7 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, + maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -939,21 +963,27 @@ pub mod pallet { ensure!(permitted, Error::::NoPermission); } - details.approved = Some(delegate); + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + details + .approvals + .try_insert(delegate.clone(), deadline) + .map_err(|_| Error::::ReachedApprovalLimit)?; Item::::insert(&collection, &item, &details); - let delegate = details.approved.expect("set as Some above; qed"); Self::deposit_event(Event::ApprovedTransfer { collection, item, owner: details.owner, delegate, + deadline, }); Ok(()) } - /// Cancel the prior approval for the transfer of an item by a delegate. + /// Cancel one of the transfer approvals for a specific item. /// /// Origin must be either: /// - the `Force` origin; @@ -962,9 +992,8 @@ pub mod pallet { /// /// Arguments: /// - `collection`: The collection of the item of whose approval will be cancelled. - /// - `item`: The item of the item of whose approval will be cancelled. - /// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to - /// which permission of transfer is delegated. + /// - `item`: The item of the collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to loose their approval. /// /// Emits `ApprovalCancelled` on success. /// @@ -974,7 +1003,67 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, item: T::ItemId, - maybe_check_delegate: Option>, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let maybe_check: Option = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + + let delegate = T::Lookup::lookup(delegate)?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + let maybe_deadline = + details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; + + let is_past_deadline = if let Some(deadline) = maybe_deadline { + let now = frame_system::Pallet::::block_number(); + now > *deadline + } else { + false + }; + + if !is_past_deadline { + if let Some(check) = maybe_check { + let permitted = check == collection_details.admin || check == details.owner; + ensure!(permitted, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + /// Cancel all the approvals of a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; + /// + /// Arguments: + /// - `collection`: The collection of the item of whose approvals will be cleared. + /// - `item`: The item of the collection of whose approvals will be cleared. + /// + /// Emits `AllApprovalsCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::clear_all_transfer_approvals())] + pub fn clear_all_transfer_approvals( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -988,18 +1077,13 @@ pub mod pallet { let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } - let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?; - let old = details.approved.take().ok_or(Error::::NoDelegate)?; - if let Some(check_delegate) = maybe_check_delegate { - ensure!(check_delegate == old, Error::::WrongDelegate); - } + details.approvals.clear(); Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::ApprovalCancelled { + Self::deposit_event(Event::AllApprovalsCancelled { collection, item, owner: details.owner, - delegate: old, }); Ok(()) diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index f3040faac5f40..ad7a94b3eed50 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -100,6 +100,7 @@ impl Config for Test { type StringLimit = ConstU32<50>; type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; + type ApprovalsLimit = ConstU32<10>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 2b20d124bd9ae..9fb29d0e95c26 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -70,6 +70,12 @@ fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { s } +fn approvals(collection_id: u32, item_id: u32) -> Vec<(u64, Option)> { + let item = Item::::get(collection_id, item_id).unwrap(); + let s: Vec<_> = item.approvals.into_iter().collect(); + s +} + fn events() -> Vec> { let result = System::events() .into_iter() @@ -180,7 +186,7 @@ fn transfer_should_work() { assert_eq!(items(), vec![(3, 0, 42)]); assert_noop!(Nfts::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); - assert_ok!(Nfts::approve_transfer(Origin::signed(3), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(Origin::signed(3), 0, 42, 2, None)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 4)); }); } @@ -535,12 +541,12 @@ fn approval_lifecycle_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); - assert!(Item::::get(0, 42).unwrap().approved.is_none()); + assert!(Item::::get(0, 42).unwrap().approvals.is_empty()); - assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 2, None)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 2)); }); } @@ -551,29 +557,107 @@ fn cancel_approval_works() { assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 1, 42, None), + Nfts::cancel_approval(Origin::signed(2), 1, 42, 3), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 43, None), + Nfts::cancel_approval(Origin::signed(2), 0, 43, 3), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(3), 0, 42, None), + Nfts::cancel_approval(Origin::signed(3), 0, 42, 3), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 42, Some(4)), - Error::::WrongDelegate + Nfts::cancel_approval(Origin::signed(2), 0, 42, 4), + Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(Origin::signed(2), 0, 42, Some(3))); + assert_ok!(Nfts::cancel_approval(Origin::signed(2), 0, 42, 3)); + assert_noop!( + Nfts::cancel_approval(Origin::signed(2), 0, 42, 3), + Error::::NotDelegate + ); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); + // approval expires after 2 blocks. + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, Some(2))); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 42, None), - Error::::NoDelegate + Nfts::cancel_approval(Origin::signed(5), 0, 42, 3), + Error::::NoPermission ); + + System::set_block_number(current_block + 3); + // 5 can cancel the approval since the deadline has passed. + assert_ok!(Nfts::cancel_approval(Origin::signed(5), 0, 42, 3)); + assert_eq!(approvals(0, 69), vec![]); + }); +} + +#[test] +fn approving_multiple_accounts_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 4, None)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 5, Some(2))); + assert_eq!(approvals(0, 42), vec![(3, None), (4, None), (5, Some(current_block + 2))]); + + assert_ok!(Nfts::transfer(Origin::signed(4), 0, 42, 6)); + assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 7), Error::::NoPermission); + assert_noop!(Nfts::transfer(Origin::signed(5), 0, 42, 8), Error::::NoPermission); + }); +} + +#[test] +fn approvals_limit_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + + for i in 3..13 { + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, i, None)); + } + // the limit is 10 + assert_noop!( + Nfts::approve_transfer(Origin::signed(2), 0, 42, 14, None), + Error::::ReachedApprovalLimit + ); + }); +} + +#[test] +fn approval_deadline_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert!(System::block_number().is_zero()); + + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + + // the approval expires after the 2nd block. + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, Some(2))); + + System::set_block_number(3); + assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 4), Error::::ApprovalExpired); + System::set_block_number(1); + assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); + + assert_eq!(System::block_number(), 1); + // make a new approval with a deadline after 4 blocks, so it will expire after the 5th + // block. + assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 6, Some(4))); + // this should still work. + System::set_block_number(5); + assert_ok!(Nfts::transfer(Origin::signed(6), 0, 42, 5)); }); } @@ -583,24 +667,24 @@ fn cancel_approval_works_with_admin() { assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 1, 42, None), + Nfts::cancel_approval(Origin::signed(1), 1, 42, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 43, None), + Nfts::cancel_approval(Origin::signed(1), 0, 43, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 42, Some(4)), - Error::::WrongDelegate + Nfts::cancel_approval(Origin::signed(1), 0, 42, 4), + Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(Origin::signed(1), 0, 42, Some(3))); + assert_ok!(Nfts::cancel_approval(Origin::signed(1), 0, 42, 3)); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 42, None), - Error::::NoDelegate + Nfts::cancel_approval(Origin::signed(1), 0, 42, 1), + Error::::NotDelegate ); }); } @@ -611,22 +695,47 @@ fn cancel_approval_works_with_force() { assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::root(), 1, 42, None), + Nfts::cancel_approval(Origin::root(), 1, 42, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::root(), 0, 43, None), + Nfts::cancel_approval(Origin::root(), 0, 43, 1), Error::::UnknownCollection ); + assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, 4), Error::::NotDelegate); + + assert_ok!(Nfts::cancel_approval(Origin::root(), 0, 42, 3)); + assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, 1), Error::::NotDelegate); + }); +} + +#[test] +fn clear_all_transfer_approvals_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 4, None)); + assert_noop!( - Nfts::cancel_approval(Origin::root(), 0, 42, Some(4)), - Error::::WrongDelegate + Nfts::clear_all_transfer_approvals(Origin::signed(3), 0, 42), + Error::::NoPermission ); - assert_ok!(Nfts::cancel_approval(Origin::root(), 0, 42, Some(3))); - assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, None), Error::::NoDelegate); + assert_ok!(Nfts::clear_all_transfer_approvals(Origin::signed(2), 0, 42)); + + assert!(events().contains(&Event::::AllApprovalsCancelled { + collection: 0, + item: 42, + owner: 2, + })); + assert_eq!(approvals(0, 42), vec![]); + + assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 5), Error::::NoPermission); + assert_noop!(Nfts::transfer(Origin::signed(4), 0, 42, 5), Error::::NoPermission); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 1081ec8110288..db1c351c4a9c5 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -29,7 +29,7 @@ pub(super) type DepositBalanceOf = pub(super) type CollectionDetailsFor = CollectionDetails<::AccountId, DepositBalanceOf>; pub(super) type ItemDetailsFor = - ItemDetails<::AccountId, DepositBalanceOf>; + ItemDetails<::AccountId, DepositBalanceOf, ApprovalsOf>; pub(super) type ItemPrice = <>::Currency as Currency<::AccountId>>::Balance; @@ -84,11 +84,11 @@ impl CollectionDetails { /// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] -pub struct ItemDetails { +pub struct ItemDetails { /// The owner of this item. pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. - pub(super) approved: Option, + pub(super) approvals: Approvals, /// Whether the item can be transferred or not. pub(super) is_frozen: bool, /// The amount held in the pallet's default account for this item. Free-hold items will have diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index de518b8286665..2c90ab54fe9fb 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,12 +18,12 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `test-bench-bot`, CPU: `Intel(R) Xeon(R) CPU @ 3.10GHz` +//! DATE: 2022-09-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet // --steps=50 @@ -68,6 +68,7 @@ pub trait WeightInfo { fn clear_collection_metadata() -> Weight; fn approve_transfer() -> Weight; fn cancel_approval() -> Weight; + fn clear_all_transfer_approvals() -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; fn set_price() -> Weight; @@ -77,39 +78,39 @@ pub trait WeightInfo { /// Weights for pallet_nfts using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(33_075_000 as u64) + Weight::from_ref_time(33_018_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(19_528_000 as u64) + Weight::from_ref_time(20_957_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ClassAccount (r:0 w:1) - // Storage: Uniques Attribute (r:0 w:1000) - // Storage: Uniques ClassMetadataOf (r:0 w:1) - // Storage: Uniques InstanceMetadataOf (r:0 w:1000) - // Storage: Uniques CollectionMaxSupply (r:0 w:1) - // Storage: Uniques Account (r:0 w:20) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:1000) + // Storage: Nfts ClassMetadataOf (r:0 w:1) + // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { Weight::from_ref_time(0 as u64) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(13_639_000 as u64).saturating_mul(n as u64)) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(2_393_000 as u64).saturating_mul(m as u64)) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(2_217_000 as u64).saturating_mul(a as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(10_091_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(1_748_000 as u64).saturating_mul(m as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(1_621_000 as u64).saturating_mul(a as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(T::DbWeight::get().writes(4 as u64)) @@ -117,176 +118,183 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(m as u64))) .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques CollectionMaxSupply (r:1 w:0) - // Storage: Uniques Account (r:0 w:1) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(42_146_000 as u64) + Weight::from_ref_time(43_007_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:1) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(42_960_000 as u64) + Weight::from_ref_time(43_922_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:2) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(33_025_000 as u64) + Weight::from_ref_time(33_951_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:100 w:100) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { Weight::from_ref_time(0 as u64) - // Standard Error: 24_000 - .saturating_add(Weight::from_ref_time(15_540_000 as u64).saturating_mul(i as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(11_194_000 as u64).saturating_mul(i as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(T::DbWeight::get().writes(1 as u64)) .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(25_194_000 as u64) + Weight::from_ref_time(26_745_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(25_397_000 as u64) + Weight::from_ref_time(27_466_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(19_278_000 as u64) + Weight::from_ref_time(22_591_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(19_304_000 as u64) + Weight::from_ref_time(22_392_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:2) + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(28_615_000 as u64) + Weight::from_ref_time(31_202_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(19_943_000 as u64) + Weight::from_ref_time(23_063_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(22_583_000 as u64) + Weight::from_ref_time(25_598_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(47_520_000 as u64) + Weight::from_ref_time(48_684_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(45_316_000 as u64) + Weight::from_ref_time(47_267_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(38_391_000 as u64) + Weight::from_ref_time(40_174_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(38_023_000 as u64) + Weight::from_ref_time(41_611_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(37_398_000 as u64) + Weight::from_ref_time(40_073_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(35_621_000 as u64) + Weight::from_ref_time(38_191_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(25_856_000 as u64) + Weight::from_ref_time(29_461_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(26_098_000 as u64) + Weight::from_ref_time(29_690_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + fn clear_all_transfer_approvals() -> Weight { + Weight::from_ref_time(27_758_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(24_076_000 as u64) + Weight::from_ref_time(26_425_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques CollectionMaxSupply (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(22_035_000 as u64) + Weight::from_ref_time(24_533_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(22_534_000 as u64) + Weight::from_ref_time(24_745_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques ItemPriceOf (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Account (r:0 w:2) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemPriceOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(45_272_000 as u64) + Weight::from_ref_time(47_967_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } @@ -294,39 +302,39 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(33_075_000 as u64) + Weight::from_ref_time(33_018_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(19_528_000 as u64) + Weight::from_ref_time(20_957_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ClassAccount (r:0 w:1) - // Storage: Uniques Attribute (r:0 w:1000) - // Storage: Uniques ClassMetadataOf (r:0 w:1) - // Storage: Uniques InstanceMetadataOf (r:0 w:1000) - // Storage: Uniques CollectionMaxSupply (r:0 w:1) - // Storage: Uniques Account (r:0 w:20) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:1000) + // Storage: Nfts ClassMetadataOf (r:0 w:1) + // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { Weight::from_ref_time(0 as u64) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(13_639_000 as u64).saturating_mul(n as u64)) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(2_393_000 as u64).saturating_mul(m as u64)) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(2_217_000 as u64).saturating_mul(a as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(10_091_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(1_748_000 as u64).saturating_mul(m as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(1_621_000 as u64).saturating_mul(a as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(RocksDbWeight::get().writes(4 as u64)) @@ -334,176 +342,183 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(m as u64))) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques CollectionMaxSupply (r:1 w:0) - // Storage: Uniques Account (r:0 w:1) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(42_146_000 as u64) + Weight::from_ref_time(43_007_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:1) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(42_960_000 as u64) + Weight::from_ref_time(43_922_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:2) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(33_025_000 as u64) + Weight::from_ref_time(33_951_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:100 w:100) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { Weight::from_ref_time(0 as u64) - // Standard Error: 24_000 - .saturating_add(Weight::from_ref_time(15_540_000 as u64).saturating_mul(i as u64)) + // Standard Error: 12_000 + .saturating_add(Weight::from_ref_time(11_194_000 as u64).saturating_mul(i as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(RocksDbWeight::get().writes(1 as u64)) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(25_194_000 as u64) + Weight::from_ref_time(26_745_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(25_397_000 as u64) + Weight::from_ref_time(27_466_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(19_278_000 as u64) + Weight::from_ref_time(22_591_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(19_304_000 as u64) + Weight::from_ref_time(22_392_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:2) + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(28_615_000 as u64) + Weight::from_ref_time(31_202_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Uniques Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(19_943_000 as u64) + Weight::from_ref_time(23_063_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(22_583_000 as u64) + Weight::from_ref_time(25_598_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(47_520_000 as u64) + Weight::from_ref_time(48_684_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(45_316_000 as u64) + Weight::from_ref_time(47_267_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(38_391_000 as u64) + Weight::from_ref_time(40_174_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(38_023_000 as u64) + Weight::from_ref_time(41_611_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(37_398_000 as u64) + Weight::from_ref_time(40_073_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(35_621_000 as u64) + Weight::from_ref_time(38_191_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(25_856_000 as u64) + Weight::from_ref_time(29_461_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(26_098_000 as u64) + Weight::from_ref_time(29_690_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Asset (r:1 w:1) + fn clear_all_transfer_approvals() -> Weight { + Weight::from_ref_time(27_758_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(24_076_000 as u64) + Weight::from_ref_time(26_425_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques CollectionMaxSupply (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(22_035_000 as u64) + Weight::from_ref_time(24_533_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(22_534_000 as u64) + Weight::from_ref_time(24_745_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques ItemPriceOf (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Account (r:0 w:2) + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemPriceOf (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(45_272_000 as u64) + Weight::from_ref_time(47_967_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } From 4930d817d057f936d993860199223f9001043435 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 15 Sep 2022 13:23:17 +0800 Subject: [PATCH 05/60] Fixes --- frame/nfts/src/lib.rs | 2 +- frame/nfts/src/mock.rs | 8 ++++---- frame/nfts/src/tests.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 14691c21a0ef2..26ab16871e879 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -90,7 +90,7 @@ pub mod pallet { /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Identifier for the collection of item. type CollectionId: Member + Parameter + MaxEncodedLen + Copy; diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index ad7a94b3eed50..bfa6c185ed78c 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -50,7 +50,7 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type Origin = Origin; - type Call = Call; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -58,7 +58,7 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); @@ -75,7 +75,7 @@ impl frame_system::Config for Test { impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); @@ -85,7 +85,7 @@ impl pallet_balances::Config for Test { } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type CollectionId = u32; type ItemId = u32; type Currency = Balances; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 9fb29d0e95c26..19d24f4924d46 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -80,7 +80,7 @@ fn events() -> Vec> { let result = System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let mock::Event::Nfts(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let mock::RuntimeEvent::Nfts(inner) = e { Some(inner) } else { None }) .collect::>(); System::reset_events(); @@ -933,7 +933,7 @@ fn buy_item_should_work() { // freeze collection assert_ok!(Nfts::freeze_collection(Origin::signed(user_1), collection_id)); - let buy_item_call = mock::Call::Nfts(crate::Call::::buy_item { + let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, @@ -945,7 +945,7 @@ fn buy_item_should_work() { // freeze item assert_ok!(Nfts::freeze(Origin::signed(user_1), collection_id, item_3)); - let buy_item_call = mock::Call::Nfts(crate::Call::::buy_item { + let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, From f369ba95b223d4fd908bd44cbc20e12d7dfc3448 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 15 Sep 2022 13:25:39 +0800 Subject: [PATCH 06/60] cargo fmt --- frame/nfts/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 26ab16871e879..4297752348d43 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -90,7 +90,8 @@ pub mod pallet { /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Identifier for the collection of item. type CollectionId: Member + Parameter + MaxEncodedLen + Copy; From ae5561efbc06dc73bb80e6d873694d0edac2b67e Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 15 Sep 2022 14:22:47 +0800 Subject: [PATCH 07/60] Fixes --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5d4bbf3ba244f..5e2e7141508e3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1482,7 +1482,7 @@ impl pallet_uniques::Config for Runtime { } impl pallet_nfts::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type CollectionId = u32; type ItemId = u32; type Currency = Balances; From f34b0ffffc1e4f6b57b97dedd11778f1c0beee62 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 15 Sep 2022 14:37:38 +0800 Subject: [PATCH 08/60] Fixes --- frame/nfts/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index ca38851222e8d..7368446e593df 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -126,9 +126,9 @@ fn add_item_attribute, I: 'static>( (key, caller, caller_lookup) } -fn assert_last_event, I: 'static>(generic_event: >::Event) { +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { let events = frame_system::Pallet::::events(); - let system_event: ::Event = generic_event.into(); + let system_event: ::RuntimeEvent = generic_event.into(); // compare to the last event record let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; assert_eq!(event, &system_event); From d09df86f269db78ab622032f4f4e9152776f7770 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 19 Sep 2022 17:45:28 +0300 Subject: [PATCH 09/60] Fix CI --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 4297752348d43..cdb098d2eceed 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -109,9 +109,9 @@ pub mod pallet { /// Standard collection creation is only allowed if the origin attempting it and the /// collection are in this set. type CreateOrigin: EnsureOriginWithArg< - Success = Self::AccountId, Self::Origin, Self::CollectionId, + Success = Self::AccountId, >; /// Locker trait to enable Locking mechanism downstream. From 3ab3356471c61ed9a5c92ffb166264d80ba2c785 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Fri, 23 Sep 2022 10:37:15 +0200 Subject: [PATCH 10/60] Nfts: Fix Auto-Increment (#12223) * commit * passing benchmarks * clean up * sync * runtime implementation * fix * fmt * fix benchmark * cfg * remove try-increment-id * remove unused error * impl Incrementable for unsigned types * clean up * fix in tests * not needed anymore * Use OptionQuery Co-authored-by: Keith Yeung --- frame/nfts/src/benchmarking.rs | 13 +++----- frame/nfts/src/functions.rs | 15 ++++++++++ frame/nfts/src/lib.rs | 48 ++++++++++++++++++++++++------ frame/nfts/src/tests.rs | 54 +++++++++++++++++----------------- 4 files changed, 85 insertions(+), 45 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 7368446e593df..eca404df2f142 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -42,13 +42,8 @@ fn create_collection, I: 'static>( let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Nfts::::force_create( - SystemOrigin::Root.into(), - collection, - caller_lookup.clone(), - false, - ) - .is_ok()); + assert!(Nfts::::force_create(SystemOrigin::Root.into(), caller_lookup.clone(), false,) + .is_ok()); (collection, caller, caller_lookup) } @@ -142,7 +137,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { collection, admin }; + let call = Call::::create { admin }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -151,7 +146,7 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, T::Helper::collection(0), caller_lookup, true) + }: _(SystemOrigin::Root, caller_lookup, true) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 27ab752dbabf6..f935a5b2eba90 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -94,7 +94,12 @@ impl, I: 'static> Pallet { }, ); + let next_id = collection.increment(); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(Some(next_id)); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); Self::deposit_event(event); Ok(()) } @@ -284,4 +289,14 @@ impl, I: 'static> Pallet { Ok(()) } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) + } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index cdb098d2eceed..2cde6e5e1ab5d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -61,6 +61,29 @@ pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +pub trait Incrementable { + fn increment(&self) -> Self; + fn initial_value() -> Self; +} + +macro_rules! impl_incrementable { + ($($type:ty),+) => { + $( + impl Incrementable for $type { + fn increment(&self) -> Self { + self.saturating_add(1) + } + + fn initial_value() -> Self { + 0 + } + } + )+ + }; +} + +impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + #[frame_support::pallet] pub mod pallet { use super::*; @@ -94,7 +117,7 @@ pub mod pallet { + IsType<::RuntimeEvent>; /// Identifier for the collection of item. - type CollectionId: Member + Parameter + MaxEncodedLen + Copy; + type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; /// The type used to identify a unique item within a collection. type ItemId: Member + Parameter + MaxEncodedLen + Copy; @@ -278,6 +301,12 @@ pub mod pallet { pub(super) type CollectionMaxSupply, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + #[pallet::storage] + /// Stores the `CollectionId` that is going to be used for the next collection. + /// This gets incremented by 1 whenever a new collection is created. + pub(super) type NextCollectionId, I: 'static = ()> = + StorageValue<_, T::CollectionId, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -372,6 +401,8 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// Event gets emmited when the `NextCollectionId` gets incremented. + NextCollectionIdIncremented { next_id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -458,7 +489,6 @@ pub mod pallet { /// `ItemDeposit` funds of sender are reserved. /// /// Parameters: - /// - `collection`: The identifier of the new collection. This must not be currently in use. /// - `admin`: The admin of this collection. The admin is the initial address of each /// member of the collection's admin team. /// @@ -466,11 +496,10 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::create())] - pub fn create( - origin: OriginFor, - collection: T::CollectionId, - admin: AccountIdLookupOf, - ) -> DispatchResult { + pub fn create(origin: OriginFor, admin: AccountIdLookupOf) -> DispatchResult { + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; @@ -492,7 +521,6 @@ pub mod pallet { /// /// Unlike `create`, no funds are reserved. /// - /// - `collection`: The identifier of the new item. This must not be currently in use. /// - `owner`: The owner of this collection of items. The owner has full superuser /// permissions /// over this item, but may later change and configure the permissions using @@ -504,13 +532,15 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - collection: T::CollectionId, owner: AccountIdLookupOf, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + Self::do_create_collection( collection, owner.clone(), diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 19d24f4924d46..f5d44bef7f886 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -98,12 +98,12 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(Origin::root(), 1, 2, true)); + assert_ok!(Nfts::force_create(Origin::root(), 2, true)); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Nfts::mint(Origin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); @@ -114,7 +114,7 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 1)); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); @@ -157,7 +157,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 1)); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -168,7 +168,7 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -179,7 +179,7 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 3)); @@ -194,7 +194,7 @@ fn transfer_should_work() { #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(Origin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); @@ -211,7 +211,7 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -236,7 +236,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create(Origin::signed(1), 1)); assert_eq!(collections(), vec![(1, 0)]); assert_noop!(Nfts::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted); assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); @@ -275,7 +275,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 2)); @@ -294,7 +294,7 @@ fn set_collection_metadata_should_work() { Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), @@ -351,7 +351,7 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( @@ -404,7 +404,7 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -449,7 +449,7 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -481,7 +481,7 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); @@ -515,7 +515,7 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create(Origin::root(), 1, false)); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -539,7 +539,7 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); @@ -554,7 +554,7 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -601,7 +601,7 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); let current_block = 1; @@ -620,7 +620,7 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); for i in 3..13 { @@ -640,7 +640,7 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. @@ -664,7 +664,7 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -692,7 +692,7 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -714,7 +714,7 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create(Origin::root(), 1, true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -747,7 +747,7 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create(Origin::root(), user_id, true)); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( @@ -793,7 +793,7 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create(Origin::root(), user_id, true)); assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_1, user_id)); assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_2, user_id)); @@ -851,7 +851,7 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_1, true)); + assert_ok!(Nfts::force_create(Origin::root(), user_1, true)); assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_1, user_1)); assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_2, user_1)); From 6434da8420cade31594db3aea83ea08decf29e0b Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 10:29:38 +0300 Subject: [PATCH 11/60] Rename Origin to RuntimeOrigin --- frame/nfts/Cargo.toml | 2 +- frame/nfts/src/lib.rs | 4 +- frame/nfts/src/mock.rs | 2 +- frame/nfts/src/tests.rs | 558 ++++++++++++++++++++++++---------------- 4 files changed, 336 insertions(+), 230 deletions(-) diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 7f1ce4ff416b0..5e1a3d79d3a65 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -32,7 +32,7 @@ sp-std = { version = "4.0.0", path = "../../primitives/std" } default = ["std"] std = [ "codec/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "log/std", diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 2cde6e5e1ab5d..ddd76aba024ef 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -127,12 +127,12 @@ pub mod pallet { /// The origin which may forcibly create or destroy an item or otherwise alter privileged /// attributes. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; /// Standard collection creation is only allowed if the origin attempting it and the /// collection are in this set. type CreateOrigin: EnsureOriginWithArg< - Self::Origin, + Self::RuntimeOrigin, Self::CollectionId, Success = Self::AccountId, >; diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index bfa6c185ed78c..8492291bfad05 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -49,7 +49,7 @@ impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index f5d44bef7f886..ac08c35b23294 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -98,14 +98,14 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(Origin::root(), 2, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, true)); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(Origin::signed(2), 1, 69, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -114,32 +114,32 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0], false)); assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 10)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10)); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 20)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20)); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42], false)); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69], false)); assert_eq!(Balances::reserved_balance(&1), 13); assert!(ItemMetadataOf::::contains_key(0, 69)); let w = Collection::::get(0).unwrap().destroy_witness(); assert_eq!(w.items, 2); assert_eq!(w.item_metadatas, 2); - assert_ok!(Nfts::destroy(Origin::signed(1), 0, w)); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Collection::::contains_key(0)); @@ -157,19 +157,19 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); - assert_noop!(Nfts::destroy(Origin::signed(1), 0, w), Error::::BadWitness); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -179,54 +179,63 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); - assert_noop!(Nfts::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4), + Error::::NoPermission + ); - assert_ok!(Nfts::approve_transfer(Origin::signed(3), 0, 42, 2, None)); - assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 4)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(3), 0, 42, 2, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4)); }); } #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::freeze(Origin::signed(1), 0, 42)); - assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Nfts::thaw(Origin::signed(1), 0, 42)); - assert_ok!(Nfts::freeze_collection(Origin::signed(1), 0)); - assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(1), 0)); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Nfts::thaw_collection(Origin::signed(1), 0)); - assert_ok!(Nfts::transfer(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(1), 0)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); } #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); - assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), + Error::::NoPermission + ); + assert_noop!( + Nfts::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), + Error::::NoPermission + ); + assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2), Error::::NoPermission); assert_noop!( - Nfts::transfer_ownership(Origin::signed(2), 0, 2), + Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission ); - assert_noop!(Nfts::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); - assert_noop!(Nfts::freeze(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); - assert_noop!(Nfts::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_noop!(Nfts::destroy(Origin::signed(2), 0, w), Error::::NoPermission); + assert_noop!(Nfts::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); }); } @@ -236,11 +245,14 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(Origin::signed(1), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); assert_eq!(collections(), vec![(1, 0)]); - assert_noop!(Nfts::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted); - assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); - assert_ok!(Nfts::transfer_ownership(Origin::signed(1), 0, 2)); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), + Error::::Unaccepted + ); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); assert_eq!(collections(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); @@ -248,18 +260,23 @@ fn transfer_owner_should_work() { assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 2); - assert_ok!(Nfts::set_accept_ownership(Origin::signed(1), Some(0))); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(1), Some(0))); assert_noop!( - Nfts::transfer_ownership(Origin::signed(1), 0, 1), + Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), Error::::NoPermission ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); - assert_ok!(Nfts::set_accept_ownership(Origin::signed(3), Some(0))); - assert_ok!(Nfts::transfer_ownership(Origin::signed(2), 0, 3)); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(2), + 0, + bvec![0u8; 20], + false + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); assert_eq!(Balances::total_balance(&2), 57); assert_eq!(Balances::total_balance(&3), 145); @@ -268,21 +285,24 @@ fn transfer_owner_should_work() { // 2's acceptence from before is reset when it became owner, so it cannot be transfered // without a fresh acceptance. - assert_noop!(Nfts::transfer_ownership(Origin::signed(3), 0, 2), Error::::Unaccepted); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), + Error::::Unaccepted + ); }); } #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 2)); - assert_ok!(Nfts::freeze(Origin::signed(4), 0, 42)); - assert_ok!(Nfts::thaw(Origin::signed(3), 0, 42)); - assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 3)); - assert_ok!(Nfts::burn(Origin::signed(3), 0, 42, None)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); + assert_ok!(Nfts::thaw(RuntimeOrigin::signed(3), 0, 42)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 42, None)); }); } @@ -291,56 +311,79 @@ fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown item assert_noop!( - Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 20], + false + )); assert_eq!(Balances::free_balance(&1), 9); assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Nfts::set_collection_metadata(Origin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 15], + false + )); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 25], + false + )); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 15], + true + )); assert_noop!( - Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Nfts::clear_collection_metadata(Origin::signed(1), 0), Error::::Frozen); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::Frozen + ); // Clear Metadata - assert_ok!(Nfts::set_collection_metadata(Origin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15], false)); assert_noop!( - Nfts::clear_collection_metadata(Origin::signed(2), 0), + Nfts::clear_collection_metadata(RuntimeOrigin::signed(2), 0), Error::::NoPermission ); assert_noop!( - Nfts::clear_collection_metadata(Origin::signed(1), 1), + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 1), Error::::UnknownCollection ); - assert_ok!(Nfts::clear_collection_metadata(Origin::signed(1), 0)); + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0)); assert!(!CollectionMetadataOf::::contains_key(0)); }); } @@ -351,50 +394,53 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), + Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 8); assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. - assert_ok!(Nfts::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false)); assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25], false)); assert_eq!(Balances::free_balance(&1), 3); // Cannot over-reserve assert_noop!( - Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 40], false), + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], true)); assert_noop!( - Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false), + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Nfts::clear_metadata(Origin::signed(1), 0, 42), Error::::Frozen); + assert_noop!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), Error::::Frozen); // Clear Metadata - assert_ok!(Nfts::set_metadata(Origin::root(), 0, 42, bvec![0u8; 15], false)); - assert_noop!(Nfts::clear_metadata(Origin::signed(2), 0, 42), Error::::NoPermission); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15], false)); assert_noop!( - Nfts::clear_metadata(Origin::signed(1), 1, 42), + Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), Error::::UnknownCollection ); - assert_ok!(Nfts::clear_metadata(Origin::signed(1), 0, 42)); + assert_ok!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); }); } @@ -404,11 +450,11 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1], bvec![0])); assert_eq!( attributes(0), vec![ @@ -419,7 +465,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0; 10])); assert_eq!( attributes(0), vec![ @@ -430,7 +476,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 18); - assert_ok!(Nfts::clear_attribute(Origin::signed(1), 0, Some(0), bvec![1])); + assert_ok!(Nfts::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] @@ -438,7 +484,7 @@ fn set_attribute_should_work() { assert_eq!(Balances::reserved_balance(1), 15); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::destroy(Origin::signed(1), 0, w)); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(attributes(0), vec![]); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -449,11 +495,11 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![0])); assert_eq!( attributes(0), vec![ @@ -464,15 +510,18 @@ fn set_attribute_should_respect_freeze() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![], true)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); + assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 0, bvec![], true)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1]), e); - assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_noop!( + Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), + e + ); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1])); }); } @@ -481,32 +530,32 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Nfts::force_item_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 142, 1)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 169, 2)); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 169, bvec![0; 20], false)); + assert_ok!(Nfts::force_item_status(RuntimeOrigin::root(), 0, 1, 1, 1, 1, true, false)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); - assert_ok!(Nfts::redeposit(Origin::signed(1), 0, bvec![0, 42, 50, 69, 100])); + assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); assert_eq!(Balances::reserved_balance(1), 63); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 42); - assert_ok!(Nfts::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -515,23 +564,29 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 1, false)); - assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_noop!( - Nfts::burn(Origin::signed(5), 0, 42, Some(5)), + Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5)), Error::::UnknownCollection ); - assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 5)); - assert_ok!(Nfts::mint(Origin::signed(2), 0, 69, 5)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5)); assert_eq!(Balances::reserved_balance(1), 2); - assert_noop!(Nfts::burn(Origin::signed(0), 0, 42, None), Error::::NoPermission); - assert_noop!(Nfts::burn(Origin::signed(5), 0, 42, Some(6)), Error::::WrongOwner); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(0), 0, 42, None), + Error::::NoPermission + ); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(6)), + Error::::WrongOwner + ); - assert_ok!(Nfts::burn(Origin::signed(5), 0, 42, Some(5))); - assert_ok!(Nfts::burn(Origin::signed(3), 0, 69, Some(5))); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5))); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 69, Some(5))); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -539,61 +594,64 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); - assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3), + Error::::NoPermission + ); assert!(Item::::get(0, 42).unwrap().approvals.is_empty()); - assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 2, None)); - assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 2, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 2)); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 1, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(2), 1, 42, 3), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 43, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 43, 3), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(3), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(3), 0, 42, 3), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 42, 4), + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 4), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(Origin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3)); assert_noop!( - Nfts::cancel_approval(Origin::signed(2), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3), Error::::NotDelegate ); let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); // approval expires after 2 blocks. - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, Some(2))); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( - Nfts::cancel_approval(Origin::signed(5), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3), Error::::NoPermission ); System::set_block_number(current_block + 3); // 5 can cancel the approval since the deadline has passed. - assert_ok!(Nfts::cancel_approval(Origin::signed(5), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3)); assert_eq!(approvals(0, 69), vec![]); }); } @@ -601,34 +659,40 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 4, None)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 5, Some(2))); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 5, Some(2))); assert_eq!(approvals(0, 42), vec![(3, None), (4, None), (5, Some(current_block + 2))]); - assert_ok!(Nfts::transfer(Origin::signed(4), 0, 42, 6)); - assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 7), Error::::NoPermission); - assert_noop!(Nfts::transfer(Origin::signed(5), 0, 42, 8), Error::::NoPermission); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 6)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 7), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(5), 0, 42, 8), + Error::::NoPermission + ); }); } #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); for i in 3..13 { - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, i, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); } // the limit is 10 assert_noop!( - Nfts::approve_transfer(Origin::signed(2), 0, 42, 14, None), + Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 14, None), Error::::ReachedApprovalLimit ); }); @@ -640,50 +704,53 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, Some(2))); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); System::set_block_number(3); - assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 4), Error::::ApprovalExpired); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4), + Error::::ApprovalExpired + ); System::set_block_number(1); - assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_eq!(System::block_number(), 1); // make a new approval with a deadline after 4 blocks, so it will expire after the 5th // block. - assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 6, Some(4))); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 6, Some(4))); // this should still work. System::set_block_number(5); - assert_ok!(Nfts::transfer(Origin::signed(6), 0, 42, 5)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(6), 0, 42, 5)); }); } #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 1, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(1), 1, 42, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 43, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 43, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 42, 4), + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 4), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(Origin::signed(1), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 3)); assert_noop!( - Nfts::cancel_approval(Origin::signed(1), 0, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 1), Error::::NotDelegate ); }); @@ -692,40 +759,46 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( - Nfts::cancel_approval(Origin::root(), 1, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, 1), Error::::UnknownCollection ); assert_noop!( - Nfts::cancel_approval(Origin::root(), 0, 43, 1), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, 1), Error::::UnknownCollection ); - assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, 4), Error::::NotDelegate); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 4), + Error::::NotDelegate + ); - assert_ok!(Nfts::cancel_approval(Origin::root(), 0, 42, 3)); - assert_noop!(Nfts::cancel_approval(Origin::root(), 0, 42, 1), Error::::NotDelegate); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 3)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 1), + Error::::NotDelegate + ); }); } #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 1, true)); - assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 4, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); assert_noop!( - Nfts::clear_all_transfer_approvals(Origin::signed(3), 0, 42), + Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(3), 0, 42), Error::::NoPermission ); - assert_ok!(Nfts::clear_all_transfer_approvals(Origin::signed(2), 0, 42)); + assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(2), 0, 42)); assert!(events().contains(&Event::::AllApprovalsCancelled { collection: 0, @@ -734,8 +807,14 @@ fn clear_all_transfer_approvals_works() { })); assert_eq!(approvals(0, 42), vec![]); - assert_noop!(Nfts::transfer(Origin::signed(3), 0, 42, 5), Error::::NoPermission); - assert_noop!(Nfts::transfer(Origin::signed(4), 0, 42, 5), Error::::NoPermission); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 5), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 5), + Error::::NoPermission + ); }); } @@ -747,11 +826,11 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(Origin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( - Origin::signed(user_id), + RuntimeOrigin::signed(user_id), collection_id, max_supply )); @@ -763,21 +842,25 @@ fn max_supply_should_work() { })); assert_noop!( - Nfts::set_collection_max_supply(Origin::signed(user_id), collection_id, max_supply + 1), + Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + 1 + ), Error::::MaxSupplyAlreadySet ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, 0, user_id)); - assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, 1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); assert_noop!( - Nfts::mint(Origin::signed(user_id), collection_id, 2, user_id), + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), Error::::MaxSupplyReached ); // validate we remove the CollectionMaxSupply record when we destroy the collection assert_ok!(Nfts::destroy( - Origin::signed(user_id), + RuntimeOrigin::signed(user_id), collection_id, Collection::::get(collection_id).unwrap().destroy_witness() )); @@ -793,15 +876,21 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(Origin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); - assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); - assert_ok!(Nfts::set_price(Origin::signed(user_id), collection_id, item_1, Some(1), None,)); + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + Some(1), + None, + )); assert_ok!(Nfts::set_price( - Origin::signed(user_id), + RuntimeOrigin::signed(user_id), collection_id, item_2, Some(2), @@ -824,7 +913,13 @@ fn set_price_should_work() { })); // validate we can unset the price - assert_ok!(Nfts::set_price(Origin::signed(user_id), collection_id, item_2, None, None)); + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + None, + None + )); assert!(events().contains(&Event::::ItemPriceRemoved { collection: collection_id, item: item_2 @@ -851,14 +946,14 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(Origin::root(), user_1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, true)); - assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_2, user_1)); - assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_3, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); assert_ok!(Nfts::set_price( - Origin::signed(user_1), + RuntimeOrigin::signed(user_1), collection_id, item_1, Some(price_1), @@ -866,7 +961,7 @@ fn buy_item_should_work() { )); assert_ok!(Nfts::set_price( - Origin::signed(user_1), + RuntimeOrigin::signed(user_1), collection_id, item_2, Some(price_2), @@ -875,12 +970,17 @@ fn buy_item_should_work() { // can't buy for less assert_noop!( - Nfts::buy_item(Origin::signed(user_2), collection_id, item_1, 1), + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_1, 1), Error::::BidTooLow ); // pass the higher price to validate it will still deduct correctly - assert_ok!(Nfts::buy_item(Origin::signed(user_2), collection_id, item_1, price_1 + 1,)); + assert_ok!(Nfts::buy_item( + RuntimeOrigin::signed(user_2), + collection_id, + item_1, + price_1 + 1, + )); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -890,18 +990,18 @@ fn buy_item_should_work() { // can't buy from yourself assert_noop!( - Nfts::buy_item(Origin::signed(user_1), collection_id, item_2, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_1), collection_id, item_2, price_2), Error::::NoPermission ); // can't buy when the item is listed for a specific buyer assert_noop!( - Nfts::buy_item(Origin::signed(user_2), collection_id, item_2, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_2, price_2), Error::::NoPermission ); // can buy when I'm a whitelisted buyer - assert_ok!(Nfts::buy_item(Origin::signed(user_3), collection_id, item_2, price_2,)); + assert_ok!(Nfts::buy_item(RuntimeOrigin::signed(user_3), collection_id, item_2, price_2,)); assert!(events().contains(&Event::::ItemBought { collection: collection_id, @@ -916,14 +1016,14 @@ fn buy_item_should_work() { // can't buy when item is not for sale assert_noop!( - Nfts::buy_item(Origin::signed(user_2), collection_id, item_3, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_3, price_2), Error::::NotForSale ); // ensure we can't buy an item when the collection or an item is frozen { assert_ok!(Nfts::set_price( - Origin::signed(user_1), + RuntimeOrigin::signed(user_1), collection_id, item_3, Some(price_1), @@ -931,26 +1031,32 @@ fn buy_item_should_work() { )); // freeze collection - assert_ok!(Nfts::freeze_collection(Origin::signed(user_1), collection_id)); + assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(user_1), collection_id)); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, }); - assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::Frozen + ); - assert_ok!(Nfts::thaw_collection(Origin::signed(user_1), collection_id)); + assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(user_1), collection_id)); // freeze item - assert_ok!(Nfts::freeze(Origin::signed(user_1), collection_id, item_3)); + assert_ok!(Nfts::freeze(RuntimeOrigin::signed(user_1), collection_id, item_3)); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, item: item_3, bid_price: price_1, }); - assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::Frozen + ); } }); } From 61dee82b0c0085306eeb995c5dab4083c1a30aeb Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Sat, 24 Sep 2022 12:15:31 +0300 Subject: [PATCH 12/60] [Uniques V2] Tips (#12168) * Allow to add tips when buying an NFT * Chore * Rework tips feature * Add weights + benchmarks * Convert tuple to struct * Fix benchmark * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update frame/nfts/src/benchmarking.rs Co-authored-by: Oliver Tale-Yazdi * Fix benchmarks * Revert the bounded_vec![] approach * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/src/benchmarking.rs | 23 ++++ frame/nfts/src/features/buy_sell.rs | 42 +++++++ frame/nfts/src/features/mod.rs | 18 +++ frame/nfts/src/lib.rs | 30 +++++ frame/nfts/src/mock.rs | 1 + frame/nfts/src/tests.rs | 47 ++++++- frame/nfts/src/types.rs | 21 +++- frame/nfts/src/weights.rs | 187 +++++++++++++++------------- 9 files changed, 282 insertions(+), 89 deletions(-) create mode 100644 frame/nfts/src/features/buy_sell.rs create mode 100644 frame/nfts/src/features/mod.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7bb244ccd5732..eb6941e85215c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1472,6 +1472,7 @@ parameter_types! { pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; pub const ApprovalsLimit: u32 = 20; + pub const MaxTips: u32 = 10; } impl pallet_uniques::Config for Runtime { @@ -1510,6 +1511,7 @@ impl pallet_nfts::Config for Runtime { type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; type ApprovalsLimit = ApprovalsLimit; + type MaxTips = MaxTips; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index eca404df2f142..00527abc99e02 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -453,5 +453,28 @@ benchmarks_instance_pallet! { }.into()); } + pay_tips { + let n in 0 .. T::MaxTips::get() as u32; + let amount = BalanceOf::::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::(Event::TipSent { + collection, + item, + sender: caller.clone(), + receiver: caller.clone(), + amount, + }.into()); + } + } + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs new file mode 100644 index 0000000000000..295d7fadfa8e6 --- /dev/null +++ b/frame/nfts/src/features/buy_sell.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + pub fn do_pay_tips( + sender: T::AccountId, + tips: BoundedVec, 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(()) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs new file mode 100644 index 0000000000000..5661797978439 --- /dev/null +++ b/frame/nfts/src/features/mod.rs @@ -0,0 +1,18 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 buy_sell; diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index ddd76aba024ef..a4555cd655a08 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -35,6 +35,7 @@ pub mod mock; #[cfg(test)] mod tests; +mod features; mod functions; mod impl_nonfungibles; mod types; @@ -177,6 +178,10 @@ pub mod pallet { #[pallet::constant] type ApprovalsLimit: Get; + /// The max number of tips a user could send. + #[pallet::constant] + type MaxTips: Get; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -420,6 +425,14 @@ pub mod pallet { seller: T::AccountId, buyer: T::AccountId, }, + /// A tip was sent. + TipSent { + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + receiver: T::AccountId, + amount: DepositBalanceOf, + }, } #[pallet::error] @@ -1608,5 +1621,22 @@ pub mod pallet { let origin = ensure_signed(origin)?; Self::do_buy_item(collection, item, origin, bid_price) } + + /// Allows to pay the tips. + /// + /// Origin must be Signed. + /// + /// - `tips`: Tips array. + /// + /// Emits `TipSent` on every tip transfer. + #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))] + #[transactional] + pub fn pay_tips( + origin: OriginFor, + tips: BoundedVec, T::MaxTips>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_pay_tips(origin, tips) + } } } diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 8492291bfad05..c13298aa8162d 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -101,6 +101,7 @@ impl Config for Test { type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; type ApprovalsLimit = ConstU32<10>; + type MaxTips = ConstU32<10>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index ac08c35b23294..7004fb1f4e93b 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1001,7 +1001,7 @@ fn buy_item_should_work() { ); // can buy when I'm a whitelisted buyer - assert_ok!(Nfts::buy_item(RuntimeOrigin::signed(user_3), collection_id, item_2, price_2,)); + assert_ok!(Nfts::buy_item(RuntimeOrigin::signed(user_3), collection_id, item_2, price_2)); assert!(events().contains(&Event::::ItemBought { collection: collection_id, @@ -1060,3 +1060,48 @@ fn buy_item_should_work() { } }); } + +#[test] +fn pay_tips_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_id = 1; + let tip = 2; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Nfts::pay_tips( + RuntimeOrigin::signed(user_1), + bvec![ + ItemTip { collection: collection_id, item: item_id, receiver: user_2, amount: tip }, + ItemTip { collection: collection_id, item: item_id, receiver: user_3, amount: tip }, + ] + )); + + assert_eq!(Balances::total_balance(&user_1), initial_balance - tip * 2); + assert_eq!(Balances::total_balance(&user_2), initial_balance + tip); + assert_eq!(Balances::total_balance(&user_3), initial_balance + tip); + + let events = events(); + assert!(events.contains(&Event::::TipSent { + collection: collection_id, + item: item_id, + sender: user_1, + receiver: user_2, + amount: tip, + })); + assert!(events.contains(&Event::::TipSent { + collection: collection_id, + item: item_id, + sender: user_1, + receiver: user_3, + amount: tip, + })); + }); +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index db1c351c4a9c5..e91c513e4c4c2 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -30,8 +30,15 @@ pub(super) type CollectionDetailsFor = CollectionDetails<::AccountId, DepositBalanceOf>; pub(super) type ItemDetailsFor = ItemDetails<::AccountId, DepositBalanceOf, ApprovalsOf>; -pub(super) type ItemPrice = +pub(super) type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; +pub(super) type ItemPrice = BalanceOf; +pub(super) type ItemTipOf = ItemTip< + >::CollectionId, + >::ItemId, + ::AccountId, + BalanceOf, +>; #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { @@ -127,3 +134,15 @@ pub struct ItemMetadata> { /// Whether the item metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemTip { + /// A collection of the item. + pub(super) collection: CollectionId, + /// An item of which the tip is send for. + pub(super) item: ItemId, + /// A sender of the tip. + pub(super) receiver: AccountId, + /// An amount the sender is willing to tip. + pub(super) amount: Amount, +} diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 2c90ab54fe9fb..1dab0838f32b2 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -73,24 +73,27 @@ pub trait WeightInfo { fn set_collection_max_supply() -> Weight; fn set_price() -> Weight; fn buy_item() -> Weight; + fn pay_tips(n: u32, ) -> Weight; } /// Weights for pallet_nfts using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(33_018_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + Weight::from_ref_time(37_627_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } + // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(20_957_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + Weight::from_ref_time(25_748_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) @@ -104,26 +107,24 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(10_091_000 as u64).saturating_mul(n as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(1_748_000 as u64).saturating_mul(m as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(1_621_000 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(2_449_817_000 as u64) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(m as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) + .saturating_add(T::DbWeight::get().writes(2004 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_007_000 as u64) + Weight::from_ref_time(43_014_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } @@ -132,7 +133,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(43_922_000 as u64) + Weight::from_ref_time(44_421_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } @@ -141,7 +142,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(33_951_000 as u64) + Weight::from_ref_time(34_315_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } @@ -149,9 +150,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(11_194_000 as u64).saturating_mul(i as u64)) + Weight::from_ref_time(22_836_000 as u64) + // Standard Error: 9_131 + .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(T::DbWeight::get().writes(1 as u64)) @@ -160,26 +161,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(26_745_000 as u64) + Weight::from_ref_time(27_329_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(27_466_000 as u64) + Weight::from_ref_time(27_842_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(22_591_000 as u64) + Weight::from_ref_time(23_129_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(22_392_000 as u64) + Weight::from_ref_time(22_584_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } @@ -187,20 +188,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_202_000 as u64) + Weight::from_ref_time(31_684_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_063_000 as u64) + Weight::from_ref_time(23_143_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(25_598_000 as u64) + Weight::from_ref_time(25_684_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } @@ -208,7 +209,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(48_684_000 as u64) + Weight::from_ref_time(50_159_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } @@ -216,76 +217,76 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_267_000 as u64) + Weight::from_ref_time(47_824_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(40_174_000 as u64) + Weight::from_ref_time(39_968_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(41_611_000 as u64) + Weight::from_ref_time(42_182_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(40_073_000 as u64) + Weight::from_ref_time(39_330_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_191_000 as u64) + Weight::from_ref_time(38_351_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_461_000 as u64) + Weight::from_ref_time(29_530_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_690_000 as u64) + Weight::from_ref_time(29_417_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(27_758_000 as u64) + Weight::from_ref_time(28_482_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_425_000 as u64) + Weight::from_ref_time(25_851_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_533_000 as u64) + Weight::from_ref_time(24_836_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(24_745_000 as u64) + Weight::from_ref_time(25_665_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } @@ -294,27 +295,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(47_967_000 as u64) + Weight::from_ref_time(47_502_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + Weight::from_ref_time(5_417_000 as u64) + // Standard Error: 32_526 + .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + } } // For backwards compatibility and tests impl WeightInfo for () { + // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(33_018_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + Weight::from_ref_time(37_627_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } + // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(20_957_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + Weight::from_ref_time(25_748_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) @@ -328,26 +337,24 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(10_091_000 as u64).saturating_mul(n as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(1_748_000 as u64).saturating_mul(m as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(1_621_000 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(2_449_817_000 as u64) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(m as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) + .saturating_add(RocksDbWeight::get().writes(2004 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_007_000 as u64) + Weight::from_ref_time(43_014_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -356,7 +363,7 @@ impl WeightInfo for () { // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(43_922_000 as u64) + Weight::from_ref_time(44_421_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } @@ -365,7 +372,7 @@ impl WeightInfo for () { // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(33_951_000 as u64) + Weight::from_ref_time(34_315_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } @@ -373,9 +380,9 @@ impl WeightInfo for () { // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(11_194_000 as u64).saturating_mul(i as u64)) + Weight::from_ref_time(22_836_000 as u64) + // Standard Error: 9_131 + .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(RocksDbWeight::get().writes(1 as u64)) @@ -384,26 +391,26 @@ impl WeightInfo for () { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(26_745_000 as u64) + Weight::from_ref_time(27_329_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(27_466_000 as u64) + Weight::from_ref_time(27_842_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(22_591_000 as u64) + Weight::from_ref_time(23_129_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(22_392_000 as u64) + Weight::from_ref_time(22_584_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } @@ -411,20 +418,20 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_202_000 as u64) + Weight::from_ref_time(31_684_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_063_000 as u64) + Weight::from_ref_time(23_143_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(25_598_000 as u64) + Weight::from_ref_time(25_684_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } @@ -432,7 +439,7 @@ impl WeightInfo for () { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(48_684_000 as u64) + Weight::from_ref_time(50_159_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } @@ -440,76 +447,76 @@ impl WeightInfo for () { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_267_000 as u64) + Weight::from_ref_time(47_824_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(40_174_000 as u64) + Weight::from_ref_time(39_968_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(41_611_000 as u64) + Weight::from_ref_time(42_182_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(40_073_000 as u64) + Weight::from_ref_time(39_330_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_191_000 as u64) + Weight::from_ref_time(38_351_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_461_000 as u64) + Weight::from_ref_time(29_530_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_690_000 as u64) + Weight::from_ref_time(29_417_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(27_758_000 as u64) + Weight::from_ref_time(28_482_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_425_000 as u64) + Weight::from_ref_time(25_851_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_533_000 as u64) + Weight::from_ref_time(24_836_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(24_745_000 as u64) + Weight::from_ref_time(25_665_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } @@ -518,8 +525,14 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(47_967_000 as u64) + Weight::from_ref_time(47_502_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + Weight::from_ref_time(5_417_000 as u64) + // Standard Error: 32_526 + .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + } } From 490a0fad02d1d0d3cd494eb48d1bca5f67e094ba Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 5 Oct 2022 13:58:04 +0300 Subject: [PATCH 13/60] [Uniques V2] Atomic NFTs swap (#12285) * Atomic NFTs swap * Fmt * Fix benchmark * Rename swap -> atomic_swap * Update target balance * Rollback * Fix * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Make desired item optional * Apply suggestions * Update frame/nfts/src/features/atomic_swap.rs Co-authored-by: Squirrel * Rename fields * Optimisation * Add a comment * deadline -> maybe_deadline * Add docs * Change comments * Add price direction field * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Wrap price and direction * Fix benchmarks * Use ensure! instead of if {} * Make duration param mandatory and limit it to MaxDeadlineDuration * Make the code safer * Fix clippy * Chore * Remove unused vars * try * try 2 * try 3 Co-authored-by: command-bot <> Co-authored-by: Squirrel --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/src/benchmarking.rs | 84 ++++++- frame/nfts/src/features/atomic_swap.rs | 175 ++++++++++++++ frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/functions.rs | 4 + frame/nfts/src/lib.rs | 145 ++++++++++++ frame/nfts/src/mock.rs | 1 + frame/nfts/src/tests.rs | 315 ++++++++++++++++++++++++- frame/nfts/src/types.rs | 26 ++ frame/nfts/src/weights.rs | 223 ++++++++++------- 10 files changed, 891 insertions(+), 85 deletions(-) create mode 100644 frame/nfts/src/features/atomic_swap.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index eb6941e85215c..875346472f21a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1473,6 +1473,7 @@ parameter_types! { pub const ValueLimit: u32 = 256; pub const ApprovalsLimit: u32 = 20; pub const MaxTips: u32 = 10; + pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } impl pallet_uniques::Config for Runtime { @@ -1512,6 +1513,7 @@ impl pallet_nfts::Config for Runtime { type ValueLimit = ValueLimit; type ApprovalsLimit = ApprovalsLimit; type MaxTips = MaxTips; + type MaxDeadlineDuration = MaxDeadlineDuration; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 00527abc99e02..b35122cf919c7 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -29,7 +29,7 @@ use frame_support::{ BoundedVec, }; use frame_system::RawOrigin as SystemOrigin; -use sp_runtime::traits::Bounded; +use sp_runtime::traits::{Bounded, One}; use sp_std::prelude::*; use crate::Pallet as Nfts; @@ -476,5 +476,87 @@ benchmarks_instance_pallet! { } } + create_swap { + let (collection, caller, _) = create_collection::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::from(100u32); + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + let duration = T::MaxDeadlineDuration::get(); + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration) + verify { + let current_block = frame_system::Pallet::::block_number(); + assert_last_event::(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::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::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 }; + frame_system::Pallet::::set_block_number(One::one()); + Nfts::::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item1) + verify { + assert_last_event::(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::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::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()); + let origin = SystemOrigin::Signed(caller.clone()); + frame_system::Pallet::::set_block_number(One::one()); + Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; + Nfts::::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 = frame_system::Pallet::::block_number(); + assert_last_event::(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()); + } + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs new file mode 100644 index 0000000000000..116da57477f4e --- /dev/null +++ b/frame/nfts/src/features/atomic_swap.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + pub 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, + maybe_price: Option>>, + duration: ::BlockNumber, + ) -> DispatchResult { + ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); + + let item = Item::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownItem)?; + ensure!(item.owner == caller, Error::::NoPermission); + + match maybe_desired_item_id { + Some(desired_item_id) => ensure!( + Item::::contains_key(&desired_collection_id, &desired_item_id), + Error::::UnknownItem + ), + None => ensure!( + Collection::::contains_key(&desired_collection_id), + Error::::UnknownCollection + ), + }; + + let now = frame_system::Pallet::::block_number(); + let deadline = duration.saturating_add(now); + + PendingSwapOf::::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(()) + } + + pub fn do_cancel_swap( + caller: T::AccountId, + offered_collection_id: T::CollectionId, + offered_item_id: T::ItemId, + ) -> DispatchResult { + let swap = PendingSwapOf::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownSwap)?; + + let now = frame_system::Pallet::::block_number(); + if swap.deadline > now { + let item = Item::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownItem)?; + ensure!(item.owner == caller, Error::::NoPermission); + } + + PendingSwapOf::::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(()) + } + + pub 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>>, + ) -> DispatchResult { + let send_item = Item::::get(&send_collection_id, &send_item_id) + .ok_or(Error::::UnknownItem)?; + let receive_item = Item::::get(&receive_collection_id, &receive_item_id) + .ok_or(Error::::UnknownItem)?; + let swap = PendingSwapOf::::get(&receive_collection_id, &receive_item_id) + .ok_or(Error::::UnknownSwap)?; + + ensure!(send_item.owner == caller, Error::::NoPermission); + ensure!( + swap.desired_collection == send_collection_id && swap.price == witness_price, + Error::::UnknownSwap + ); + + if let Some(desired_item) = swap.desired_item { + ensure!(desired_item == send_item_id, Error::::UnknownSwap); + } + + let now = frame_system::Pallet::::block_number(); + ensure!(now <= swap.deadline, Error::::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(()) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 5661797978439..24b58dee5f3a0 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -15,4 +15,5 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod atomic_swap; pub mod buy_sell; diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index f935a5b2eba90..17be672834bf2 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -56,6 +56,7 @@ impl, I: 'static> Pallet { Item::::insert(&collection, &item, &details); ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); Self::deposit_event(Event::Transferred { collection, @@ -129,6 +130,8 @@ impl, I: 'static> Pallet { ItemMetadataOf::::remove_prefix(&collection, None); #[allow(deprecated)] ItemPriceOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + PendingSwapOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); #[allow(deprecated)] Attribute::::remove_prefix((&collection,), None); @@ -219,6 +222,7 @@ impl, I: 'static> Pallet { Item::::remove(&collection, &item); Account::::remove((&owner, &collection, &item)); ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index a4555cd655a08..7e9b2a42f7e14 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -182,6 +182,10 @@ pub mod pallet { #[pallet::constant] type MaxTips: Get; + /// The max duration in blocks for deadlines. + #[pallet::constant] + type MaxDeadlineDuration: Get<::BlockNumber>; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -312,6 +316,23 @@ pub mod pallet { pub(super) type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; + #[pallet::storage] + /// Handles all the pending swaps. + pub(super) type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + PendingSwap< + T::CollectionId, + T::ItemId, + PriceWithDirection>, + ::BlockNumber, + >, + OptionQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -433,6 +454,35 @@ pub mod pallet { receiver: T::AccountId, amount: DepositBalanceOf, }, + /// An `item` swap intent was created. + SwapCreated { + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + desired_item: Option, + price: Option>>, + deadline: ::BlockNumber, + }, + /// The swap was cancelled. + SwapCancelled { + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + desired_item: Option, + price: Option>>, + deadline: ::BlockNumber, + }, + /// The swap has been claimed. + SwapClaimed { + sent_collection: T::CollectionId, + sent_item: T::ItemId, + sent_item_owner: T::AccountId, + received_collection: T::CollectionId, + received_item: T::ItemId, + received_item_owner: T::AccountId, + price: Option>>, + deadline: ::BlockNumber, + }, } #[pallet::error] @@ -471,12 +521,18 @@ pub mod pallet { MaxSupplyTooSmall, /// The given item ID is unknown. UnknownItem, + /// Swap doesn't exist. + UnknownSwap, /// Item is not for sale. NotForSale, /// The provided bid is too low. BidTooLow, /// The item has reached its approval limit. ReachedApprovalLimit, + /// The deadline has already expired. + DeadlineExpired, + /// The duration provided should be less or equal to MaxDeadlineDuration. + WrongDuration, } impl, I: 'static> Pallet { @@ -1638,5 +1694,94 @@ pub mod pallet { let origin = ensure_signed(origin)?; Self::do_pay_tips(origin, tips) } + + /// Register a new atomic swap, declaring an intention to send an `item` in exchange for + /// `desired_item` from origin to target on the current blockchain. + /// The target can execute the swap during the specified `duration` of blocks (if set). + /// Additionally, the price could be set for the desired `item`. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item an owner wants to give. + /// - `desired_collection`: The collection of the desired item. + /// - `desired_item`: The desired item an owner wants to receive. + /// - `maybe_price`: The price an owner is willing to pay or receive for the desired `item`. + /// - `maybe_duration`: Optional deadline for the swap. Specified by providing the + /// number of blocks after which the swap will expire. + /// + /// Emits `SwapCreated` on success. + #[pallet::weight(T::WeightInfo::create_swap())] + pub fn create_swap( + origin: OriginFor, + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + maybe_desired_item: Option, + maybe_price: Option>>, + duration: ::BlockNumber, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_create_swap( + origin, + offered_collection, + offered_item, + desired_collection, + maybe_desired_item, + maybe_price, + duration, + ) + } + + /// Cancel an atomic swap. + /// + /// Origin must be Signed. + /// Origin must be an owner of the `item` if the deadline hasn't expired. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item an owner wants to give. + /// + /// Emits `SwapCancelled` on success. + #[pallet::weight(T::WeightInfo::cancel_swap())] + pub fn cancel_swap( + origin: OriginFor, + offered_collection: T::CollectionId, + offered_item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_cancel_swap(origin, offered_collection, offered_item) + } + + /// Claim an atomic swap. + /// This method executes a pending swap, that was created by a counterpart before. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `send_collection`: The collection of the item to be sent. + /// - `send_item`: The item to be sent. + /// - `receive_collection`: The collection of the item to be received. + /// - `receive_item`: The item to be received. + /// - `witness_price`: A price that was previously agreed on. + /// + /// Emits `SwapClaimed` on success. + #[pallet::weight(T::WeightInfo::claim_swap())] + pub fn claim_swap( + origin: OriginFor, + send_collection: T::CollectionId, + send_item: T::ItemId, + receive_collection: T::CollectionId, + receive_item: T::ItemId, + witness_price: Option>>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_claim_swap( + origin, + send_collection, + send_item, + receive_collection, + receive_item, + witness_price, + ) + } } } diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index c13298aa8162d..23493829eaca7 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -102,6 +102,7 @@ impl Config for Test { type ValueLimit = ConstU32<50>; type ApprovalsLimit = ConstU32<10>; type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 7004fb1f4e93b..0d2d0c661b273 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -18,7 +18,11 @@ //! Tests for Nfts pallet. use crate::{mock::*, Event, *}; -use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency}; +use frame_support::{ + assert_noop, assert_ok, + dispatch::Dispatchable, + traits::{Currency, Get}, +}; use pallet_balances::Error as BalancesError; use sp_std::prelude::*; @@ -1105,3 +1109,312 @@ fn pay_tips_should_work() { })); }); } + +#[test] +fn create_cancel_swap_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let price = 1; + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + let duration = 2; + let expect_deadline = 3; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + + // validate desired item and the collection exists + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2 + 1), + Some(price_with_direction.clone()), + duration, + ), + Error::::UnknownItem + ); + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id + 1, + None, + Some(price_with_direction.clone()), + duration, + ), + Error::::UnknownCollection + ); + + let max_duration: u64 = ::MaxDeadlineDuration::get(); + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + max_duration.saturating_add(1), + ), + Error::::WrongDuration + ); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + + let swap = PendingSwapOf::::get(collection_id, item_1).unwrap(); + assert_eq!(swap.desired_collection, collection_id); + assert_eq!(swap.desired_item, Some(item_2)); + assert_eq!(swap.price, Some(price_with_direction.clone())); + assert_eq!(swap.deadline, expect_deadline); + + assert!(events().contains(&Event::::SwapCreated { + offered_collection: collection_id, + offered_item: item_1, + desired_collection: collection_id, + desired_item: Some(item_2), + price: Some(price_with_direction.clone()), + deadline: expect_deadline, + })); + + // validate we can cancel the swap + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert!(events().contains(&Event::::SwapCancelled { + offered_collection: collection_id, + offered_item: item_1, + desired_collection: collection_id, + desired_item: Some(item_2), + price: Some(price_with_direction.clone()), + deadline: expect_deadline, + })); + assert!(!PendingSwapOf::::contains_key(collection_id, item_1)); + + // validate anyone can cancel the expired swap + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + assert_noop!( + Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1), + Error::::NoPermission + ); + System::set_block_number(expect_deadline + 1); + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1)); + + // validate optional desired_item param + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + None, + Some(price_with_direction), + duration, + )); + + let swap = PendingSwapOf::::get(collection_id, item_1).unwrap(); + assert_eq!(swap.desired_item, None); + }); +} + +#[test] +fn claim_swap_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user_1 = 1; + let user_2 = 2; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let item_4 = 4; + let item_5 = 5; + let price = 100; + let price_direction = PriceDirection::Receive; + let price_with_direction = + PriceWithDirection { amount: price, direction: price_direction.clone() }; + let duration = 2; + let initial_balance = 1000; + let deadline = 1 + duration; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, true)); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_4, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_5, user_2)); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + + // validate the deadline + System::set_block_number(5); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate edge cases + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_4, // no swap was created for that asset + Some(price_with_direction.clone()), + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_4, // not my item + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::NoPermission + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_5, // my item, but not the one another part wants + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(PriceWithDirection { amount: price + 1, direction: price_direction.clone() }), // wrong price + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(PriceWithDirection { amount: price, direction: PriceDirection::Send }), // wrong direction + ), + Error::::UnknownSwap + ); + + assert_ok!(Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(price_with_direction.clone()), + )); + + // validate the new owner + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + let item = Item::::get(collection_id, item_2).unwrap(); + assert_eq!(item.owner, user_1); + + // validate the balances + assert_eq!(Balances::total_balance(&user_1), initial_balance + price); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price); + + // ensure we reset the swap + assert!(!PendingSwapOf::::contains_key(collection_id, item_1)); + + // validate the event + assert!(events().contains(&Event::::SwapClaimed { + sent_collection: collection_id, + sent_item: item_2, + sent_item_owner: user_2, + received_collection: collection_id, + received_item: item_1, + received_item_owner: user_1, + price: Some(price_with_direction.clone()), + deadline, + })); + + // validate the optional desired_item param and another price direction + let price_direction = PriceDirection::Send; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_1), + collection_id, + item_4, + collection_id, + None, + Some(price_with_direction.clone()), + duration, + )); + assert_ok!(Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_1, + collection_id, + item_4, + Some(price_with_direction), + )); + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_1); + let item = Item::::get(collection_id, item_4).unwrap(); + assert_eq!(item.owner, user_2); + + assert_eq!(Balances::total_balance(&user_1), initial_balance - price); + assert_eq!(Balances::total_balance(&user_2), initial_balance + price); + }); +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index e91c513e4c4c2..399de3c5dad1e 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -146,3 +146,29 @@ pub struct ItemTip { /// An amount the sender is willing to tip. pub(super) amount: Amount, } + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct PendingSwap { + /// A collection of the item user wants to receive. + pub(super) desired_collection: CollectionId, + /// An item user wants to receive. + pub(super) desired_item: Option, + /// A price for the desired `item` with the direction. + pub(super) price: Option, + /// An optional deadline for the swap. + pub(super) deadline: Deadline, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum PriceDirection { + Send, + Receive, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct PriceWithDirection { + /// An amount. + pub(super) amount: Amount, + /// A direction (send or receive). + pub(super) direction: PriceDirection, +} diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 1dab0838f32b2..9d62db0f8d85d 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -74,6 +74,9 @@ pub trait WeightInfo { fn set_price() -> Weight; fn buy_item() -> Weight; fn pay_tips(n: u32, ) -> Weight; + fn create_swap() -> Weight; + fn cancel_swap() -> Weight; + fn claim_swap() -> Weight; } /// Weights for pallet_nfts using the Substrate node and recommended hardware. @@ -83,7 +86,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(37_627_000 as u64) + Weight::from_ref_time(38_062_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } @@ -91,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_748_000 as u64) + Weight::from_ref_time(25_917_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } @@ -107,13 +110,13 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_449_817_000 as u64) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(2_432_555_000 as u64) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(8_474_465 as u64).saturating_mul(n as u64)) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(333_758 as u64).saturating_mul(m as u64)) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(222_052 as u64).saturating_mul(a as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(T::DbWeight::get().writes(2004 as u64)) @@ -124,7 +127,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionMaxSupply (r:1 w:0) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_014_000 as u64) + Weight::from_ref_time(43_755_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } @@ -132,27 +135,29 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(44_421_000 as u64) + Weight::from_ref_time(46_768_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(34_315_000 as u64) + Weight::from_ref_time(36_282_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(22_836_000 as u64) - // Standard Error: 9_131 - .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) + Weight::from_ref_time(23_359_000 as u64) + // Standard Error: 9_645 + .saturating_add(Weight::from_ref_time(10_822_144 as u64).saturating_mul(i as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(T::DbWeight::get().writes(1 as u64)) @@ -161,26 +166,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(27_329_000 as u64) + Weight::from_ref_time(27_805_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(27_842_000 as u64) + Weight::from_ref_time(27_712_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(23_129_000 as u64) + Weight::from_ref_time(23_068_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(22_584_000 as u64) + Weight::from_ref_time(23_200_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } @@ -188,20 +193,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_684_000 as u64) + Weight::from_ref_time(31_800_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_143_000 as u64) + Weight::from_ref_time(23_959_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(25_684_000 as u64) + Weight::from_ref_time(26_334_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } @@ -209,7 +214,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_159_000 as u64) + Weight::from_ref_time(50_978_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } @@ -217,76 +222,76 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_824_000 as u64) + Weight::from_ref_time(49_555_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(39_968_000 as u64) + Weight::from_ref_time(41_099_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_182_000 as u64) + Weight::from_ref_time(42_893_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_330_000 as u64) + Weight::from_ref_time(39_785_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_351_000 as u64) + Weight::from_ref_time(39_764_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_530_000 as u64) + Weight::from_ref_time(29_577_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_417_000 as u64) + Weight::from_ref_time(29_696_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_482_000 as u64) + Weight::from_ref_time(28_692_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(25_851_000 as u64) + Weight::from_ref_time(26_345_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_836_000 as u64) + Weight::from_ref_time(24_826_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(25_665_000 as u64) + Weight::from_ref_time(26_376_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } @@ -294,16 +299,41 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(47_502_000 as u64) + Weight::from_ref_time(49_140_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_417_000 as u64) - // Standard Error: 32_526 - .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + Weight::from_ref_time(5_477_000 as u64) + // Standard Error: 33_188 + .saturating_add(Weight::from_ref_time(4_285_339 as u64).saturating_mul(n as u64)) + } + // Storage: Nfts Asset (r:2 w:0) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn create_swap() -> Weight { + Weight::from_ref_time(30_330_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Nfts PendingSwapOf (r:1 w:1) + // Storage: Nfts Asset (r:1 w:0) + fn cancel_swap() -> Weight { + Weight::from_ref_time(30_516_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Nfts Asset (r:2 w:2) + // Storage: Nfts PendingSwapOf (r:1 w:2) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Account (r:0 w:4) + // Storage: Nfts ItemPriceOf (r:0 w:2) + fn claim_swap() -> Weight { + Weight::from_ref_time(66_191_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) } } @@ -313,7 +343,7 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(37_627_000 as u64) + Weight::from_ref_time(38_062_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -321,7 +351,7 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_748_000 as u64) + Weight::from_ref_time(25_917_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -337,13 +367,13 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_449_817_000 as u64) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(2_432_555_000 as u64) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(8_474_465 as u64).saturating_mul(n as u64)) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(333_758 as u64).saturating_mul(m as u64)) + // Standard Error: 28_964 + .saturating_add(Weight::from_ref_time(222_052 as u64).saturating_mul(a as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(RocksDbWeight::get().writes(2004 as u64)) @@ -354,7 +384,7 @@ impl WeightInfo for () { // Storage: Nfts CollectionMaxSupply (r:1 w:0) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_014_000 as u64) + Weight::from_ref_time(43_755_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -362,27 +392,29 @@ impl WeightInfo for () { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(44_421_000 as u64) + Weight::from_ref_time(46_768_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(34_315_000 as u64) + Weight::from_ref_time(36_282_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(22_836_000 as u64) - // Standard Error: 9_131 - .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) + Weight::from_ref_time(23_359_000 as u64) + // Standard Error: 9_645 + .saturating_add(Weight::from_ref_time(10_822_144 as u64).saturating_mul(i as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(RocksDbWeight::get().writes(1 as u64)) @@ -391,26 +423,26 @@ impl WeightInfo for () { // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn freeze() -> Weight { - Weight::from_ref_time(27_329_000 as u64) + Weight::from_ref_time(27_805_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn thaw() -> Weight { - Weight::from_ref_time(27_842_000 as u64) + Weight::from_ref_time(27_712_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn freeze_collection() -> Weight { - Weight::from_ref_time(23_129_000 as u64) + Weight::from_ref_time(23_068_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) fn thaw_collection() -> Weight { - Weight::from_ref_time(22_584_000 as u64) + Weight::from_ref_time(23_200_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } @@ -418,20 +450,20 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_684_000 as u64) + Weight::from_ref_time(31_800_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_143_000 as u64) + Weight::from_ref_time(23_959_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - Weight::from_ref_time(25_684_000 as u64) + Weight::from_ref_time(26_334_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } @@ -439,7 +471,7 @@ impl WeightInfo for () { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_159_000 as u64) + Weight::from_ref_time(50_978_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } @@ -447,76 +479,76 @@ impl WeightInfo for () { // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_824_000 as u64) + Weight::from_ref_time(49_555_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(39_968_000 as u64) + Weight::from_ref_time(41_099_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_182_000 as u64) + Weight::from_ref_time(42_893_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_330_000 as u64) + Weight::from_ref_time(39_785_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_351_000 as u64) + Weight::from_ref_time(39_764_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_530_000 as u64) + Weight::from_ref_time(29_577_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_417_000 as u64) + Weight::from_ref_time(29_696_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_482_000 as u64) + Weight::from_ref_time(28_692_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(25_851_000 as u64) + Weight::from_ref_time(26_345_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_836_000 as u64) + Weight::from_ref_time(24_826_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(25_665_000 as u64) + Weight::from_ref_time(26_376_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } @@ -524,15 +556,40 @@ impl WeightInfo for () { // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(47_502_000 as u64) + Weight::from_ref_time(49_140_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_417_000 as u64) - // Standard Error: 32_526 - .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + Weight::from_ref_time(5_477_000 as u64) + // Standard Error: 33_188 + .saturating_add(Weight::from_ref_time(4_285_339 as u64).saturating_mul(n as u64)) + } + // Storage: Nfts Asset (r:2 w:0) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn create_swap() -> Weight { + Weight::from_ref_time(30_330_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Nfts PendingSwapOf (r:1 w:1) + // Storage: Nfts Asset (r:1 w:0) + fn cancel_swap() -> Weight { + Weight::from_ref_time(30_516_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Nfts Asset (r:2 w:2) + // Storage: Nfts PendingSwapOf (r:1 w:2) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Account (r:0 w:4) + // Storage: Nfts ItemPriceOf (r:0 w:2) + fn claim_swap() -> Weight { + Weight::from_ref_time(66_191_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) } } From ef16fd2da6ec283919551228e253f8490f79c3e9 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:59:53 +0300 Subject: [PATCH 14/60] [Uniques V2] Feature flags (#12367) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Rework pallet features * Move macros * Change comments * Fmt * Refactor Incrementable * Use pub(crate) for do_* functions * Update comments * Refactor freeze and lock functions * Rework Collection config and Item confg api * Chore * Make clippy happy * Chore * Update comment * RequiredDeposit => DepositRequired * Address comments Co-authored-by: command-bot <> --- Cargo.lock | 5 +- bin/node/runtime/src/lib.rs | 6 + frame/nfts/Cargo.toml | 1 + frame/nfts/src/benchmarking.rs | 105 ++- frame/nfts/src/features/atomic_swap.rs | 15 +- frame/nfts/src/features/buy_sell.rs | 2 +- frame/nfts/src/features/lock.rs | 120 +++ frame/nfts/src/features/mod.rs | 2 + frame/nfts/src/features/settings.rs | 47 ++ frame/nfts/src/functions.rs | 81 +- frame/nfts/src/impl_nonfungibles.rs | 32 +- frame/nfts/src/lib.rs | 479 +++++++----- frame/nfts/src/macros.rs | 74 ++ frame/nfts/src/mock.rs | 7 +- frame/nfts/src/tests.rs | 734 ++++++++++++++---- frame/nfts/src/types.rs | 151 +++- frame/nfts/src/weights.rs | 352 +++++---- frame/support/src/traits/tokens.rs | 2 + .../src/traits/tokens/nonfungible_v2.rs | 204 +++++ .../src/traits/tokens/nonfungibles_v2.rs | 243 ++++++ 20 files changed, 2042 insertions(+), 620 deletions(-) create mode 100644 frame/nfts/src/features/lock.rs create mode 100644 frame/nfts/src/features/settings.rs create mode 100644 frame/nfts/src/macros.rs create mode 100644 frame/support/src/traits/tokens/nonfungible_v2.rs create mode 100644 frame/support/src/traits/tokens/nonfungibles_v2.rs diff --git a/Cargo.lock b/Cargo.lock index 3151b061d461e..27e94a780add3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1895,9 +1895,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", ] @@ -5935,6 +5935,7 @@ dependencies = [ name = "pallet-nfts" version = "4.0.0-dev" dependencies = [ + "enumflags2", "frame-benchmarking", "frame-support", "frame-system", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d8152ceb770c2..6d8de0b47100b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -53,6 +53,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_nfts::PalletFeatures; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; @@ -1504,6 +1505,10 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +parameter_types! { + pub Features: PalletFeatures = PalletFeatures::all_enabled(); +} + impl pallet_nfts::Config for Runtime { type RuntimeEvent = RuntimeEvent; type CollectionId = u32; @@ -1521,6 +1526,7 @@ impl pallet_nfts::Config for Runtime { type ApprovalsLimit = ApprovalsLimit; type MaxTips = MaxTips; type MaxDeadlineDuration = MaxDeadlineDuration; + type Features = Features; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 5e1a3d79d3a65..f0b68ea702e3a 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +enumflags2 = { version = "0.7.5" } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index b35122cf919c7..c65430fd35108 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -24,6 +24,7 @@ use frame_benchmarking::{ account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; use frame_support::{ + assert_ok, dispatch::UnfilteredDispatchable, traits::{EnsureOrigin, Get}, BoundedVec, @@ -42,8 +43,11 @@ fn create_collection, I: 'static>( let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Nfts::::force_create(SystemOrigin::Root.into(), caller_lookup.clone(), false,) - .is_ok()); + assert_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + CollectionConfig::all_settings_enabled() + )); (collection, caller, caller_lookup) } @@ -53,13 +57,11 @@ fn add_collection_metadata, I: 'static>() -> (T::AccountId, Account whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Nfts::::set_collection_metadata( + assert_ok!(Nfts::::set_collection_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), - false, - ) - .is_ok()); + )); (caller, caller_lookup) } @@ -72,13 +74,13 @@ fn mint_item, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let item = T::Helper::item(index); - assert!(Nfts::::mint( + assert_ok!(Nfts::::mint( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, caller_lookup.clone(), - ) - .is_ok()); + ItemConfig::all_settings_enabled(), + )); (item, caller, caller_lookup) } @@ -90,14 +92,12 @@ fn add_item_metadata, I: 'static>( whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Nfts::::set_metadata( + assert_ok!(Nfts::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, vec![0; T::StringLimit::get() as usize].try_into().unwrap(), - false, - ) - .is_ok()); + )); (caller, caller_lookup) } @@ -110,14 +110,13 @@ fn add_item_attribute, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); - assert!(Nfts::::set_attribute( + assert_ok!(Nfts::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), Some(item), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), - ) - .is_ok()); + )); (key, caller, caller_lookup) } @@ -137,7 +136,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { admin }; + let call = Call::::create { admin, config: CollectionConfig::all_settings_enabled() }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -146,25 +145,19 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, caller_lookup, true) + }: _(SystemOrigin::Root, caller_lookup, CollectionConfig::all_settings_enabled()) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } destroy { let n in 0 .. 1_000; - let m in 0 .. 1_000; - let a in 0 .. 1_000; let (collection, caller, caller_lookup) = create_collection::(); add_collection_metadata::(); for i in 0..n { mint_item::(i as u16); - } - for i in 0..m { add_item_metadata::(T::Helper::item(i as u16)); - } - for i in 0..a { add_item_attribute::(T::Helper::item(i as u16)); } let witness = Collection::::get(collection).unwrap().destroy_witness(); @@ -176,7 +169,7 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, ItemConfig::all_settings_enabled()) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -204,56 +197,51 @@ benchmarks_instance_pallet! { let i in 0 .. 5_000; let (collection, caller, caller_lookup) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); - Nfts::::force_item_status( + Nfts::::force_collection_status( SystemOrigin::Root.into(), collection, caller_lookup.clone(), caller_lookup.clone(), caller_lookup.clone(), caller_lookup, - true, - false, + CollectionConfig(CollectionSetting::DepositRequired.into()), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); } - freeze { + lock_item_transfer { let (collection, caller, caller_lookup) = create_collection::(); let (item, ..) = mint_item::(0); }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) verify { - assert_last_event::(Event::Frozen { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); + assert_last_event::(Event::ItemTransferLocked { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); } - thaw { + unlock_item_transfer { let (collection, caller, caller_lookup) = create_collection::(); let (item, ..) = mint_item::(0); - Nfts::::freeze( + Nfts::::lock_item_transfer( SystemOrigin::Signed(caller.clone()).into(), collection, item, )?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { - assert_last_event::(Event::Thawed { collection, item }.into()); + assert_last_event::(Event::ItemTransferUnlocked { collection, item }.into()); } - freeze_collection { + lock_collection { let (collection, caller, caller_lookup) = create_collection::(); - }: _(SystemOrigin::Signed(caller.clone()), collection) + let lock_config = CollectionConfig( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes, + ); + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_config) verify { - assert_last_event::(Event::CollectionFrozen { collection }.into()); - } - - thaw_collection { - let (collection, caller, caller_lookup) = create_collection::(); - let origin = SystemOrigin::Signed(caller.clone()).into(); - Nfts::::freeze_collection(origin, collection)?; - }: _(SystemOrigin::Signed(caller.clone()), collection) - verify { - assert_last_event::(Event::CollectionThawed { collection }.into()); + assert_last_event::(Event::CollectionLocked { collection }.into()); } transfer_ownership { @@ -283,21 +271,30 @@ benchmarks_instance_pallet! { }.into()); } - force_item_status { + force_collection_status { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_item_status { + let call = Call::::force_collection_status { collection, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), freezer: caller_lookup, - free_holding: true, - is_frozen: false, + config: CollectionConfig(CollectionSetting::DepositRequired.into()), }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::ItemStatusChanged { collection }.into()); + assert_last_event::(Event::CollectionStatusChanged { collection }.into()); + } + + lock_item_properties { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + let lock_metadata = true; + let lock_attributes = true; + }: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes) + verify { + assert_last_event::(Event::ItemPropertiesLocked { collection, item, lock_metadata, lock_attributes }.into()); } set_attribute { @@ -327,9 +324,9 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection, item, data.clone()) verify { - assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { collection, item, data }.into()); } clear_metadata { @@ -345,9 +342,9 @@ benchmarks_instance_pallet! { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection, data.clone()) verify { - assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); + assert_last_event::(Event::CollectionMetadataSet { collection, data }.into()); } clear_collection_metadata { diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs index 116da57477f4e..bacaccdaedcbf 100644 --- a/frame/nfts/src/features/atomic_swap.rs +++ b/frame/nfts/src/features/atomic_swap.rs @@ -22,7 +22,7 @@ use frame_support::{ }; impl, I: 'static> Pallet { - pub fn do_create_swap( + pub(crate) fn do_create_swap( caller: T::AccountId, offered_collection_id: T::CollectionId, offered_item_id: T::ItemId, @@ -31,6 +31,10 @@ impl, I: 'static> Pallet { maybe_price: Option>>, duration: ::BlockNumber, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Swaps), + Error::::MethodDisabled + ); ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); let item = Item::::get(&offered_collection_id, &offered_item_id) @@ -74,7 +78,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn do_cancel_swap( + pub(crate) fn do_cancel_swap( caller: T::AccountId, offered_collection_id: T::CollectionId, offered_item_id: T::ItemId, @@ -103,7 +107,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn do_claim_swap( + pub(crate) fn do_claim_swap( caller: T::AccountId, send_collection_id: T::CollectionId, send_item_id: T::ItemId, @@ -111,6 +115,11 @@ impl, I: 'static> Pallet { receive_item_id: T::ItemId, witness_price: Option>>, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Swaps), + Error::::MethodDisabled + ); + let send_item = Item::::get(&send_collection_id, &send_item_id) .ok_or(Error::::UnknownItem)?; let receive_item = Item::::get(&receive_collection_id, &receive_item_id) diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs index 295d7fadfa8e6..c1e29057af9c9 100644 --- a/frame/nfts/src/features/buy_sell.rs +++ b/frame/nfts/src/features/buy_sell.rs @@ -22,7 +22,7 @@ use frame_support::{ }; impl, I: 'static> Pallet { - pub fn do_pay_tips( + pub(crate) fn do_pay_tips( sender: T::AccountId, tips: BoundedVec, T::MaxTips>, ) -> DispatchResult { diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs new file mode 100644 index 0000000000000..0a5fecc1d6224 --- /dev/null +++ b/frame/nfts/src/features/lock.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_lock_collection( + origin: T::AccountId, + collection: T::CollectionId, + lock_config: CollectionConfig, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + + if lock_config.has_disabled_setting(CollectionSetting::TransferableItems) { + config.disable_setting(CollectionSetting::TransferableItems); + } + if lock_config.has_disabled_setting(CollectionSetting::UnlockedMetadata) { + config.disable_setting(CollectionSetting::UnlockedMetadata); + } + if lock_config.has_disabled_setting(CollectionSetting::UnlockedAttributes) { + config.disable_setting(CollectionSetting::UnlockedAttributes); + } + + Self::deposit_event(Event::::CollectionLocked { collection }); + Ok(()) + }) + } + + pub(crate) fn do_lock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + + let mut config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_setting(ItemSetting::Transferable) { + config.disable_setting(ItemSetting::Transferable); + } + ItemConfigOf::::insert(&collection, &item, config); + + Self::deposit_event(Event::::ItemTransferLocked { collection, item }); + Ok(()) + } + + pub(crate) fn do_unlock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + + let mut config = Self::get_item_config(&collection, &item)?; + if config.has_disabled_setting(ItemSetting::Transferable) { + config.enable_setting(ItemSetting::Transferable); + } + ItemConfigOf::::insert(&collection, &item, config); + + Self::deposit_event(Event::::ItemTransferUnlocked { collection, item }); + Ok(()) + } + + pub(crate) fn do_lock_item_properties( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; + + if lock_metadata { + config.disable_setting(ItemSetting::UnlockedMetadata); + } + if lock_attributes { + config.disable_setting(ItemSetting::UnlockedAttributes); + } + + Self::deposit_event(Event::::ItemPropertiesLocked { + collection, + item, + lock_metadata, + lock_attributes, + }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 24b58dee5f3a0..47e5816bc953c 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -17,3 +17,5 @@ pub mod atomic_swap; pub mod buy_sell; +pub mod lock; +pub mod settings; diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs new file mode 100644 index 0000000000000..2596d360d8dcd --- /dev/null +++ b/frame/nfts/src/features/settings.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +/// The helper methods bellow allow to read and validate different +/// collection/item/pallet settings. +/// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular +/// collection, or for a specific item. +impl, I: 'static> Pallet { + pub(crate) fn get_collection_config( + collection_id: &T::CollectionId, + ) -> Result { + let config = CollectionConfigOf::::get(&collection_id) + .ok_or(Error::::UnknownCollection)?; + Ok(config) + } + + pub(crate) fn get_item_config( + collection_id: &T::CollectionId, + item_id: &T::ItemId, + ) -> Result { + let config = ItemConfigOf::::get(&collection_id, &item_id) + .ok_or(Error::::UnknownItem)?; + Ok(config) + } + + pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool { + let features = T::Features::get(); + return features.is_enabled(feature) + } +} diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 17be672834bf2..275e3668d7a20 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -36,12 +36,22 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!collection_details.is_frozen, Error::::Frozen); - ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNotTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - ensure!(!details.is_frozen, Error::::Frozen); + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; with_details(&collection_details, &mut details)?; Account::::remove((&details.owner, &collection, &item)); @@ -71,11 +81,11 @@ impl, I: 'static> Pallet { collection: T::CollectionId, owner: T::AccountId, admin: T::AccountId, + config: CollectionConfig, deposit: DepositBalanceOf, - free_holding: bool, event: Event, ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::InUse); + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); T::Currency::reserve(&owner, deposit)?; @@ -87,16 +97,15 @@ impl, I: 'static> Pallet { admin: admin.clone(), freezer: admin, total_deposit: deposit, - free_holding, items: 0, item_metadatas: 0, attributes: 0, - is_frozen: false, }, ); let next_id = collection.increment(); + CollectionConfigOf::::insert(&collection, config); CollectionAccount::::insert(&owner, &collection, ()); NextCollectionId::::set(Some(next_id)); @@ -138,6 +147,8 @@ impl, I: 'static> Pallet { CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); CollectionMaxSupply::::remove(&collection); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); Self::deposit_event(Event::Destroyed { collection }); @@ -153,6 +164,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, + config: ItemConfig, with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -173,21 +185,27 @@ impl, I: 'static> Pallet { collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; - let deposit = match collection_details.free_holding { - true => Zero::zero(), - false => T::ItemDeposit::get(), + let collection_config = Self::get_collection_config(&collection)?; + let deposit = match collection_config + .is_setting_enabled(CollectionSetting::DepositRequired) + { + true => T::ItemDeposit::get(), + false => Zero::zero(), }; T::Currency::reserve(&collection_details.owner, deposit)?; collection_details.total_deposit += deposit; let owner = owner.clone(); Account::::insert((&owner, &collection, &item), ()); - let details = ItemDetails { - owner, - approvals: ApprovalsOf::::default(), - is_frozen: false, - deposit, - }; + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, config); + } + + let details = + ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; Item::::insert(&collection, &item, details); Ok(()) }, @@ -224,6 +242,13 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_settings() { + ItemConfigOf::::remove(&collection, &item); + } + Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) } @@ -235,9 +260,26 @@ impl, I: 'static> Pallet { price: Option>, whitelisted_buyer: Option, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNotTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + if let Some(ref price) = price { ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); Self::deposit_event(Event::ItemPriceSet { @@ -260,6 +302,11 @@ impl, I: 'static> Pallet { buyer: T::AccountId, bid_price: ItemPrice, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner != buyer, Error::::NoPermission); diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index cead6f562ab58..8a7c79fc0c14f 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -19,7 +19,8 @@ use super::*; use frame_support::{ - traits::{tokens::nonfungibles::*, Get}, + ensure, + traits::{tokens::nonfungibles_v2::*, Get}, BoundedSlice, }; use sp_runtime::{DispatchError, DispatchResult}; @@ -78,26 +79,40 @@ impl, I: 'static> Inspect<::AccountId> for Palle /// /// Default implementation is that all items are transferable. fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { - match (Collection::::get(collection), Item::::get(collection, item)) { - (Some(cd), Some(id)) if !cd.is_frozen && !id.is_frozen => true, + match ( + CollectionConfigOf::::get(collection), + ItemConfigOf::::get(collection, item), + ) { + (Some(cc), Some(ic)) + if cc.is_setting_enabled(CollectionSetting::TransferableItems) && + ic.is_setting_enabled(ItemSetting::Transferable) => + true, _ => false, } } } -impl, I: 'static> Create<::AccountId> for Pallet { +impl, I: 'static> Create<::AccountId, CollectionConfig> + for Pallet +{ /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, + config: &CollectionConfig, ) -> DispatchResult { + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); Self::do_create_collection( *collection, who.clone(), admin.clone(), + *config, T::CollectionDeposit::get(), - false, Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, ) } @@ -119,13 +134,16 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId> for Pallet { +impl, I: 'static> Mutate<::AccountId, ItemSettings> + for Pallet +{ fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, who: &T::AccountId, + settings: &ItemSettings, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings), |_| Ok(())) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 7e9b2a42f7e14..bfba0c1ea3330 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -40,6 +40,7 @@ mod functions; mod impl_nonfungibles; mod types; +pub mod macros; pub mod weights; use codec::{Decode, Encode}; @@ -62,29 +63,6 @@ pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -pub trait Incrementable { - fn increment(&self) -> Self; - fn initial_value() -> Self; -} - -macro_rules! impl_incrementable { - ($($type:ty),+) => { - $( - impl Incrementable for $type { - fn increment(&self) -> Self { - self.saturating_add(1) - } - - fn initial_value() -> Self { - 0 - } - } - )+ - }; -} - -impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); - #[frame_support::pallet] pub mod pallet { use super::*; @@ -186,6 +164,10 @@ pub mod pallet { #[pallet::constant] type MaxDeadlineDuration: Get<::BlockNumber>; + /// Disables some of pallet's features. + #[pallet::constant] + type Features: Get; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -194,15 +176,9 @@ pub mod pallet { type WeightInfo: WeightInfo; } - pub type ApprovalsOf = BoundedBTreeMap< - ::AccountId, - Option<::BlockNumber>, - >::ApprovalsLimit, - >; - + /// Details of a collection. #[pallet::storage] #[pallet::storage_prefix = "Class"] - /// Details of a collection. pub(super) type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, @@ -210,14 +186,14 @@ pub mod pallet { CollectionDetails>, >; - #[pallet::storage] /// The collection, if any, of which an account is willing to take ownership. + #[pallet::storage] pub(super) type OwnershipAcceptance, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; - #[pallet::storage] /// The items held by any given account; set out this way so that items owned by a single /// account can be enumerated. + #[pallet::storage] pub(super) type Account, I: 'static = ()> = StorageNMap< _, ( @@ -229,10 +205,10 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] - #[pallet::storage_prefix = "ClassAccount"] /// The collections owned by any given account; set out this way so that collections owned by /// a single account can be enumerated. + #[pallet::storage] + #[pallet::storage_prefix = "ClassAccount"] pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -243,9 +219,9 @@ pub mod pallet { OptionQuery, >; + /// The items in existence and their ownership details. #[pallet::storage] #[pallet::storage_prefix = "Asset"] - /// The items in existence and their ownership details. pub(super) type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -256,9 +232,9 @@ pub mod pallet { OptionQuery, >; + /// Metadata of a collection. #[pallet::storage] #[pallet::storage_prefix = "ClassMetadataOf"] - /// Metadata of a collection. pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, @@ -267,9 +243,9 @@ pub mod pallet { OptionQuery, >; + /// Metadata of an item. #[pallet::storage] #[pallet::storage_prefix = "InstanceMetadataOf"] - /// Metadata of an item. pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -280,8 +256,8 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] /// Attributes of a collection. + #[pallet::storage] pub(super) type Attribute, I: 'static = ()> = StorageNMap< _, ( @@ -293,8 +269,8 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] /// Price of an asset instance. + #[pallet::storage] pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -305,19 +281,19 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] /// Keeps track of the number of items a collection might have. + #[pallet::storage] pub(super) type CollectionMaxSupply, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; - #[pallet::storage] /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented by 1 whenever a new collection is created. + #[pallet::storage] pub(super) type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; - #[pallet::storage] /// Handles all the pending swaps. + #[pallet::storage] pub(super) type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -333,6 +309,23 @@ pub mod pallet { OptionQuery, >; + /// Config of a collection. + #[pallet::storage] + pub(super) type CollectionConfigOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig, OptionQuery>; + + /// Config of an item. + #[pallet::storage] + pub(super) type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemConfig, + OptionQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -353,14 +346,19 @@ pub mod pallet { }, /// An `item` was destroyed. Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, - /// Some `item` was frozen. - Frozen { collection: T::CollectionId, item: T::ItemId }, - /// Some `item` was thawed. - Thawed { collection: T::CollectionId, item: T::ItemId }, - /// Some `collection` was frozen. - CollectionFrozen { collection: T::CollectionId }, - /// Some `collection` was thawed. - CollectionThawed { collection: T::CollectionId }, + /// An `item` became non-transferable. + ItemTransferLocked { collection: T::CollectionId, item: T::ItemId }, + /// An `item` became transferable. + ItemTransferUnlocked { collection: T::CollectionId, item: T::ItemId }, + /// `item` metadata or attributes were locked. + ItemPropertiesLocked { + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + }, + /// Some `collection` was locked. + CollectionLocked { collection: T::CollectionId }, /// The owner changed. OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, /// The management team changed. @@ -390,13 +388,9 @@ pub mod pallet { /// All approvals of an item got cancelled. AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// A `collection` has had its attributes changed by the `Force` origin. - ItemStatusChanged { collection: T::CollectionId }, + CollectionStatusChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. - CollectionMetadataSet { - collection: T::CollectionId, - data: BoundedVec, - is_frozen: bool, - }, + CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, /// Metadata has been cleared for a `collection`. CollectionMetadataCleared { collection: T::CollectionId }, /// New metadata has been set for an item. @@ -404,7 +398,6 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, - is_frozen: bool, }, /// Metadata has been cleared for an item. MetadataCleared { collection: T::CollectionId, item: T::ItemId }, @@ -429,6 +422,8 @@ pub mod pallet { CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, /// Event gets emmited when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, + /// The config of a collection has change. + CollectionConfigChanged { id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -497,22 +492,30 @@ pub mod pallet { ApprovalExpired, /// The owner turned out to be different to what was expected. WrongOwner, - /// Invalid witness data given. + /// The witness data given does not match the current state of the chain. BadWitness, - /// The item ID is already taken. - InUse, - /// The item or collection is frozen. - Frozen, + /// Collection ID is already taken. + CollectionIdInUse, + /// Items within that collection are non-transferable. + ItemsNotTransferable, /// The provided account is not a delegate. NotDelegate, /// The delegate turned out to be different to what was expected. WrongDelegate, /// No approval exists that would allow the transfer. Unapproved, - /// The named owner has not signed ownership of the collection is acceptable. + /// The named owner has not signed ownership acceptance of the collection. Unaccepted, - /// The item is locked. - Locked, + /// The item is locked (non-transferable). + ItemLocked, + /// Item's attributes are locked. + LockedItemAttributes, + /// Collection's attributes are locked. + LockedCollectionAttributes, + /// Item's metadata is locked. + LockedItemMetadata, + /// Collection's metadata is locked. + LockedCollectionMetadata, /// All items have been minted. MaxSupplyReached, /// The max supply has already been set. @@ -533,6 +536,14 @@ pub mod pallet { DeadlineExpired, /// The duration provided should be less or equal to MaxDeadlineDuration. WrongDuration, + /// The method is disabled by system settings. + MethodDisabled, + /// The provided is setting can't be set. + WrongSetting, + /// Item's config already exists and should be equal to the provided one. + InconsistentItemConfig, + /// Config for a collection or an item can't be found. + NoConfig, } impl, I: 'static> Pallet { @@ -565,19 +576,29 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::create())] - pub fn create(origin: OriginFor, admin: AccountIdLookupOf) -> DispatchResult { + pub fn create( + origin: OriginFor, + admin: AccountIdLookupOf, + config: CollectionConfig, + ) -> DispatchResult { let collection = NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); + Self::do_create_collection( collection, owner.clone(), admin.clone(), + config, T::CollectionDeposit::get(), - false, Event::Created { collection, creator: owner, owner: admin }, ) } @@ -602,7 +623,7 @@ pub mod pallet { pub fn force_create( origin: OriginFor, owner: AccountIdLookupOf, - free_holding: bool, + config: CollectionConfig, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; @@ -614,8 +635,8 @@ pub mod pallet { collection, owner.clone(), owner.clone(), + config, Zero::zero(), - free_holding, Event::ForceCreated { collection, owner }, ) } @@ -676,11 +697,12 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, owner: AccountIdLookupOf, + config: ItemConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, |collection_details| { + Self::do_mint(collection, item, owner, config, |collection_details| { ensure!(collection_details.issuer == origin, Error::::NoPermission); Ok(()) }) @@ -758,7 +780,7 @@ pub mod pallet { }) } - /// Reevaluate the deposits on some items. + /// Re-evaluate the deposits on some items. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// @@ -786,9 +808,11 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); - let deposit = match collection_details.free_holding { - true => Zero::zero(), - false => T::ItemDeposit::get(), + + let config = Self::get_collection_config(&collection)?; + let deposit = match config.is_setting_enabled(CollectionSetting::DepositRequired) { + true => T::ItemDeposit::get(), + false => Zero::zero(), }; let mut successful = Vec::with_capacity(items.len()); @@ -829,116 +853,61 @@ pub mod pallet { /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `collection`: The collection of the item to be frozen. - /// - `item`: The item of the item to be frozen. + /// - `collection`: The collection of the item to be changed. + /// - `item`: The item to become non-transferable. /// - /// Emits `Frozen`. + /// Emits `ItemTransferLocked`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze())] - pub fn freeze( + #[pallet::weight(T::WeightInfo::lock_item_transfer())] + pub fn lock_item_transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - details.is_frozen = true; - Item::::insert(&collection, &item, &details); - - Self::deposit_event(Event::::Frozen { collection, item }); - Ok(()) + Self::do_lock_item_transfer(origin, collection, item) } /// Re-allow unprivileged transfer of an item. /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `collection`: The collection of the item to be thawed. - /// - `item`: The item of the item to be thawed. + /// - `collection`: The collection of the item to be changed. + /// - `item`: The item to become transferable. /// - /// Emits `Thawed`. + /// Emits `ItemTransferUnlocked`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw())] - pub fn thaw( + #[pallet::weight(T::WeightInfo::unlock_item_transfer())] + pub fn unlock_item_transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.admin == origin, Error::::NoPermission); - - details.is_frozen = false; - Item::::insert(&collection, &item, &details); - - Self::deposit_event(Event::::Thawed { collection, item }); - Ok(()) + Self::do_unlock_item_transfer(origin, collection, item) } - /// Disallow further unprivileged transfers for a whole collection. + /// Disallows specified settings for the whole collection. /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `collection`: The collection to be frozen. - /// - /// Emits `CollectionFrozen`. + /// - `collection`: The collection to be locked. + /// - `lock_config`: The config with the settings to be locked. /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze_collection())] - pub fn freeze_collection( - origin: OriginFor, - collection: T::CollectionId, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.freezer, Error::::NoPermission); - - details.is_frozen = true; - - Self::deposit_event(Event::::CollectionFrozen { collection }); - Ok(()) - }) - } - - /// Re-allow unprivileged transfers for a whole collection. - /// - /// Origin must be Signed and the sender should be the Admin of the `collection`. - /// - /// - `collection`: The collection to be thawed. - /// - /// Emits `CollectionThawed`. + /// Note: it's possible to only lock(set) the setting, but not to unset it. + /// Emits `CollectionLocked`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw_collection())] - pub fn thaw_collection( + #[pallet::weight(T::WeightInfo::lock_collection())] + pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, + lock_config: CollectionConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.admin, Error::::NoPermission); - - details.is_frozen = false; - - Self::deposit_event(Event::::CollectionThawed { collection }); - Ok(()) - }) + Self::do_lock_collection(origin, collection, lock_config) } /// Change the Owner of a collection. @@ -1047,6 +1016,10 @@ pub mod pallet { delegate: AccountIdLookupOf, maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1058,6 +1031,12 @@ pub mod pallet { let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNotTransferable + ); + if let Some(check) = maybe_check { let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); @@ -1189,54 +1168,85 @@ pub mod pallet { Ok(()) } - /// Alter the attributes of a given item. + /// Alter the attributes of a given collection. /// /// Origin must be `ForceOrigin`. /// - /// - `collection`: The identifier of the item. - /// - `owner`: The new Owner of this item. - /// - `issuer`: The new Issuer of this item. - /// - `admin`: The new Admin of this item. - /// - `freezer`: The new Freezer of this item. - /// - `free_holding`: Whether a deposit is taken for holding an item of this collection. - /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin - /// instructions. + /// - `collection`: The identifier of the collection. + /// - `owner`: The new Owner of this collection. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. + /// - `config`: Collection's config. /// - /// Emits `ItemStatusChanged` with the identity of the item. + /// Emits `CollectionStatusChanged` with the identity of the item. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_item_status())] - pub fn force_item_status( + #[pallet::weight(T::WeightInfo::force_collection_status())] + pub fn force_collection_status( origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, issuer: AccountIdLookupOf, admin: AccountIdLookupOf, freezer: AccountIdLookupOf, - free_holding: bool, - is_frozen: bool, + config: CollectionConfig, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Collection::::try_mutate(collection, |maybe_item| { - let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; - let old_owner = item.owner; + Collection::::try_mutate(collection, |maybe_collection| { + let mut collection_info = + maybe_collection.take().ok_or(Error::::UnknownCollection)?; + let old_owner = collection_info.owner; let new_owner = T::Lookup::lookup(owner)?; - item.owner = new_owner.clone(); - item.issuer = T::Lookup::lookup(issuer)?; - item.admin = T::Lookup::lookup(admin)?; - item.freezer = T::Lookup::lookup(freezer)?; - item.free_holding = free_holding; - item.is_frozen = is_frozen; - *maybe_item = Some(item); + collection_info.owner = new_owner.clone(); + collection_info.issuer = T::Lookup::lookup(issuer)?; + collection_info.admin = T::Lookup::lookup(admin)?; + collection_info.freezer = T::Lookup::lookup(freezer)?; + *maybe_collection = Some(collection_info); CollectionAccount::::remove(&old_owner, &collection); CollectionAccount::::insert(&new_owner, &collection, ()); + CollectionConfigOf::::insert(&collection, config); - Self::deposit_event(Event::ItemStatusChanged { collection }); + Self::deposit_event(Event::CollectionStatusChanged { collection }); Ok(()) }) } + /// Disallows changing the metadata of attributes of the item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// - `collection`: The collection if the `item`. + /// - `item`: An item to be locked. + /// - `lock_config`: The config with the settings to be locked. + /// + /// Note: when the metadata or attributes are locked, it won't be possible the unlock them. + /// Emits `ItemPropertiesLocked`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::lock_item_properties())] + pub fn lock_item_properties( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + Self::do_lock_item_properties( + maybe_check_owner, + collection, + item, + lock_metadata, + lock_attributes, + ) + } + /// Set an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the @@ -1262,20 +1272,35 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + + let collection_config = Self::get_collection_config(&collection)?; + match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::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::::LockedItemAttributes); + }, }; - ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); let attribute = Attribute::::get((collection, maybe_item, &key)); if attribute.is_none() { @@ -1284,7 +1309,9 @@ pub mod pallet { let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !collection_details.free_holding && maybe_check_owner.is_some() { + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); @@ -1332,11 +1359,28 @@ pub mod pallet { if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), - }; - ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); + + if maybe_check_owner.is_some() { + match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config + .is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemSettings record might + // not exists. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| { + c.has_disabled_setting(ItemSetting::UnlockedAttributes) + }); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + } if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); @@ -1360,7 +1404,6 @@ pub mod pallet { /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `item`: The identifier of the item whose metadata to set. /// - `data`: The general information of this item. Limited in length by `StringLimit`. - /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `MetadataSet`. /// @@ -1371,7 +1414,6 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, - is_frozen: bool, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1380,21 +1422,29 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + maybe_check_owner.is_none() || + item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + let collection_config = Self::get_collection_config(&collection)?; + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { if metadata.is_none() { collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !collection_details.free_holding && maybe_check_owner.is_some() { + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); @@ -1406,10 +1456,10 @@ pub mod pallet { } collection_details.total_deposit.saturating_accrue(deposit); - *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen }); + Self::deposit_event(Event::MetadataSet { collection, item, data }); Ok(()) }) } @@ -1417,7 +1467,7 @@ pub mod pallet { /// Clear the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// `item`. + /// `collection`. /// /// Any deposit is freed for the collection's owner. /// @@ -1443,14 +1493,17 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + // NOTE: if the item was previously burned, the ItemSettings record might not exists + let is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); + + ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { if metadata.is_some() { collection_details.item_metadatas.saturating_dec(); } - let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; T::Currency::unreserve(&collection_details.owner, deposit); collection_details.total_deposit.saturating_reduce(deposit); @@ -1471,7 +1524,6 @@ pub mod pallet { /// /// - `collection`: The identifier of the item whose metadata to update. /// - `data`: The general information of this item. Limited in length by `StringLimit`. - /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `CollectionMetadataSet`. /// @@ -1481,12 +1533,18 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, data: BoundedVec, - is_frozen: bool, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { @@ -1494,13 +1552,12 @@ pub mod pallet { } CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && !details.free_holding { + if maybe_check_owner.is_some() && + collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); @@ -1514,9 +1571,9 @@ pub mod pallet { Collection::::insert(&collection, details); - *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); - Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen }); + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); Ok(()) }) } @@ -1548,10 +1605,14 @@ pub mod pallet { ensure!(check_owner == &details.owner, Error::::NoPermission); } - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; T::Currency::unreserve(&details.owner, deposit); Self::deposit_event(Event::CollectionMetadataCleared { collection }); diff --git a/frame/nfts/src/macros.rs b/frame/nfts/src/macros.rs new file mode 100644 index 0000000000000..07a8f3b9f9556 --- /dev/null +++ b/frame/nfts/src/macros.rs @@ -0,0 +1,74 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +macro_rules! impl_incrementable { + ($($type:ty),+) => { + $( + impl Incrementable for $type { + fn increment(&self) -> Self { + let mut val = self.clone(); + val.saturating_inc(); + val + } + + fn initial_value() -> Self { + 0 + } + } + )+ + }; +} +pub(crate) use impl_incrementable; + +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>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl EncodeLike for $wrapper {} + impl Decode for $wrapper { + fn decode( + input: &mut I, + ) -> sp_std::result::Result { + 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; diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 23493829eaca7..bbd1625710500 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_nfts; use frame_support::{ - construct_runtime, + construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; use sp_core::H256; @@ -84,6 +84,10 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type CollectionId = u32; @@ -103,6 +107,7 @@ impl Config for Test { type ApprovalsLimit = ConstU32<10>; type MaxTips = ConstU32<10>; type MaxDeadlineDuration = ConstU64<10000>; + type Features = Features; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 0d2d0c661b273..1b60fd6431b19 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -92,6 +92,14 @@ fn events() -> Vec> { result } +fn default_collection_config() -> CollectionConfig { + CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) +} + +fn default_item_config() -> ItemConfig { + ItemConfig::all_settings_enabled() +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -102,14 +110,14 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -118,25 +126,29 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + CollectionConfig::all_settings_enabled() + )); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0])); assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69])); assert_eq!(Balances::reserved_balance(&1), 13); assert!(ItemMetadataOf::::contains_key(0, 69)); @@ -147,6 +159,7 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Collection::::contains_key(0)); + assert!(!CollectionConfigOf::::contains_key(0)); assert!(!Item::::contains_key(0, 42)); assert!(!Item::::contains_key(0, 69)); assert!(!CollectionMetadataOf::::contains_key(0)); @@ -161,10 +174,14 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + CollectionConfig::all_settings_enabled() + )); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -172,8 +189,8 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -183,8 +200,8 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); @@ -195,22 +212,54 @@ fn transfer_should_work() { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(3), 0, 42, 2, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4)); + + // validate we can't transfer non-transferable items + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::disable_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), + Error::::ItemsNotTransferable + ); }); } #[test] -fn freezing_should_work() { +fn locking_transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); - assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); - assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(1), 0)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), + Error::::ItemsNotTransferable + ); - assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(1), 0)); + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + CollectionConfig::all_settings_enabled(), + )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); } @@ -218,8 +267,8 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -231,9 +280,18 @@ fn origin_guards_should_work() { Nfts::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), Error::::NoPermission ); - assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2), Error::::NoPermission); + assert_noop!( + Nfts::lock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::unlock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, default_item_config()), + Error::::NoPermission + ); assert_noop!( Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission @@ -249,7 +307,11 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1)); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + CollectionConfig::all_settings_enabled() + )); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), @@ -271,14 +333,9 @@ fn transfer_owner_should_work() { ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(2), - 0, - bvec![0u8; 20], - false - )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); @@ -299,12 +356,12 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); - assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); - assert_ok!(Nfts::thaw(RuntimeOrigin::signed(3), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 42, None)); }); @@ -315,70 +372,59 @@ fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 20], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20])); assert_eq!(Balances::free_balance(&1), 9); assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 15], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 25], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 25])); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_collection_metadata( + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); + assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - bvec![0u8; 15], - true + CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()) )); assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15], false), - Error::::Frozen, + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), + Error::::LockedCollectionMetadata, ); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), - Error::::Frozen + Error::::LockedCollectionMetadata ); // Clear Metadata - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15])); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(2), 0), Error::::NoPermission @@ -387,7 +433,11 @@ fn set_collection_metadata_should_work() { Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 1), Error::::UnknownCollection ); - assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0)); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::LockedCollectionMetadata + ); + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); assert!(!CollectionMetadataOf::::contains_key(0)); }); } @@ -398,53 +448,61 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false), + Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20])); assert_eq!(Balances::free_balance(&1), 8); assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. - assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25])); assert_eq!(Balances::free_balance(&1), 3); // Cannot over-reserve assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40], false), + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 42, true, false)); + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15]), + Error::::LockedItemMetadata, + ); assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false), - Error::::Frozen, + Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), + Error::::LockedItemMetadata, ); - assert_noop!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), Error::::Frozen); // Clear Metadata - assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); assert_noop!( Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), - Error::::NoPermission + Error::::NoPermission, ); assert_noop!( Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), - Error::::UnknownCollection + Error::::UnknownCollection, ); - assert_ok!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); }); } @@ -454,7 +512,12 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -467,7 +530,7 @@ fn set_attribute_should_work() { (Some(0), bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(Balances::reserved_balance(1), 10); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0; 10])); assert_eq!( @@ -478,14 +541,14 @@ fn set_attribute_should_work() { (Some(0), bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 18); + assert_eq!(Balances::reserved_balance(1), 19); assert_ok!(Nfts::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] ); - assert_eq!(Balances::reserved_balance(1), 15); + assert_eq!(Balances::reserved_balance(1), 16); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); @@ -495,11 +558,17 @@ fn set_attribute_should_work() { } #[test] -fn set_attribute_should_respect_freeze() { +fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -512,15 +581,21 @@ fn set_attribute_should_respect_freeze() { (Some(1), bvec![0], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(Balances::reserved_balance(1), 11); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![], true)); - let e = Error::::Frozen; + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![])); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionConfig::disable_settings(CollectionSetting::UnlockedAttributes.into()) + )); + + let e = Error::::LockedCollectionAttributes; assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 0, bvec![], true)); - let e = Error::::Frozen; + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); + let e = Error::::LockedItemAttributes; assert_noop!( Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), e @@ -530,36 +605,87 @@ fn set_attribute_should_respect_freeze() { } #[test] -fn force_item_status_should_work() { +fn preserve_config_for_frozen_items() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + + // if the item is not locked/frozen then the config gets deleted on item burn + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); + assert!(!ItemConfigOf::::contains_key(0, 1)); + + // lock the item and ensure the config stays unchanged + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, true, true)); + + let expect_config = + ItemConfig(ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 0, Some(1))); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + // can't mint with the different config + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Error::::InconsistentItemConfig + ); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); + }); +} + +#[test] +fn force_collection_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Nfts::force_item_status(RuntimeOrigin::root(), 0, 1, 1, 1, 1, true, false)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20], false)); + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()), + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); assert_eq!(Balances::reserved_balance(1), 63); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 42); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -568,7 +694,11 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, false)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::all_settings_enabled() + )); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -576,8 +706,8 @@ fn burn_works() { Error::::UnknownCollection ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); assert_eq!(Balances::reserved_balance(1), 2); assert_noop!( @@ -598,8 +728,8 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_noop!( @@ -610,14 +740,37 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 2, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 2)); + + // ensure we can't buy an item when the collection has a NonTransferableItems flag + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::disable_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(1), + 1, + collection_id, + 1, + default_item_config() + )); + + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), + Error::::ItemsNotTransferable + ); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -645,7 +798,7 @@ fn cancel_approval_works() { let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); // approval expires after 2 blocks. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( @@ -663,8 +816,8 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); let current_block = 1; System::set_block_number(current_block); @@ -688,8 +841,8 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); for i in 3..13 { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); @@ -708,8 +861,12 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); // the approval expires after the 2nd block. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); @@ -735,8 +892,8 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -763,8 +920,8 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -791,8 +948,8 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, true)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); @@ -830,7 +987,7 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( @@ -855,10 +1012,28 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 0, + user_id, + default_item_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 1, + user_id, + default_item_config() + )); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), + Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 2, + user_id, + default_item_config() + ), Error::::MaxSupplyReached ); @@ -880,10 +1055,22 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + default_item_config() + )); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -929,6 +1116,29 @@ fn set_price_should_work() { item: item_2 })); assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // ensure we can't set price when the items are non-transferable + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig::disable_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config() + )); + + assert_noop!( + Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), + Error::::ItemsNotTransferable + ); }); } @@ -950,11 +1160,29 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + user_1, + default_item_config(), + )); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1024,7 +1252,7 @@ fn buy_item_should_work() { Error::::NotForSale ); - // ensure we can't buy an item when the collection or an item is frozen + // ensure we can't buy an item when the collection or an item are frozen { assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1034,8 +1262,12 @@ fn buy_item_should_work() { None, )); - // freeze collection - assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(user_1), collection_id)); + // lock the collection + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_1), + collection_id, + CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, @@ -1044,13 +1276,26 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::Frozen + Error::::ItemsNotTransferable ); - assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(user_1), collection_id)); + // unlock the collection + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + collection_id, + user_1, + user_1, + user_1, + user_1, + CollectionConfig::all_settings_enabled(), + )); - // freeze item - assert_ok!(Nfts::freeze(RuntimeOrigin::signed(user_1), collection_id, item_3)); + // lock the transfer + assert_ok!(Nfts::lock_item_transfer( + RuntimeOrigin::signed(user_1), + collection_id, + item_3 + )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, @@ -1059,7 +1304,7 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::Frozen + Error::::ItemLocked ); } }); @@ -1124,10 +1369,22 @@ fn create_cancel_swap_should_work() { let duration = 2; let expect_deadline = 3; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + default_item_config(), + )); // validate desired item and the collection exists assert_noop!( @@ -1262,13 +1519,43 @@ fn claim_swap_should_work() { Balances::make_free_balance_be(&user_1, initial_balance); Balances::make_free_balance_be(&user_2, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_2)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_2)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_4, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_5, user_2)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_4, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_5, + user_2, + default_item_config(), + )); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1), @@ -1418,3 +1705,122 @@ fn claim_swap_should_work() { assert_eq!(Balances::total_balance(&user_2), initial_balance + price); }); } + +#[test] +fn various_collection_settings() { + new_test_ext().execute_with(|| { + // when we set only one value it's required to call .into() on it + let config = + CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + + let config = CollectionConfigOf::::get(0).unwrap(); + assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); + assert!(config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); + + // no need to call .into() for multiple values + let config = CollectionConfig::disable_settings( + CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, + ); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + + let config = CollectionConfigOf::::get(1).unwrap(); + assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); + assert!(!config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + }); +} + +#[test] +fn collection_locking_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig::all_settings_enabled() + )); + + // validate partial lock + let lock_config = CollectionConfig::disable_settings( + CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config, + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, lock_config); + + // validate full lock + let full_lock_config = CollectionConfig::disable_settings( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()), + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, full_lock_config); + }); +} + +#[test] +fn pallet_level_feature_flags_should_work() { + new_test_ext().execute_with(|| { + Features::set(&PalletFeatures::disable( + PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, + )); + + let user_id = 1; + let collection_id = 0; + let item_id = 1; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + default_item_config(), + )); + + // PalletFeature::Trading + assert_noop!( + Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_id, Some(1), None), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_id), collection_id, item_id, 1), + Error::::MethodDisabled + ); + + // PalletFeature::Approvals + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), + Error::::MethodDisabled + ); + + // PalletFeature::Attributes + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(user_id), + collection_id, + None, + bvec![0], + bvec![0] + ), + Error::::MethodDisabled + ); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 399de3c5dad1e..6ed57e4da25e5 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -18,16 +18,24 @@ //! Various basic types for use in the Nfts pallet. use super::*; +use crate::macros::*; +use codec::EncodeLike; +use enumflags2::{bitflags, BitFlags}; use frame_support::{ pallet_prelude::{BoundedVec, MaxEncodedLen}, traits::Get, }; -use scale_info::TypeInfo; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; pub(super) type CollectionDetailsFor = CollectionDetails<::AccountId, DepositBalanceOf>; +pub(super) type ApprovalsOf = BoundedBTreeMap< + ::AccountId, + Option<::BlockNumber>, + >::ApprovalsLimit, +>; pub(super) type ItemDetailsFor = ItemDetails<::AccountId, DepositBalanceOf, ApprovalsOf>; pub(super) type BalanceOf = @@ -40,6 +48,12 @@ pub(super) type ItemTipOf = ItemTip< BalanceOf, >; +pub trait Incrementable { + fn increment(&self) -> Self; + fn initial_value() -> Self; +} +impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. @@ -53,16 +67,12 @@ pub struct CollectionDetails { /// The total balance deposited for the all storage associated with this collection. /// Used by `destroy`. pub(super) total_deposit: DepositBalance, - /// If `true`, then no deposit is needed to hold items of this collection. - pub(super) free_holding: bool, /// The total number of outstanding items of this collection. pub(super) items: u32, /// The total number of outstanding item metadata of this collection. pub(super) item_metadatas: u32, /// The total number of attributes for this collection. pub(super) attributes: u32, - /// Whether the collection is frozen for non-admin transfers. - pub(super) is_frozen: bool, } /// Witness data for the destroy transactions. @@ -96,8 +106,6 @@ pub struct ItemDetails { pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. pub(super) approvals: Approvals, - /// Whether the item can be transferred or not. - pub(super) is_frozen: bool, /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. pub(super) deposit: DepositBalance, @@ -115,8 +123,6 @@ pub struct CollectionMetadata> { /// 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(super) data: BoundedVec, - /// Whether the collection's metadata may be changed by a non Force origin. - pub(super) is_frozen: bool, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] @@ -131,8 +137,6 @@ pub struct ItemMetadata> { /// 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(super) data: BoundedVec, - /// Whether the item metadata may be changed by a non Force origin. - pub(super) is_frozen: bool, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -172,3 +176,128 @@ pub struct PriceWithDirection { /// A direction (send or receive). pub(super) 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, + /// When this isn't set then the deposit is required to hold the items of this collection. + DepositRequired, +} +pub(super) type CollectionSettings = BitFlags; + +/// Wrapper type for `CollectionSettings` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct CollectionConfig(pub CollectionSettings); + +impl CollectionConfig { + pub fn all_settings_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn get_disabled_settings(&self) -> CollectionSettings { + self.0 + } + pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { + !self.get_disabled_settings().contains(setting) + } + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { + self.get_disabled_settings().contains(setting) + } + pub fn disable_settings(settings: CollectionSettings) -> Self { + Self(settings) + } + pub fn enable_setting(&mut self, setting: CollectionSetting) { + self.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: CollectionSetting) { + self.0.insert(setting); + } +} +impl_codec_bitflags!(CollectionConfig, u64, CollectionSetting); + +/// 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, +} +pub(super) type ItemSettings = BitFlags; + +/// Wrapper type for `ItemSettings` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct ItemConfig(pub ItemSettings); + +impl ItemConfig { + pub fn all_settings_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn get_disabled_settings(&self) -> ItemSettings { + self.0 + } + pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { + !self.get_disabled_settings().contains(setting) + } + pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { + self.get_disabled_settings().contains(setting) + } + pub fn has_disabled_settings(&self) -> bool { + !self.get_disabled_settings().is_empty() + } + pub fn disable_settings(settings: ItemSettings) -> Self { + Self(settings) + } + pub fn enable_setting(&mut self, setting: ItemSetting) { + self.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: ItemSetting) { + self.0.insert(setting); + } +} +impl_codec_bitflags!(ItemConfig, u64, ItemSetting); + +/// 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, + /// Allow/disallow public mints. + PublicMints, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Default, RuntimeDebug)] +pub struct PalletFeatures(pub BitFlags); + +impl PalletFeatures { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn disable(features: BitFlags) -> Self { + Self(features) + } + pub fn is_enabled(&self, feature: PalletFeature) -> bool { + !self.0.contains(feature) + } +} +impl_codec_bitflags!(PalletFeatures, u64, PalletFeature); diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 9d62db0f8d85d..5f6ee43a09ffe 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -53,13 +53,13 @@ pub trait WeightInfo { fn burn() -> Weight; fn transfer() -> Weight; fn redeposit(i: u32, ) -> Weight; - fn freeze() -> Weight; - fn thaw() -> Weight; - fn freeze_collection() -> Weight; - fn thaw_collection() -> Weight; + fn lock_item_transfer() -> Weight; + fn unlock_item_transfer() -> Weight; + fn lock_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_item_status() -> Weight; + fn force_collection_status() -> Weight; + fn lock_item_properties() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; fn set_metadata() -> Weight; @@ -85,231 +85,256 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn create() -> Weight { Weight::from_ref_time(38_062_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_create() -> Weight { Weight::from_ref_time(25_917_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:1000) // Storage: Nfts ClassMetadataOf (r:0 w:1) - // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:20) + // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_432_555_000 as u64) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(8_474_465 as u64).saturating_mul(n as u64)) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(333_758 as u64).saturating_mul(m as u64)) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(222_052 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(55_419_000 as u64) + // Standard Error: 18_623 + .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(2004 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + .saturating_add(T::DbWeight::get().writes((5 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_755_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(46_768_000 as u64) + Weight::from_ref_time(47_193_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(36_282_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + Weight::from_ref_time(42_305_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(23_359_000 as u64) - // Standard Error: 9_645 - .saturating_add(Weight::from_ref_time(10_822_144 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + Weight::from_ref_time(26_327_000 as u64) + // Standard Error: 10_090 + .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(T::DbWeight::get().writes(1 as u64)) .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) - fn freeze() -> Weight { - Weight::from_ref_time(27_805_000 as u64) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_transfer() -> Weight { + Weight::from_ref_time(28_194_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) - fn thaw() -> Weight { - Weight::from_ref_time(27_712_000 as u64) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn unlock_item_transfer() -> Weight { + Weight::from_ref_time(28_821_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) - fn freeze_collection() -> Weight { - Weight::from_ref_time(23_068_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - fn thaw_collection() -> Weight { - Weight::from_ref_time(23_200_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn lock_collection() -> Weight { + Weight::from_ref_time(25_896_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_800_000 as u64) + Weight::from_ref_time(32_728_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_959_000 as u64) + Weight::from_ref_time(24_805_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) - fn force_item_status() -> Weight { - Weight::from_ref_time(26_334_000 as u64) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_status() -> Weight { + Weight::from_ref_time(28_468_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_properties() -> Weight { + Weight::from_ref_time(27_377_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_978_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(49_555_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(41_099_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(48_054_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_893_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(46_590_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_785_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(44_281_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(39_764_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_355_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_577_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(33_170_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_696_000 as u64) + Weight::from_ref_time(31_121_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_692_000 as u64) + Weight::from_ref_time(30_133_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_345_000 as u64) + Weight::from_ref_time(26_421_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_826_000 as u64) + Weight::from_ref_time(26_358_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(26_376_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + Weight::from_ref_time(33_607_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(49_140_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + Weight::from_ref_time(54_511_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_477_000 as u64) - // Standard Error: 33_188 - .saturating_add(Weight::from_ref_time(4_285_339 as u64).saturating_mul(n as u64)) + Weight::from_ref_time(6_015_000 as u64) + // Standard Error: 34_307 + .saturating_add(Weight::from_ref_time(4_308_600 as u64).saturating_mul(n as u64)) } // Storage: Nfts Asset (r:2 w:0) // Storage: Nfts PendingSwapOf (r:0 w:1) @@ -342,225 +367,250 @@ impl WeightInfo for () { // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(38_062_000 as u64) + Weight::from_ref_time(39_252_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_917_000 as u64) + Weight::from_ref_time(27_479_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:1000) // Storage: Nfts ClassMetadataOf (r:0 w:1) - // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:20) + // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_432_555_000 as u64) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(8_474_465 as u64).saturating_mul(n as u64)) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(333_758 as u64).saturating_mul(m as u64)) - // Standard Error: 28_964 - .saturating_add(Weight::from_ref_time(222_052 as u64).saturating_mul(a as u64)) + Weight::from_ref_time(55_419_000 as u64) + // Standard Error: 18_623 + .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(2004 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + .saturating_add(RocksDbWeight::get().writes((5 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_755_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(46_768_000 as u64) + Weight::from_ref_time(47_193_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(36_282_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + Weight::from_ref_time(42_305_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(23_359_000 as u64) - // Standard Error: 9_645 - .saturating_add(Weight::from_ref_time(10_822_144 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + Weight::from_ref_time(26_327_000 as u64) + // Standard Error: 10_090 + .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(RocksDbWeight::get().writes(1 as u64)) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) - fn freeze() -> Weight { - Weight::from_ref_time(27_805_000 as u64) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_transfer() -> Weight { + Weight::from_ref_time(28_194_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) - fn thaw() -> Weight { - Weight::from_ref_time(27_712_000 as u64) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn unlock_item_transfer() -> Weight { + Weight::from_ref_time(28_821_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) - fn freeze_collection() -> Weight { - Weight::from_ref_time(23_068_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - fn thaw_collection() -> Weight { - Weight::from_ref_time(23_200_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn lock_collection() -> Weight { + Weight::from_ref_time(25_896_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_800_000 as u64) + Weight::from_ref_time(32_728_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_959_000 as u64) + Weight::from_ref_time(24_805_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) - fn force_item_status() -> Weight { - Weight::from_ref_time(26_334_000 as u64) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_status() -> Weight { + Weight::from_ref_time(28_468_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_properties() -> Weight { + Weight::from_ref_time(27_377_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_978_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(49_555_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(41_099_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(48_054_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_893_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(46_590_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_785_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(44_281_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(39_764_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_355_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_577_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(33_170_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_696_000 as u64) + Weight::from_ref_time(31_121_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_692_000 as u64) + Weight::from_ref_time(30_133_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_345_000 as u64) + Weight::from_ref_time(26_421_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_826_000 as u64) + Weight::from_ref_time(26_358_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(26_376_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + Weight::from_ref_time(33_607_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(49_140_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + Weight::from_ref_time(54_511_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 77eb83adfbfb0..b3b3b4b7d90b1 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -23,7 +23,9 @@ pub mod fungibles; pub mod imbalance; mod misc; pub mod nonfungible; +pub mod nonfungible_v2; pub mod nonfungibles; +pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs new file mode 100644 index 0000000000000..850195852cf72 --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -0,0 +1,204 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for dealing with a single non-fungible collection of items. +//! +//! This assumes a single level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets which wants to expose a single collection of NFT-like +//! objects. +//! +//! For an NFT API which has dual-level namespacing, the traits in `nonfungibles` are better to +//! use. + +use super::nonfungibles_v2 as nonfungibles; +use crate::{dispatch::DispatchResult, traits::Get}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to a read-only NFT-like set of items. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Returns the owner of `item`, or `None` if the item doesn't exist or has no + /// owner. + fn owner(item: &Self::ItemId) -> Option; + + /// Returns the attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns `true` if the `item` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(_item: &Self::ItemId) -> bool { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over a collection +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// Returns an iterator of the items within a `collection` in existence. + fn items() -> Box>; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; +} + +/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have +/// attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item`. + /// + /// By default, this is not a supported operation. + fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) + } +} + +/// Trait for providing a non-fungible set of items which can only be transferred. +pub trait Transfer: Inspect { + /// Transfer `item` into `destination` account. + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; +} + +/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying +/// a single item. +pub struct ItemOf< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, +>(sp_std::marker::PhantomData<(F, A, AccountId)>); + +impl< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, + > Inspect for ItemOf +{ + type ItemId = >::ItemId; + fn owner(item: &Self::ItemId) -> Option { + >::owner(&A::get(), item) + } + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) + } + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) + } + fn can_transfer(item: &Self::ItemId) -> bool { + >::can_transfer(&A::get(), item) + } +} + +impl< + F: nonfungibles::InspectEnumerable, + A: Get<>::CollectionId>, + AccountId, + > InspectEnumerable for ItemOf +{ + fn items() -> Box> { + >::items(&A::get()) + } + fn owned(who: &AccountId) -> Box> { + >::owned_in_collection(&A::get(), who) + } +} + +impl< + F: nonfungibles::Mutate, + A: Get<>::CollectionId>, + AccountId, + ItemConfig, + > Mutate for ItemOf +{ + fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { + >::mint_into(&A::get(), item, who, config) + } + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), item, maybe_check_owner) + } + fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { + >::set_attribute( + &A::get(), + item, + key, + value, + ) + } + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + >::set_typed_attribute( + &A::get(), + item, + key, + value, + ) + } +} + +impl< + F: nonfungibles::Transfer, + A: Get<>::CollectionId>, + AccountId, + > Transfer for ItemOf +{ + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { + >::transfer(&A::get(), item, destination) + } +} diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs new file mode 100644 index 0000000000000..d23e6d67573c7 --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for dealing with multiple collections of non-fungible items. +//! +//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets which want to expose multiple independent collections of +//! NFT-like objects. +//! +//! For an NFT API which has single-level namespacing, the traits in `nonfungible` are better to +//! use. +//! +//! Implementations of these traits may be converted to implementations of corresponding +//! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. + +use crate::dispatch::{DispatchError, DispatchResult}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to many read-only NFT-like sets of items. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Type for identifying a collection (an identifier for an independent collection of + /// items). + type CollectionId; + + /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist + /// (or somehow has no owner). + fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option; + + /// Returns the owner of the `collection`, if there is one. For many NFTs this may not + /// make any sense, so users of this API should not be surprised to find a collection + /// results in `None` here. + fn collection_owner(_collection: &Self::CollectionId) -> Option { + None + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::attribute(collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the attribute value of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `collection` corresponding to `key`. + /// + /// By default this just attempts to use `collection_attribute`. + fn typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::collection_attribute(collection, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// 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 { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over many collections +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// Returns an iterator of the collections in existence. + fn collections() -> Box>; + + /// Returns an iterator of the items of a `collection` in existence. + fn items(collection: &Self::CollectionId) -> Box>; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; + + /// Returns an iterator of the items of `collection` owned by `who`. + fn owned_in_collection( + collection: &Self::CollectionId, + who: &AccountId, + ) -> Box>; +} + +/// Trait for providing the ability to create collections of nonfungible items. +pub trait Create: Inspect { + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, + who: &AccountId, + admin: &AccountId, + config: &CollectionConfig, + ) -> DispatchResult; +} + +/// Trait for providing the ability to destroy collections of nonfungible items. +pub trait Destroy: Inspect { + /// The witness data needed to destroy an item. + type DestroyWitness; + + /// Provide the appropriate witness data needed to destroy an item. + fn get_destroy_witness(collection: &Self::CollectionId) -> Option; + + /// Destroy an existing fungible item. + /// * `collection`: The `CollectionId` to be destroyed. + /// * `witness`: Any witness data that needs to be provided to complete the operation + /// successfully. + /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// command. If not provided, we will not do any authorization checks before destroying the + /// item. + /// + /// If successful, this function will return the actual witness data from the destroyed item. + /// This may be different than the witness data provided, and can be used to refund weight. + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result; +} + +/// Trait for providing an interface for multiple collections of NFT-like items which may be +/// minted, burned and/or have attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` of `collection` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item` of `collection`. + /// + /// By default, this is not a supported operation. + fn burn( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v))) + } + + /// Set attribute `value` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_collection_attribute( + _collection: &Self::CollectionId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) + }) + } +} + +/// Trait for providing a non-fungible sets of items which can only be transferred. +pub trait Transfer: Inspect { + /// Transfer `item` of `collection` into `destination` account. + fn transfer( + collection: &Self::CollectionId, + item: &Self::ItemId, + destination: &AccountId, + ) -> DispatchResult; +} From 6763dd6124882843e488c905dc0ab6b030670c4d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:32:52 +0300 Subject: [PATCH 15/60] [Uniques V2] Refactor roles (#12437) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Refactor roles structure * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Rework pallet features * Move macros * Change comments * Fmt * Refactor Incrementable * Use pub(crate) for do_* functions * Update comments * Refactor freeze and lock functions * Rework Collection config and Item confg api * Chore * Make clippy happy * Chore * Fix artifacts * Address comments * Further refactoring * Add comments * Add tests for group_roles_by_account() * Update frame/nfts/src/impl_nonfungibles.rs * Add test * Replace Itertools group_by with a custom implementation * ItemsNotTransferable => ItemsNonTransferable * Update frame/nfts/src/features/roles.rs Co-authored-by: Muharem Ismailov * Address PR comments * Add missed comment Co-authored-by: command-bot <> Co-authored-by: Muharem Ismailov --- frame/nfts/src/benchmarking.rs | 2 +- frame/nfts/src/features/lock.rs | 22 +++--- frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/features/roles.rs | 69 ++++++++++++++++++ frame/nfts/src/functions.rs | 22 +++--- frame/nfts/src/impl_nonfungibles.rs | 4 +- frame/nfts/src/lib.rs | 97 ++++++++++++++++++-------- frame/nfts/src/tests.rs | 104 +++++++++++++++++++++++----- frame/nfts/src/types.rs | 44 +++++++++--- 9 files changed, 291 insertions(+), 74 deletions(-) create mode 100644 frame/nfts/src/features/roles.rs diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index c65430fd35108..a5a264c40a715 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -68,7 +68,7 @@ fn add_collection_metadata, I: 'static>() -> (T::AccountId, Account fn mint_item, I: 'static>( index: u16, ) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { - let caller = Collection::::get(T::Helper::collection(0)).unwrap().admin; + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 0a5fecc1d6224..50420d8e3de87 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -24,10 +24,10 @@ impl, I: 'static> Pallet { collection: T::CollectionId, lock_config: CollectionConfig, ) -> DispatchResult { - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.freezer, Error::::NoPermission); - + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; @@ -51,9 +51,10 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); let mut config = Self::get_item_config(&collection, &item)?; if !config.has_disabled_setting(ItemSetting::Transferable) { @@ -70,9 +71,10 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); let mut config = Self::get_item_config(&collection, &item)?; if config.has_disabled_setting(ItemSetting::Transferable) { diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 47e5816bc953c..f814d696d774b 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -18,4 +18,5 @@ pub mod atomic_swap; pub mod buy_sell; pub mod lock; +pub mod roles; pub mod settings; diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs new file mode 100644 index 0000000000000..e961779725b6e --- /dev/null +++ b/frame/nfts/src/features/roles.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; +use sp_std::collections::btree_map::BTreeMap; + +impl, I: 'static> Pallet { + /// Clears all the roles in a specified collection. + /// + /// - `collection_id`: A collection to clear the roles in. + /// + /// Throws an error if some of the roles were left in storage. + /// This means the `CollectionRoles::max_roles()` needs to be adjusted. + pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> { + let res = CollectionRoleOf::::clear_prefix( + &collection_id, + CollectionRoles::max_roles() as u32, + None, + ); + ensure!(res.maybe_cursor.is_none(), Error::::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 boolean. + pub(crate) fn has_role( + collection_id: &T::CollectionId, + account_id: &T::AccountId, + role: CollectionRole, + ) -> bool { + CollectionRoleOf::::get(&collection_id, &account_id) + .map_or(false, |roles| roles.has_role(role)) + } + + /// Groups provided roles by account, given one account could have multiple roles. + /// + /// - `input`: A vector of (Account, Role) tuples. + /// + /// Returns a grouped vector. + 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() + } +} diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 275e3668d7a20..90a701bc9eaa0 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -41,7 +41,7 @@ impl, I: 'static> Pallet { let collection_config = Self::get_collection_config(&collection)?; ensure!( collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); let item_config = Self::get_item_config(&collection, &item)?; @@ -93,15 +93,19 @@ impl, I: 'static> Pallet { collection, CollectionDetails { owner: owner.clone(), - issuer: admin.clone(), - admin: admin.clone(), - freezer: admin, total_deposit: deposit, items: 0, item_metadatas: 0, attributes: 0, }, ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); let next_id = collection.increment(); @@ -142,6 +146,7 @@ impl, I: 'static> Pallet { #[allow(deprecated)] PendingSwapOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; #[allow(deprecated)] Attribute::::remove_prefix((&collection,), None); CollectionAccount::::remove(&collection_details.owner, &collection); @@ -165,7 +170,6 @@ impl, I: 'static> Pallet { item: T::ItemId, owner: T::AccountId, config: ItemConfig, - with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -175,8 +179,6 @@ impl, I: 'static> Pallet { let collection_details = maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - with_details(collection_details)?; - if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); } @@ -218,7 +220,7 @@ impl, I: 'static> Pallet { pub fn do_burn( collection: T::CollectionId, item: T::ItemId, - with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { let owner = Collection::::try_mutate( &collection, @@ -227,7 +229,7 @@ impl, I: 'static> Pallet { maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; let details = Item::::get(&collection, &item) .ok_or(Error::::UnknownCollection)?; - with_details(collection_details, &details)?; + with_details(&details)?; // Return the deposit. T::Currency::unreserve(&collection_details.owner, details.deposit); @@ -271,7 +273,7 @@ impl, I: 'static> Pallet { let collection_config = Self::get_collection_config(&collection)?; ensure!( collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); let item_config = Self::get_item_config(&collection, &item)?; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 8a7c79fc0c14f..210fe4831991d 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -143,7 +143,7 @@ impl, I: 'static> Mutate<::AccountId, ItemSettin who: &T::AccountId, settings: &ItemSettings, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings)) } fn burn( @@ -151,7 +151,7 @@ impl, I: 'static> Mutate<::AccountId, ItemSettin item: &Self::ItemId, maybe_check_owner: Option<&T::AccountId>, ) -> DispatchResult { - Self::do_burn(*collection, *item, |_, d| { + Self::do_burn(*collection, *item, |d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { return Err(Error::::NoPermission.into()) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index bfba0c1ea3330..8b8b21f944f3c 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -219,6 +219,19 @@ pub mod pallet { OptionQuery, >; + /// The items in existence and their ownership details. + #[pallet::storage] + /// Stores collection roles as per account. + pub(super) type CollectionRoleOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, + CollectionRoles, + OptionQuery, + >; + /// The items in existence and their ownership details. #[pallet::storage] #[pallet::storage_prefix = "Asset"] @@ -497,7 +510,7 @@ pub mod pallet { /// Collection ID is already taken. CollectionIdInUse, /// Items within that collection are non-transferable. - ItemsNotTransferable, + ItemsNonTransferable, /// The provided account is not a delegate. NotDelegate, /// The delegate turned out to be different to what was expected. @@ -544,6 +557,8 @@ pub mod pallet { InconsistentItemConfig, /// Config for a collection or an item can't be found. NoConfig, + /// Some roles were not cleared. + RolesNotCleared, } impl, I: 'static> Pallet { @@ -702,10 +717,11 @@ pub mod pallet { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, config, |collection_details| { - ensure!(collection_details.issuer == origin, Error::::NoPermission); - Ok(()) - }) + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Issuer), + Error::::NoPermission + ); + Self::do_mint(collection, item, owner, config) } /// Destroy a single item. @@ -731,8 +747,9 @@ pub mod pallet { let origin = ensure_signed(origin)?; let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; - Self::do_burn(collection, item, |collection_details, details| { - let is_permitted = collection_details.admin == origin || details.owner == origin; + Self::do_burn(collection, item, |details| { + let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); + let is_permitted = is_admin || details.owner == origin; ensure!(is_permitted, Error::::NoPermission); ensure!( check_owner.map_or(true, |o| o == details.owner), @@ -767,8 +784,9 @@ pub mod pallet { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - Self::do_transfer(collection, item, dest, |collection_details, details| { - if details.owner != origin && collection_details.admin != origin { + Self::do_transfer(collection, item, dest, |_, details| { + let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); + if details.owner != origin && !is_admin { let deadline = details.approvals.get(&origin).ok_or(Error::::NoPermission)?; if let Some(d) = deadline { @@ -986,9 +1004,17 @@ pub mod pallet { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); - details.issuer = issuer.clone(); - details.admin = admin.clone(); - details.freezer = freezer.clone(); + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); Ok(()) @@ -1026,19 +1052,24 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); let collection_config = Self::get_collection_config(&collection)?; ensure!( collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); if let Some(check) = maybe_check { - let permitted = check == collection_details.admin || check == details.owner; + let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); + let permitted = is_admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } @@ -1090,10 +1121,8 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; @@ -1107,7 +1136,8 @@ pub mod pallet { if !is_past_deadline { if let Some(check) = maybe_check { - let permitted = check == collection_details.admin || check == details.owner; + let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); + let permitted = is_admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } } @@ -1148,12 +1178,12 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + if let Some(check) = maybe_check { - let permitted = check == collection_details.admin || check == details.owner; + let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); + let permitted = is_admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } @@ -1200,14 +1230,27 @@ pub mod pallet { let old_owner = collection_info.owner; let new_owner = T::Lookup::lookup(owner)?; collection_info.owner = new_owner.clone(); - collection_info.issuer = T::Lookup::lookup(issuer)?; - collection_info.admin = T::Lookup::lookup(admin)?; - collection_info.freezer = T::Lookup::lookup(freezer)?; *maybe_collection = Some(collection_info); CollectionAccount::::remove(&old_owner, &collection); CollectionAccount::::insert(&new_owner, &collection, ()); CollectionConfigOf::::insert(&collection, config); + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer, CollectionRole::Issuer), + (admin, CollectionRole::Admin), + (freezer, CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + Self::deposit_event(Event::CollectionStatusChanged { collection }); Ok(()) }) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 1b60fd6431b19..d0841ebc1f238 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -21,7 +21,7 @@ use crate::{mock::*, Event, *}; use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, - traits::{Currency, Get}, + traits::{tokens::nonfungibles_v2::Destroy, Currency, Get}, }; use pallet_balances::Error as BalancesError; use sp_std::prelude::*; @@ -152,7 +152,7 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 13); assert!(ItemMetadataOf::::contains_key(0, 69)); - let w = Collection::::get(0).unwrap().destroy_witness(); + let w = Nfts::get_destroy_witness(&0).unwrap(); assert_eq!(w.items, 2); assert_eq!(w.item_metadatas, 2); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); @@ -227,7 +227,7 @@ fn transfer_should_work() { assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); }); } @@ -248,7 +248,7 @@ fn locking_transfer_should_work() { )); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); assert_ok!(Nfts::force_collection_status( @@ -296,7 +296,7 @@ fn origin_guards_should_work() { Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission ); - let w = Collection::::get(0).unwrap().destroy_witness(); + let w = Nfts::get_destroy_witness(&0).unwrap(); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); }); } @@ -550,7 +550,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 16); - let w = Collection::::get(0).unwrap().destroy_witness(); + let w = Nfts::get_destroy_witness(&0).unwrap(); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(attributes(0), vec![]); assert_eq!(Balances::reserved_balance(1), 0); @@ -687,6 +687,48 @@ fn force_collection_status_should_work() { assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 0); + + // validate new roles + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 2, + 3, + 4, + CollectionConfig::all_settings_enabled(), + )); + assert_eq!( + CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoles(CollectionRole::Issuer.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 4).unwrap(), + CollectionRoles(CollectionRole::Freezer.into()) + ); + + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 3, + 2, + 3, + CollectionConfig::all_settings_enabled(), + )); + + assert_eq!( + CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoles(CollectionRole::Issuer | CollectionRole::Freezer) + ); }); } @@ -761,7 +803,7 @@ fn approval_lifecycle_works() { assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); }); } @@ -775,11 +817,11 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(2), 1, 42, 3), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 43, 3), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(3), 0, 42, 3), @@ -898,11 +940,11 @@ fn cancel_approval_works_with_admin() { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(1), 1, 42, 1), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 43, 1), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 4), @@ -926,11 +968,11 @@ fn cancel_approval_works_with_force() { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, 1), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, 1), - Error::::UnknownCollection + Error::::UnknownItem ); assert_noop!( Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 4), @@ -1041,7 +1083,7 @@ fn max_supply_should_work() { assert_ok!(Nfts::destroy( RuntimeOrigin::signed(user_id), collection_id, - Collection::::get(collection_id).unwrap().destroy_witness() + Nfts::get_destroy_witness(&collection_id).unwrap() )); assert!(!CollectionMaxSupply::::contains_key(collection_id)); }); @@ -1137,7 +1179,7 @@ fn set_price_should_work() { assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); }); } @@ -1276,7 +1318,7 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::ItemsNotTransferable + Error::::ItemsNonTransferable ); // unlock the collection @@ -1824,3 +1866,33 @@ fn pallet_level_feature_flags_should_work() { ); }) } + +#[test] +fn group_roles_by_account_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Nfts::group_roles_by_account(vec![]), vec![]); + + let account_to_role = Nfts::group_roles_by_account(vec![ + (3, CollectionRole::Freezer), + (1, CollectionRole::Issuer), + (2, CollectionRole::Admin), + ]); + let expect = vec![ + (1, CollectionRoles(CollectionRole::Issuer.into())), + (2, CollectionRoles(CollectionRole::Admin.into())), + (3, CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + + let account_to_role = Nfts::group_roles_by_account(vec![ + (3, CollectionRole::Freezer), + (2, CollectionRole::Issuer), + (2, CollectionRole::Admin), + ]); + let expect = vec![ + (2, CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)), + (3, CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 6ed57e4da25e5..0122a817229ac 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -56,14 +56,8 @@ impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { - /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. + /// Collection's owner. pub(super) owner: AccountId, - /// Can mint tokens. - pub(super) issuer: AccountId, - /// Can thaw tokens, force transfers and burn tokens from any account. - pub(super) admin: AccountId, - /// Can freeze tokens. - pub(super) freezer: AccountId, /// The total balance deposited for the all storage associated with this collection. /// Used by `destroy`. pub(super) total_deposit: DepositBalance, @@ -84,8 +78,8 @@ pub struct DestroyWitness { /// The total number of items in this collection that have outstanding item metadata. #[codec(compact)] pub item_metadatas: u32, - #[codec(compact)] /// The total number of attributes for this collection. + #[codec(compact)] pub attributes: u32, } @@ -301,3 +295,37 @@ impl PalletFeatures { } } 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); + +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 = BitFlags::all(); + all.len() as u8 + } +} +impl_codec_bitflags!(CollectionRoles, u8, CollectionRole); From fef9b48c76ca125bebbf1b52b4d1109358ad0e81 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sun, 23 Oct 2022 07:28:26 +0200 Subject: [PATCH 16/60] Fix copy --- frame/nfts/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8b8b21f944f3c..d864374a675a5 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -729,7 +729,7 @@ pub mod pallet { /// Origin must be Signed and the sender should be the Admin of the `collection`. /// /// - `collection`: The collection of the item to be burned. - /// - `item`: The item of the item to be burned. + /// - `item`: The item to be burned. /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the /// item is owned by this value. /// @@ -768,7 +768,7 @@ pub mod pallet { /// /// Arguments: /// - `collection`: The collection of the item to be transferred. - /// - `item`: The item of the item to be transferred. + /// - `item`: The item to be transferred. /// - `dest`: The account to receive ownership of the item. /// /// Emits `Transferred`. @@ -1026,7 +1026,7 @@ pub mod pallet { /// Origin must be Signed and must be the owner of the `item`. /// /// - `collection`: The collection of the item to be approved for delegated transfer. - /// - `item`: The item of the item to be approved for delegated transfer. + /// - `item`: The item to be approved for delegated transfer. /// - `delegate`: The account to delegate permission to transfer the item. /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the /// number of blocks after which the approval will expire From 799cdf335a4418d87d1c2d913a6fad662e43e936 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sun, 23 Oct 2022 07:28:55 +0200 Subject: [PATCH 17/60] Remove storage_prefix --- frame/nfts/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index d864374a675a5..671adba703eab 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -178,7 +178,6 @@ pub mod pallet { /// Details of a collection. #[pallet::storage] - #[pallet::storage_prefix = "Class"] pub(super) type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, @@ -208,7 +207,6 @@ pub mod pallet { /// The collections owned by any given account; set out this way so that collections owned by /// a single account can be enumerated. #[pallet::storage] - #[pallet::storage_prefix = "ClassAccount"] pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -234,7 +232,6 @@ pub mod pallet { /// The items in existence and their ownership details. #[pallet::storage] - #[pallet::storage_prefix = "Asset"] pub(super) type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -247,7 +244,6 @@ pub mod pallet { /// Metadata of a collection. #[pallet::storage] - #[pallet::storage_prefix = "ClassMetadataOf"] pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, @@ -258,7 +254,6 @@ pub mod pallet { /// Metadata of an item. #[pallet::storage] - #[pallet::storage_prefix = "InstanceMetadataOf"] pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, From 83a601d2638ab9ae053bbe56faf517d778d23663 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sun, 23 Oct 2022 07:30:13 +0200 Subject: [PATCH 18/60] Remove transactional --- frame/nfts/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 671adba703eab..73c8e2c391e68 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -48,7 +48,7 @@ use frame_support::{ traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, }, - transactional, BoundedBTreeMap, + BoundedBTreeMap, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -1766,7 +1766,6 @@ pub mod pallet { /// /// Emits `ItemBought` on success. #[pallet::weight(T::WeightInfo::buy_item())] - #[transactional] pub fn buy_item( origin: OriginFor, collection: T::CollectionId, @@ -1785,7 +1784,6 @@ pub mod pallet { /// /// Emits `TipSent` on every tip transfer. #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))] - #[transactional] pub fn pay_tips( origin: OriginFor, tips: BoundedVec, T::MaxTips>, From e1b24d79adc82c339b3b83ef8a22a8cdd9390109 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 28 Oct 2022 16:45:18 +0200 Subject: [PATCH 19/60] Update comment --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 73c8e2c391e68..00fb6dc238f86 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -277,7 +277,7 @@ pub mod pallet { OptionQuery, >; - /// Price of an asset instance. + /// A price of an item. #[pallet::storage] pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< _, From afd4c18a4676c5889a03beb9591384851f695498 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:12:50 +0200 Subject: [PATCH 20/60] [Uniques V2] Minting options (#12483) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Change the format of CollectionConfig to store more data * Move max supply to the CollectionConfig and allow to change it * Remove ItemConfig from the mint() function and use the one set in mint settings * Add different mint options * Allow to change the mint settings * Add a force_mint() method * Check mint params * Some optimisations * Cover with tests * Remove merge artifacts * Chore * Use the new has_role() method * Rework item deposits * More tests * Refactoring * Address comments * Refactor lock_collection() * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Private => Issuer * Add more tests * Fix benchmarks * Add benchmarks for new methods * [Uniques v2] Refactoring (#12570) * Move do_set_price() and do_buy_item() to buy_sell.rs * Move approvals to feature file * Move metadata to feature files * Move the rest of methods to feature files * Remove artifacts * Split force_collection_status into 2 methods * Fix benchmarks * Fix benchmarks * Update deps Co-authored-by: command-bot <> Co-authored-by: Squirrel --- frame/nfts/Cargo.toml | 10 +- frame/nfts/src/benchmarking.rs | 96 ++- frame/nfts/src/common_functions.rs | 42 + frame/nfts/src/features/approvals.rs | 132 ++++ frame/nfts/src/features/attributes.rs | 123 +++ frame/nfts/src/features/buy_sell.rs | 90 ++- .../src/features/create_delete_collection.rs | 109 +++ frame/nfts/src/features/create_delete_item.rs | 123 +++ frame/nfts/src/features/lock.rs | 16 +- frame/nfts/src/features/metadata.rs | 173 +++++ frame/nfts/src/features/mod.rs | 6 + frame/nfts/src/features/roles.rs | 30 + frame/nfts/src/features/settings.rs | 70 +- frame/nfts/src/features/transfer.rs | 165 ++++ frame/nfts/src/functions.rs | 355 --------- frame/nfts/src/impl_nonfungibles.rs | 20 +- frame/nfts/src/lib.rs | 733 ++++++------------ frame/nfts/src/tests.rs | 514 ++++++------ frame/nfts/src/types.rs | 163 +++- frame/nfts/src/weights.rs | 61 +- .../src/traits/tokens/nonfungible_v2.rs | 22 +- .../src/traits/tokens/nonfungibles_v2.rs | 1 + 22 files changed, 1856 insertions(+), 1198 deletions(-) create mode 100644 frame/nfts/src/common_functions.rs create mode 100644 frame/nfts/src/features/approvals.rs create mode 100644 frame/nfts/src/features/attributes.rs create mode 100644 frame/nfts/src/features/create_delete_collection.rs create mode 100644 frame/nfts/src/features/create_delete_item.rs create mode 100644 frame/nfts/src/features/metadata.rs create mode 100644 frame/nfts/src/features/transfer.rs delete mode 100644 frame/nfts/src/functions.rs diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index f0b68ea702e3a..109dffdd10f50 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -20,14 +20,14 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index a5a264c40a715..61407abd9f985 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -20,6 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; +use enumflags2::{BitFlag, BitFlags}; use frame_benchmarking::{ account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; @@ -46,7 +47,7 @@ fn create_collection, I: 'static>( assert_ok!(Nfts::::force_create( SystemOrigin::Root.into(), caller_lookup.clone(), - CollectionConfig::all_settings_enabled() + default_collection_config::() )); (collection, caller, caller_lookup) } @@ -78,8 +79,7 @@ fn mint_item, I: 'static>( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, - caller_lookup.clone(), - ItemConfig::all_settings_enabled(), + None, )); (item, caller, caller_lookup) } @@ -128,6 +128,24 @@ fn assert_last_event, I: 'static>(generic_event: >:: assert_eq!(event, &system_event); } +fn make_collection_config, I: 'static>( + disable_settings: BitFlags, +) -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::from_disabled(disable_settings), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn default_collection_config, I: 'static>() -> CollectionConfigFor { + make_collection_config::(CollectionSetting::empty()) +} + +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::all_enabled() } +} + benchmarks_instance_pallet! { create { let collection = T::Helper::collection(0); @@ -136,7 +154,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { admin, config: CollectionConfig::all_settings_enabled() }; + let call = Call::::create { admin, config: default_collection_config::() }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -145,7 +163,7 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, caller_lookup, CollectionConfig::all_settings_enabled()) + }: _(SystemOrigin::Root, caller_lookup, default_collection_config::()) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } @@ -169,7 +187,15 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, ItemConfig::all_settings_enabled()) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, None) + verify { + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + } + + force_mint { + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config()) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -188,6 +214,7 @@ benchmarks_instance_pallet! { 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::(Event::Transferred { collection, item, from: caller, to: target }.into()); @@ -197,14 +224,10 @@ benchmarks_instance_pallet! { let i in 0 .. 5_000; let (collection, caller, caller_lookup) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); - Nfts::::force_collection_status( + Nfts::::force_collection_config( SystemOrigin::Root.into(), collection, - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup, - CollectionConfig(CollectionSetting::DepositRequired.into()), + make_collection_config::(CollectionSetting::DepositRequired.into()), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { @@ -234,12 +257,13 @@ benchmarks_instance_pallet! { lock_collection { let (collection, caller, caller_lookup) = create_collection::(); - let lock_config = CollectionConfig( + let lock_settings = CollectionSettings::from_disabled( CollectionSetting::TransferableItems | CollectionSetting::UnlockedMetadata | - CollectionSetting::UnlockedAttributes, + CollectionSetting::UnlockedAttributes | + CollectionSetting::UnlockedMaxSupply, ); - }: _(SystemOrigin::Signed(caller.clone()), collection, lock_config) + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) verify { assert_last_event::(Event::CollectionLocked { collection }.into()); } @@ -271,20 +295,31 @@ benchmarks_instance_pallet! { }.into()); } - force_collection_status { + force_collection_owner { + let (collection, _, _) = create_collection::(); + let origin = T::ForceOrigin::successful_origin(); + 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::::force_collection_owner { + collection, + owner: target_lookup, + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); + } + + force_collection_config { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_collection_status { + let call = Call::::force_collection_config { collection, - owner: caller_lookup.clone(), - issuer: caller_lookup.clone(), - admin: caller_lookup.clone(), - freezer: caller_lookup, - config: CollectionConfig(CollectionSetting::DepositRequired.into()), + config: make_collection_config::(CollectionSetting::DepositRequired.into()), }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::CollectionStatusChanged { collection }.into()); + assert_last_event::(Event::CollectionConfigChanged { collection }.into()); } lock_item_properties { @@ -414,6 +449,20 @@ benchmarks_instance_pallet! { }.into()); } + update_mint_settings { + let (collection, caller, _) = create_collection::(); + 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::::from(1u32)), + default_item_settings: ItemSettings::all_enabled(), + }; + }: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings) + verify { + assert_last_event::(Event::CollectionMintSettingsUpdated { collection }.into()); + } + set_price { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); @@ -528,6 +577,7 @@ benchmarks_instance_pallet! { 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()); frame_system::Pallet::::set_block_number(One::one()); Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs new file mode 100644 index 0000000000000..b3cac7f69ec0e --- /dev/null +++ b/frame/nfts/src/common_functions.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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 super::*; + +impl, I: 'static> Pallet { + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) + } +} diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs new file mode 100644 index 0000000000000..0cbceb9113d0c --- /dev/null +++ b/frame/nfts/src/features/approvals.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_approve_transfer( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + maybe_deadline: Option<::BlockNumber>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + details + .approvals + .try_insert(delegate.clone(), deadline) + .map_err(|_| Error::::ReachedApprovalLimit)?; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovedTransfer { + collection, + item, + owner: details.owner, + delegate, + deadline, + }); + + Ok(()) + } + + pub(crate) fn do_cancel_approval( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; + + let is_past_deadline = if let Some(deadline) = maybe_deadline { + let now = frame_system::Pallet::::block_number(); + now > *deadline + } else { + false + }; + + if !is_past_deadline { + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + pub(crate) fn do_clear_all_transfer_approvals( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + details.approvals.clear(); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::AllApprovalsCancelled { + collection, + item, + owner: details.owner, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs new file mode 100644 index 0000000000000..85c1e0b302d12 --- /dev/null +++ b/frame/nfts/src/features/attributes.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::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::::LockedItemAttributes); + }, + }; + + let attribute = Attribute::::get((collection, maybe_item, &key)); + if attribute.is_none() { + collection_details.attributes.saturating_inc(); + } + let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + collection_details.total_deposit.saturating_accrue(deposit); + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + if maybe_check_owner.is_some() { + match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::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(false, |c| c.has_disabled_setting(ItemSetting::UnlockedAttributes)); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + } + + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + } + Ok(()) + } +} diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs index c1e29057af9c9..8ba5171f8d822 100644 --- a/frame/nfts/src/features/buy_sell.rs +++ b/frame/nfts/src/features/buy_sell.rs @@ -18,7 +18,7 @@ use crate::*; use frame_support::{ pallet_prelude::*, - traits::{Currency, ExistenceRequirement::KeepAlive}, + traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, }; impl, I: 'static> Pallet { @@ -39,4 +39,92 @@ impl, I: 'static> Pallet { } Ok(()) } + + pub(crate) fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub(crate) fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::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(()) + } } diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs new file mode 100644 index 0000000000000..b9530e88b18cd --- /dev/null +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -0,0 +1,109 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + config: CollectionConfigFor, + deposit: DepositBalanceOf, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + total_deposit: deposit, + items: 0, + item_metadatas: 0, + attributes: 0, + }, + ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); + + let next_id = collection.increment(); + + CollectionConfigOf::::insert(&collection, config); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(Some(next_id)); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + Self::deposit_event(event); + Ok(()) + } + + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + PendingSwapOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; + #[allow(deprecated)] + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } +} diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs new file mode 100644 index 0000000000000..10670f4b10c1c --- /dev/null +++ b/frame/nfts/src/features/create_delete_item.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + item_config: ItemConfig, + deposit_collection_owner: bool, + with_details_and_config: impl FnOnce( + &CollectionDetailsFor, + &CollectionConfigFor, + ) -> DispatchResult, + ) -> DispatchResult { + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::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::::MaxSupplyReached); + } + + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; + + 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 deposit_collection_owner { + true => collection_details.owner.clone(), + false => owner.clone(), + }; + + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, item_config); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = + ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner }); + Ok(()) + } + + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(&details)?; + + // Return the deposit. + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + collection_details.items.saturating_dec(); + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_settings() { + ItemConfigOf::::remove(&collection, &item); + } + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } +} diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 50420d8e3de87..e96a30dfd2c7c 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -22,23 +22,21 @@ impl, I: 'static> Pallet { pub(crate) fn do_lock_collection( origin: T::AccountId, collection: T::CollectionId, - lock_config: CollectionConfig, + lock_settings: CollectionSettings, ) -> DispatchResult { ensure!( Self::has_role(&collection, &origin, CollectionRole::Freezer), Error::::NoPermission ); + ensure!( + !lock_settings.is_disabled(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - if lock_config.has_disabled_setting(CollectionSetting::TransferableItems) { - config.disable_setting(CollectionSetting::TransferableItems); - } - if lock_config.has_disabled_setting(CollectionSetting::UnlockedMetadata) { - config.disable_setting(CollectionSetting::UnlockedMetadata); - } - if lock_config.has_disabled_setting(CollectionSetting::UnlockedAttributes) { - config.disable_setting(CollectionSetting::UnlockedAttributes); + for setting in lock_settings.get_disabled() { + config.disable_setting(setting); } Self::deposit_event(Event::::CollectionLocked { collection }); diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs new file mode 100644 index 0000000000000..0b0a337197d9b --- /dev/null +++ b/frame/nfts/src/features/metadata.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + maybe_check_owner.is_none() || + item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_none() { + collection_details.item_metadatas.saturating_inc(); + } + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + collection_details.total_deposit.saturating_accrue(deposit); + + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + // 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!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_some() { + collection_details.item_metadatas.saturating_dec(); + } + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if maybe_check_owner.is_some() && + 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.total_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&details.owner, deposit); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index f814d696d774b..b77ee9bf2491b 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -15,8 +15,14 @@ // 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; diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs index e961779725b6e..d6be9965a5e74 100644 --- a/frame/nfts/src/features/roles.rs +++ b/frame/nfts/src/features/roles.rs @@ -20,6 +20,36 @@ use frame_support::pallet_prelude::*; use sp_std::collections::btree_map::BTreeMap; impl, I: 'static> Pallet { + pub(crate) fn do_set_team( + maybe_check_owner: Option, + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + if let Some(check_origin) = maybe_check_owner { + ensure!(check_origin == details.owner, Error::::NoPermission); + } + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::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. diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 2596d360d8dcd..5f408ed183c35 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -18,16 +18,72 @@ use crate::*; use frame_support::pallet_prelude::*; -/// The helper methods bellow allow to read and validate different -/// collection/item/pallet settings. -/// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular -/// collection, or for a specific item. impl, I: 'static> Pallet { + pub(crate) fn do_force_collection_config( + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + ensure!(Collection::::contains_key(&collection), Error::::UnknownCollection); + CollectionConfigOf::::insert(&collection, config); + Self::deposit_event(Event::CollectionConfigChanged { collection }); + Ok(()) + } + + pub(crate) fn do_set_collection_max_supply( + maybe_check_owner: Option, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), + Error::::MaxSupplyLocked + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) + } + + pub(crate) fn do_update_mint_settings( + maybe_check_owner: Option, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.mint_settings = mint_settings; + Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); + Ok(()) + }) + } + pub(crate) fn get_collection_config( collection_id: &T::CollectionId, - ) -> Result { - let config = CollectionConfigOf::::get(&collection_id) - .ok_or(Error::::UnknownCollection)?; + ) -> Result, DispatchError> { + let config = + CollectionConfigOf::::get(&collection_id).ok_or(Error::::NoConfig)?; Ok(config) } diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs new file mode 100644 index 0000000000000..7ebad853902a9 --- /dev/null +++ b/frame/nfts/src/features/transfer.rs @@ -0,0 +1,165 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + with_details(&collection_details, &mut details)?; + + if details.deposit.account == details.owner { + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &dest, + details.deposit.amount, + Reserved, + )?; + } + + Account::::remove((&details.owner, &collection, &item)); + Account::::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 his second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + pub(crate) fn do_transfer_ownership( + origin: T::AccountId, + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } + + pub(crate) fn do_set_accept_ownership( + who: T::AccountId, + maybe_collection: Option, + ) -> DispatchResult { + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } + + pub(crate) fn do_force_collection_owner( + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs deleted file mode 100644 index 90a701bc9eaa0..0000000000000 --- a/frame/nfts/src/functions.rs +++ /dev/null @@ -1,355 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 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 super::*; -use frame_support::{ - ensure, - traits::{ExistenceRequirement, Get}, -}; -use sp_runtime::{DispatchError, DispatchResult}; - -impl, I: 'static> Pallet { - pub fn do_transfer( - collection: T::CollectionId, - item: T::ItemId, - dest: T::AccountId, - with_details: impl FnOnce( - &CollectionDetailsFor, - &mut ItemDetailsFor, - ) -> DispatchResult, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - with_details(&collection_details, &mut details)?; - - Account::::remove((&details.owner, &collection, &item)); - Account::::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 his second account before making the - // transaction and then claiming the item back. - details.approvals.clear(); - - Item::::insert(&collection, &item, &details); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - Self::deposit_event(Event::Transferred { - collection, - item, - from: origin, - to: details.owner, - }); - Ok(()) - } - - pub fn do_create_collection( - collection: T::CollectionId, - owner: T::AccountId, - admin: T::AccountId, - config: CollectionConfig, - deposit: DepositBalanceOf, - event: Event, - ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); - - T::Currency::reserve(&owner, deposit)?; - - Collection::::insert( - collection, - CollectionDetails { - owner: owner.clone(), - total_deposit: deposit, - items: 0, - item_metadatas: 0, - attributes: 0, - }, - ); - CollectionRoleOf::::insert( - collection, - admin, - CollectionRoles( - CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, - ), - ); - - let next_id = collection.increment(); - - CollectionConfigOf::::insert(&collection, config); - CollectionAccount::::insert(&owner, &collection, ()); - NextCollectionId::::set(Some(next_id)); - - Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); - Self::deposit_event(event); - Ok(()) - } - - pub fn do_destroy_collection( - collection: T::CollectionId, - witness: DestroyWitness, - maybe_check_owner: Option, - ) -> Result { - Collection::::try_mutate_exists(collection, |maybe_details| { - let collection_details = - maybe_details.take().ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(collection_details.owner == check_owner, Error::::NoPermission); - } - ensure!(collection_details.items == witness.items, Error::::BadWitness); - ensure!( - collection_details.item_metadatas == witness.item_metadatas, - Error::::BadWitness - ); - ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); - - for (item, details) in Item::::drain_prefix(&collection) { - Account::::remove((&details.owner, &collection, &item)); - } - #[allow(deprecated)] - ItemMetadataOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - ItemPriceOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - PendingSwapOf::::remove_prefix(&collection, None); - CollectionMetadataOf::::remove(&collection); - Self::clear_roles(&collection)?; - #[allow(deprecated)] - Attribute::::remove_prefix((&collection,), None); - CollectionAccount::::remove(&collection_details.owner, &collection); - T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); - CollectionMaxSupply::::remove(&collection); - CollectionConfigOf::::remove(&collection); - let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); - - Self::deposit_event(Event::Destroyed { collection }); - - Ok(DestroyWitness { - items: collection_details.items, - item_metadatas: collection_details.item_metadatas, - attributes: collection_details.attributes, - }) - }) - } - - pub fn do_mint( - collection: T::CollectionId, - item: T::ItemId, - owner: T::AccountId, - config: ItemConfig, - ) -> DispatchResult { - ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); - - Collection::::try_mutate( - &collection, - |maybe_collection_details| -> DispatchResult { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - - if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { - ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); - } - - let items = - collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; - collection_details.items = items; - - let collection_config = Self::get_collection_config(&collection)?; - let deposit = match collection_config - .is_setting_enabled(CollectionSetting::DepositRequired) - { - true => T::ItemDeposit::get(), - false => Zero::zero(), - }; - T::Currency::reserve(&collection_details.owner, deposit)?; - collection_details.total_deposit += deposit; - - let owner = owner.clone(); - Account::::insert((&owner, &collection, &item), ()); - - if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { - ensure!(existing_config == config, Error::::InconsistentItemConfig); - } else { - ItemConfigOf::::insert(&collection, &item, config); - } - - let details = - ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; - Item::::insert(&collection, &item, details); - Ok(()) - }, - )?; - - Self::deposit_event(Event::Issued { collection, item, owner }); - Ok(()) - } - - pub fn do_burn( - collection: T::CollectionId, - item: T::ItemId, - with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, - ) -> DispatchResult { - let owner = Collection::::try_mutate( - &collection, - |maybe_collection_details| -> Result { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - let details = Item::::get(&collection, &item) - .ok_or(Error::::UnknownCollection)?; - with_details(&details)?; - - // Return the deposit. - T::Currency::unreserve(&collection_details.owner, details.deposit); - collection_details.total_deposit.saturating_reduce(details.deposit); - collection_details.items.saturating_dec(); - Ok(details.owner) - }, - )?; - - Item::::remove(&collection, &item); - Account::::remove((&owner, &collection, &item)); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - // NOTE: if item's settings are not empty (e.g. item's metadata is locked) - // then we keep the record and don't remove it - let config = Self::get_item_config(&collection, &item)?; - if !config.has_disabled_settings() { - ItemConfigOf::::remove(&collection, &item); - } - - Self::deposit_event(Event::Burned { collection, item, owner }); - Ok(()) - } - - pub fn do_set_price( - collection: T::CollectionId, - item: T::ItemId, - sender: T::AccountId, - price: Option>, - whitelisted_buyer: Option, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner == sender, Error::::NoPermission); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - if let Some(ref price) = price { - ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); - Self::deposit_event(Event::ItemPriceSet { - collection, - item, - price: *price, - whitelisted_buyer, - }); - } else { - ItemPriceOf::::remove(&collection, &item); - Self::deposit_event(Event::ItemPriceRemoved { collection, item }); - } - - Ok(()) - } - - pub fn do_buy_item( - collection: T::CollectionId, - item: T::ItemId, - buyer: T::AccountId, - bid_price: ItemPrice, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner != buyer, Error::::NoPermission); - - let price_info = - ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; - - ensure!(bid_price >= price_info.0, Error::::BidTooLow); - - if let Some(only_buyer) = price_info.1 { - ensure!(only_buyer == buyer, Error::::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(()) - } - - #[cfg(any(test, feature = "runtime-benchmarks"))] - pub fn set_next_id(id: T::CollectionId) { - NextCollectionId::::set(Some(id)); - } - - #[cfg(test)] - pub fn get_next_id() -> T::CollectionId { - NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) - } -} diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 210fe4831991d..b42147e6687d9 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -92,7 +92,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } -impl, I: 'static> Create<::AccountId, CollectionConfig> +impl, I: 'static> Create<::AccountId, CollectionConfigFor> for Pallet { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. @@ -100,7 +100,7 @@ impl, I: 'static> Create<::AccountId, Collection collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, - config: &CollectionConfig, + config: &CollectionConfigFor, ) -> DispatchResult { // DepositRequired can be disabled by calling the force_create() only ensure!( @@ -134,16 +134,22 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId, ItemSettings> - for Pallet -{ +impl, I: 'static> Mutate<::AccountId, ItemConfig> for Pallet { fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, who: &T::AccountId, - settings: &ItemSettings, + item_config: &ItemConfig, + deposit_collection_owner: bool, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings)) + Self::do_mint( + *collection, + *item, + who.clone(), + *item_config, + deposit_collection_owner, + |_, _| Ok(()), + ) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 00fb6dc238f86..0f3d3c89c2932 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -35,8 +35,8 @@ pub mod mock; #[cfg(test)] mod tests; +mod common_functions; mod features; -mod functions; mod impl_nonfungibles; mod types; @@ -66,7 +66,7 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -238,7 +238,7 @@ pub mod pallet { T::CollectionId, Blake2_128Concat, T::ItemId, - ItemDetails, ApprovalsOf>, + ItemDetails, ApprovalsOf>, OptionQuery, >; @@ -289,11 +289,6 @@ pub mod pallet { OptionQuery, >; - /// Keeps track of the number of items a collection might have. - #[pallet::storage] - pub(super) type CollectionMaxSupply, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; - /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented by 1 whenever a new collection is created. #[pallet::storage] @@ -320,7 +315,7 @@ pub mod pallet { /// Config of a collection. #[pallet::storage] pub(super) type CollectionConfigOf, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; /// Config of an item. #[pallet::storage] @@ -395,8 +390,8 @@ pub mod pallet { }, /// All approvals of an item got cancelled. AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, - /// A `collection` has had its attributes changed by the `Force` origin. - CollectionStatusChanged { collection: T::CollectionId }, + /// A `collection` has had its config changed by the `Force` origin. + CollectionConfigChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, /// Metadata has been cleared for a `collection`. @@ -428,10 +423,10 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// Mint settings for a collection had changed. + CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emmited when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, - /// The config of a collection has change. - CollectionConfigChanged { id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -526,8 +521,8 @@ pub mod pallet { LockedCollectionMetadata, /// All items have been minted. MaxSupplyReached, - /// The max supply has already been set. - MaxSupplyAlreadySet, + /// The max supply is locked and can't be changed. + MaxSupplyLocked, /// The provided max supply is less to the amount of items a collection already has. MaxSupplyTooSmall, /// The given item ID is unknown. @@ -554,18 +549,10 @@ pub mod pallet { NoConfig, /// Some roles were not cleared. RolesNotCleared, - } - - impl, I: 'static> Pallet { - /// Get the owner of the item, if the item exists. - pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { - Item::::get(collection, item).map(|i| i.owner) - } - - /// Get the owner of the item, if the item exists. - pub fn collection_owner(collection: T::CollectionId) -> Option { - Collection::::get(collection).map(|i| i.owner) - } + /// Mint has not started yet. + MintNotStated, + /// Mint has already ended. + MintEnded, } #[pallet::call] @@ -589,7 +576,7 @@ pub mod pallet { pub fn create( origin: OriginFor, admin: AccountIdLookupOf, - config: CollectionConfig, + config: CollectionConfigFor, ) -> DispatchResult { let collection = NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); @@ -633,7 +620,7 @@ pub mod pallet { pub fn force_create( origin: OriginFor, owner: AccountIdLookupOf, - config: CollectionConfig, + config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; @@ -676,10 +663,9 @@ pub mod pallet { collection: T::CollectionId, witness: DestroyWitness, ) -> DispatchResultWithPostInfo { - let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { - Ok(_) => None, - Err(origin) => Some(ensure_signed(origin)?), - }; + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; Ok(Some(T::WeightInfo::destroy( @@ -695,28 +681,112 @@ pub mod pallet { /// The origin must be Signed and the sender must be the Issuer of the `collection`. /// /// - `collection`: The collection of the item to be minted. - /// - `item`: The item value of the item to be minted. - /// - `beneficiary`: The initial owner of the minted item. + /// - `item`: An identifier of the new item. + /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned + /// item_id from that collection needs to be provided within the witness data object. /// /// Emits `Issued` event when successful. /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::mint())] pub fn mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + witness_data: Option>, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + + let collection_config = Self::get_collection_config(&collection)?; + let item_settings = collection_config.mint_settings.default_item_settings; + let item_config = ItemConfig { settings: item_settings }; + + Self::do_mint( + collection, + item, + caller.clone(), + item_config, + false, + |collection_details, collection_config| { + let mint_settings = collection_config.mint_settings; + let now = frame_system::Pallet::::block_number(); + + if let Some(start_block) = mint_settings.start_block { + ensure!(start_block <= now, Error::::MintNotStated); + } + if let Some(end_block) = mint_settings.end_block { + ensure!(end_block >= now, Error::::MintEnded); + } + + match mint_settings.mint_type { + MintType::Issuer => { + ensure!( + Self::has_role(&collection, &caller, CollectionRole::Issuer), + Error::::NoPermission + ) + }, + MintType::HolderOf(collection_id) => { + let correct_witness = match witness_data { + Some(MintWitness { owner_of_item }) => + Account::::contains_key(( + &caller, + &collection_id, + &owner_of_item, + )), + None => false, + }; + ensure!(correct_witness, Error::::BadWitness) + }, + _ => {}, + } + + if let Some(price) = mint_settings.price { + T::Currency::transfer( + &caller, + &collection_details.owner, + price, + ExistenceRequirement::KeepAlive, + )?; + } + + Ok(()) + }, + ) + } + + /// Mint an item of a particular collection from a privileged origin. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the + /// Issuer of the `collection`. + /// + /// - `collection`: The collection of the item to be minted. + /// - `item`: An identifier of the new item. + /// - `owner`: An owner of the minted item. + /// - `item_config`: A config of the new item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_mint())] + pub fn force_mint( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, owner: AccountIdLookupOf, - config: ItemConfig, + item_config: ItemConfig, ) -> DispatchResult { - let origin = ensure_signed(origin)?; + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let owner = T::Lookup::lookup(owner)?; - ensure!( - Self::has_role(&collection, &origin, CollectionRole::Issuer), - Error::::NoPermission - ); - Self::do_mint(collection, item, owner, config) + if let Some(check_origin) = maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Issuer), + Error::::NoPermission + ); + } + Self::do_mint(collection, item, owner, item_config, true, |_, _| Ok(())) } /// Destroy a single item. @@ -818,7 +888,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut collection_details = + let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); @@ -834,11 +904,11 @@ pub mod pallet { Some(x) => x, None => continue, }; - let old = details.deposit; + let old = details.deposit.amount; if old > deposit { - T::Currency::unreserve(&collection_details.owner, old - deposit); + T::Currency::unreserve(&details.deposit.account, old - deposit); } else if deposit > old { - if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() { + if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { // NOTE: No alterations made to collection_details in this iteration so far, // so this is OK to do. continue @@ -846,13 +916,10 @@ pub mod pallet { } else { continue } - collection_details.total_deposit.saturating_accrue(deposit); - collection_details.total_deposit.saturating_reduce(old); - details.deposit = deposit; + details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); successful.push(item); } - Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::::Redeposited { collection, @@ -907,7 +974,7 @@ pub mod pallet { /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// /// - `collection`: The collection to be locked. - /// - `lock_config`: The config with the settings to be locked. + /// - `lock_settings`: The settings to be locked. /// /// Note: it's possible to only lock(set) the setting, but not to unset it. /// Emits `CollectionLocked`. @@ -917,10 +984,10 @@ pub mod pallet { pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, - lock_config: CollectionConfig, + lock_settings: CollectionSettings, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::do_lock_collection(origin, collection, lock_config) + Self::do_lock_collection(origin, collection, lock_settings) } /// Change the Owner of a collection. @@ -942,37 +1009,13 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - - let acceptable_collection = OwnershipAcceptance::::get(&owner); - ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - if details.owner == owner { - return Ok(()) - } - - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &owner, - details.total_deposit, - Reserved, - )?; - CollectionAccount::::remove(&details.owner, &collection); - CollectionAccount::::insert(&owner, &collection, ()); - details.owner = owner.clone(); - OwnershipAcceptance::::remove(&owner); - - Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); - Ok(()) - }) + Self::do_transfer_ownership(origin, collection, owner) } /// Change the Issuer, Admin and Freezer of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. /// /// - `collection`: The collection whose team should be changed. /// - `issuer`: The new Issuer of this collection. @@ -990,35 +1033,60 @@ pub mod pallet { admin: AccountIdLookupOf, freezer: AccountIdLookupOf, ) -> DispatchResult { - let origin = ensure_signed(origin)?; + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; + Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) + } - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer.clone(), CollectionRole::Issuer), - (admin.clone(), CollectionRole::Admin), - (freezer.clone(), CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } + /// Change the Owner of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `owner`: The new Owner of this collection. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_collection_owner())] + pub fn force_collection_owner( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let new_owner = T::Lookup::lookup(owner)?; + Self::do_force_collection_owner(collection, new_owner) + } - Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); - Ok(()) - }) + /// Change the config of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `config`: The new config of this collection. + /// + /// Emits `CollectionConfigChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_collection_config())] + pub fn force_collection_config( + origin: OriginFor, + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_collection_config(collection, config) } /// Approve an item to be transferred by a delegated third-party account. /// - /// Origin must be Signed and must be the owner of the `item`. + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `item`. /// /// - `collection`: The collection of the item to be approved for delegated transfer. /// - `item`: The item to be approved for delegated transfer. @@ -1037,55 +1105,17 @@ pub mod pallet { delegate: AccountIdLookupOf, maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Approvals), - Error::::MethodDisabled - ); - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - let now = frame_system::Pallet::::block_number(); - let deadline = maybe_deadline.map(|d| d.saturating_add(now)); - - details - .approvals - .try_insert(delegate.clone(), deadline) - .map_err(|_| Error::::ReachedApprovalLimit)?; - Item::::insert(&collection, &item, &details); - - Self::deposit_event(Event::ApprovedTransfer { + Self::do_approve_transfer( + maybe_check_origin, collection, item, - owner: details.owner, delegate, - deadline, - }); - - Ok(()) + maybe_deadline, + ) } /// Cancel one of the transfer approvals for a specific item. @@ -1110,43 +1140,11 @@ pub mod pallet { item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let maybe_deadline = - details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; - - let is_past_deadline = if let Some(deadline) = maybe_deadline { - let now = frame_system::Pallet::::block_number(); - now > *deadline - } else { - false - }; - - if !is_past_deadline { - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - } - - details.approvals.remove(&delegate); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::ApprovalCancelled { - collection, - item, - owner: details.owner, - delegate, - }); - - Ok(()) + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) } /// Cancel all the approvals of a specific item. @@ -1169,86 +1167,10 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - details.approvals.clear(); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::AllApprovalsCancelled { - collection, - item, - owner: details.owner, - }); - - Ok(()) - } - - /// Alter the attributes of a given collection. - /// - /// Origin must be `ForceOrigin`. - /// - /// - `collection`: The identifier of the collection. - /// - `owner`: The new Owner of this collection. - /// - `issuer`: The new Issuer of this collection. - /// - `admin`: The new Admin of this collection. - /// - `freezer`: The new Freezer of this collection. - /// - `config`: Collection's config. - /// - /// Emits `CollectionStatusChanged` with the identity of the item. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_collection_status())] - pub fn force_collection_status( - origin: OriginFor, - collection: T::CollectionId, - owner: AccountIdLookupOf, - issuer: AccountIdLookupOf, - admin: AccountIdLookupOf, - freezer: AccountIdLookupOf, - config: CollectionConfig, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - - Collection::::try_mutate(collection, |maybe_collection| { - let mut collection_info = - maybe_collection.take().ok_or(Error::::UnknownCollection)?; - let old_owner = collection_info.owner; - let new_owner = T::Lookup::lookup(owner)?; - collection_info.owner = new_owner.clone(); - *maybe_collection = Some(collection_info); - CollectionAccount::::remove(&old_owner, &collection); - CollectionAccount::::insert(&new_owner, &collection, ()); - CollectionConfigOf::::insert(&collection, config); - - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer, CollectionRole::Issuer), - (admin, CollectionRole::Admin), - (freezer, CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::CollectionStatusChanged { collection }); - Ok(()) - }) + Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } /// Disallows changing the metadata of attributes of the item. @@ -1274,8 +1196,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_lock_item_properties( maybe_check_owner, collection, @@ -1310,61 +1231,10 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Attributes), - Error::::MethodDisabled - ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - match maybe_item { - None => { - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::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::::LockedItemAttributes); - }, - }; - - let attribute = Attribute::::get((collection, maybe_item, &key)); - if attribute.is_none() { - collection_details.attributes.saturating_inc(); - } - let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((key.len() + value.len()) as u32).into()) - .saturating_add(T::AttributeDepositBase::get()); - } - collection_details.total_deposit.saturating_accrue(deposit); - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - - Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); - Ok(()) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) } /// Clear an attribute for a collection or item. @@ -1390,44 +1260,8 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - if maybe_check_owner.is_some() { - match maybe_item { - None => { - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config - .is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - // NOTE: if the item was previously burned, the ItemSettings record might - // not exists. In that case, we allow to clear the attribute. - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| { - c.has_disabled_setting(ItemSetting::UnlockedAttributes) - }); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - } - - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { - collection_details.attributes.saturating_dec(); - collection_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&collection_details.owner, deposit); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); - } - Ok(()) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) } /// Set the metadata for an item. @@ -1455,51 +1289,8 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - maybe_check_owner.is_none() || - item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), - Error::::LockedItemMetadata - ); - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_none() { - collection_details.item_metadatas.saturating_inc(); - } - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((data.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - } - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - collection_details.total_deposit.saturating_accrue(deposit); - - *metadata = Some(ItemMetadata { deposit, data: data.clone() }); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data }); - Ok(()) - }) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_item_metadata(maybe_check_owner, collection, item, data) } /// Clear the metadata for an item. @@ -1523,32 +1314,8 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - // NOTE: if the item was previously burned, the ItemSettings record might not exists - let is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); - - ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_some() { - collection_details.item_metadatas.saturating_dec(); - } - let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; - T::Currency::unreserve(&collection_details.owner, deposit); - collection_details.total_deposit.saturating_reduce(deposit); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataCleared { collection, item }); - Ok(()) - }) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_item_metadata(maybe_check_owner, collection, item) } /// Set the metadata for a collection. @@ -1574,46 +1341,8 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - let mut details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && - 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.total_deposit.saturating_accrue(deposit); - - Collection::::insert(&collection, details); - - *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); - - Self::deposit_event(Event::CollectionMetadataSet { collection, data }); - Ok(()) - }) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_collection_metadata(maybe_check_owner, collection, data) } /// Clear the metadata for a collection. @@ -1635,27 +1364,8 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; - T::Currency::unreserve(&details.owner, deposit); - Self::deposit_event(Event::CollectionMetadataCleared { collection }); - Ok(()) - }) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_collection_metadata(maybe_check_owner, collection) } /// Set (or reset) the acceptance of ownership for a particular account. @@ -1674,23 +1384,7 @@ pub mod pallet { maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_collection.is_some()) { - (false, true) => { - frame_system::Pallet::::inc_consumers(&who)?; - }, - (true, false) => { - frame_system::Pallet::::dec_consumers(&who); - }, - _ => {}, - } - if let Some(collection) = maybe_collection.as_ref() { - OwnershipAcceptance::::insert(&who, collection); - } else { - OwnershipAcceptance::::remove(&who); - } - Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); - Ok(()) + Self::do_set_accept_ownership(who, maybe_collection) } /// Set the maximum amount of items a collection could have. @@ -1698,8 +1392,6 @@ pub mod pallet { /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of /// the `collection`. /// - /// Note: This function can only succeed once per collection. - /// /// - `collection`: The identifier of the collection to change. /// - `max_supply`: The maximum amount of items a collection could have. /// @@ -1712,24 +1404,33 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - - ensure!( - !CollectionMaxSupply::::contains_key(&collection), - Error::::MaxSupplyAlreadySet - ); - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) + } - CollectionMaxSupply::::insert(&collection, max_supply); - Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); - Ok(()) + /// Update mint settings. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `mint_settings`: The new mint settings. + /// + /// Emits `CollectionMintSettingsUpdated` event when successful. + #[pallet::weight(T::WeightInfo::update_mint_settings())] + pub fn update_mint_settings( + origin: OriginFor, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) } /// Set (or reset) the price for an item. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index d0841ebc1f238..b58c81b1d70f8 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -18,6 +18,7 @@ //! Tests for Nfts pallet. use crate::{mock::*, Event, *}; +use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, @@ -92,12 +93,34 @@ fn events() -> Vec> { result } -fn default_collection_config() -> CollectionConfig { - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) +fn collection_config_from_disabled_settings( + settings: BitFlags, +) -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::from_disabled(settings), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn collection_config_with_all_settings_enabled() -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn default_collection_config() -> CollectionConfigFor { + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) } fn default_item_config() -> ItemConfig { - ItemConfig::all_settings_enabled() + ItemConfig { settings: ItemSettings::all_enabled() } +} + +fn item_config_from_disabled_settings(settings: BitFlags) -> ItemConfig { + ItemConfig { settings: ItemSettings::from_disabled(settings) } } #[test] @@ -112,12 +135,12 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -126,10 +149,11 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); @@ -137,14 +161,19 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); - assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); - assert_eq!(Collection::::get(0).unwrap().items, 2); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, None)); + assert_eq!(items(), vec![(1, 0, 70), (10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 70, 2)); + assert_eq!(Balances::reserved_balance(&2), 1); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); @@ -153,7 +182,7 @@ fn lifecycle_should_work() { assert!(ItemMetadataOf::::contains_key(0, 69)); let w = Nfts::get_destroy_witness(&0).unwrap(); - assert_eq!(w.items, 2); + assert_eq!(w.items, 3); assert_eq!(w.item_metadatas, 2); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); @@ -177,11 +206,11 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -190,10 +219,55 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); + + // validate minting start and end settings + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { start_block: Some(2), end_block: Some(3), ..Default::default() } + )); + + System::set_block_number(1); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), + Error::::MintNotStated + ); + System::set_block_number(4); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), Error::::MintEnded); + + // validate price + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } + )); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, None)); + assert_eq!(Balances::total_balance(&2), 99); + + // validate types + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 1, + MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } + )); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(3), 1, 42, None), Error::::BadWitness); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 1, 42, None), Error::::BadWitness); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, Some(MintWitness { owner_of_item: 42 })), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(2), + 1, + 42, + Some(MintWitness { owner_of_item: 43 }) + )); }); } @@ -201,7 +275,7 @@ fn mint_should_work() { fn transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); @@ -218,12 +292,12 @@ fn transfer_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), @@ -236,7 +310,7 @@ fn transfer_should_work() { fn locking_transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); @@ -244,21 +318,17 @@ fn locking_transfer_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemsNonTransferable ); - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), 0, - 1, - 1, - 1, - 1, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); @@ -268,7 +338,7 @@ fn locking_transfer_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -289,7 +359,7 @@ fn origin_guards_should_work() { Error::::NoPermission ); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, default_item_config()), + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, None), Error::::NoPermission ); assert_noop!( @@ -310,7 +380,7 @@ fn transfer_owner_should_work() { assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( @@ -334,17 +404,22 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_eq!(Balances::reserved_balance(&1), 1); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); - assert_eq!(Balances::total_balance(&2), 57); - assert_eq!(Balances::total_balance(&3), 145); + assert_eq!(Balances::total_balance(&2), 58); + assert_eq!(Balances::total_balance(&3), 144); assert_eq!(Balances::reserved_balance(&2), 0); - assert_eq!(Balances::reserved_balance(&3), 45); + assert_eq!(Balances::reserved_balance(&3), 44); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&2), 1); - // 2's acceptence from before is reset when it became owner, so it cannot be transfered + // 2's acceptance from before is reset when it became an owner, so it cannot be transferred // without a fresh acceptance. assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), @@ -359,7 +434,7 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, None)); assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); @@ -373,12 +448,12 @@ fn set_collection_metadata_should_work() { // Cannot add metadata to unknown item assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), - Error::::UnknownCollection, + Error::::NoConfig, ); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); // Cannot add metadata to unowned item assert_noop!( @@ -412,7 +487,7 @@ fn set_collection_metadata_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()) + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()) )); assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), @@ -451,9 +526,9 @@ fn set_item_metadata_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), @@ -515,9 +590,9 @@ fn set_attribute_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -565,10 +640,10 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -587,7 +662,7 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::UnlockedAttributes.into()) + CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into()) )); let e = Error::::LockedCollectionAttributes; @@ -612,10 +687,10 @@ fn preserve_config_for_frozen_items() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); // if the item is not locked/frozen then the config gets deleted on item burn assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); @@ -624,8 +699,9 @@ fn preserve_config_for_frozen_items() { // lock the item and ensure the config stays unchanged assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, true, true)); - let expect_config = - ItemConfig(ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata); + let expect_config = item_config_from_disabled_settings( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata, + ); let config = ItemConfigOf::::get(0, 0).unwrap(); assert_eq!(config, expect_config); @@ -635,69 +711,72 @@ fn preserve_config_for_frozen_items() { // can't mint with the different config assert_noop!( - Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), Error::::InconsistentItemConfig ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata + ), + ..Default::default() + } + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); }); } #[test] -fn force_collection_status_should_work() { +fn force_update_collection_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), 0, - 1, - 1, - 1, - 1, - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()), + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, None)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 65); - assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); - assert_eq!(Balances::reserved_balance(1), 63); + Balances::make_free_balance_be(&5, 100); + assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, 5)); + assert_eq!(collections(), vec![(5, 0)]); + assert_eq!(Balances::reserved_balance(1), 2); + assert_eq!(Balances::reserved_balance(5), 63); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 42); + assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(5), 0, bvec![0, 42, 50, 69, 100])); + assert_eq!(Balances::reserved_balance(1), 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 21); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 42, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 42); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 0); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 21); + + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(5), 0, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 0); // validate new roles - assert_ok!(Nfts::force_collection_status( - RuntimeOrigin::root(), - 0, - 1, - 2, - 3, - 4, - CollectionConfig::all_settings_enabled(), - )); + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 2, 3, 4)); assert_eq!( CollectionRoleOf::::get(0, 2).unwrap(), CollectionRoles(CollectionRole::Issuer.into()) @@ -711,15 +790,7 @@ fn force_collection_status_should_work() { CollectionRoles(CollectionRole::Freezer.into()) ); - assert_ok!(Nfts::force_collection_status( - RuntimeOrigin::root(), - 0, - 1, - 3, - 2, - 3, - CollectionConfig::all_settings_enabled(), - )); + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 3, 2, 3)); assert_eq!( CollectionRoleOf::::get(0, 2).unwrap(), @@ -739,7 +810,7 @@ fn burn_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); @@ -748,8 +819,8 @@ fn burn_works() { Error::::UnknownCollection ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); assert_eq!(Balances::reserved_balance(1), 2); assert_noop!( @@ -771,7 +842,7 @@ fn burn_works() { fn approval_lifecycle_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_noop!( @@ -788,18 +859,12 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(1), - 1, - collection_id, - 1, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, None)); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -812,7 +877,7 @@ fn approval_lifecycle_works() { fn cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -840,7 +905,7 @@ fn cancel_approval_works() { let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); // approval expires after 2 blocks. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( @@ -859,7 +924,7 @@ fn cancel_approval_works() { fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); let current_block = 1; System::set_block_number(current_block); @@ -884,7 +949,7 @@ fn approving_multiple_accounts_works() { fn approvals_limit_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); for i in 3..13 { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); @@ -906,9 +971,9 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); // the approval expires after the 2nd block. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); @@ -935,7 +1000,7 @@ fn approval_deadline_works() { fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -963,7 +1028,7 @@ fn cancel_approval_works_with_admin() { fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -991,7 +1056,7 @@ fn cancel_approval_works_with_force() { fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); @@ -1026,66 +1091,96 @@ fn max_supply_should_work() { new_test_ext().execute_with(|| { let collection_id = 0; let user_id = 1; - let max_supply = 2; + let max_supply = 1; // validate set_collection_max_supply assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert!(!CollectionMaxSupply::::contains_key(collection_id)); + assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); assert_ok!(Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id), collection_id, max_supply )); - assert_eq!(CollectionMaxSupply::::get(collection_id).unwrap(), max_supply); + assert_eq!( + CollectionConfigOf::::get(collection_id).unwrap().max_supply, + Some(max_supply) + ); assert!(events().contains(&Event::::CollectionMaxSupplySet { collection: collection_id, max_supply, })); - assert_noop!( - Nfts::set_collection_max_supply( - RuntimeOrigin::signed(user_id), - collection_id, - max_supply + 1 - ), - Error::::MaxSupplyAlreadySet - ); - - // validate we can't mint more to max supply - assert_ok!(Nfts::mint( + assert_ok!(Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id), collection_id, - 0, - user_id, - default_item_config() + max_supply + 1 )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - 1, - user_id, - default_item_config() + CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into()) )); assert_noop!( - Nfts::mint( + Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id), collection_id, - 2, - user_id, - default_item_config() + max_supply + 2 ), + Error::::MaxSupplyLocked + ); + + // validate we can't mint more to max supply + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, None)); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, None), Error::::MaxSupplyReached ); + }); +} + +#[test] +fn mint_settings_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::all_enabled().get_disabled() + ); - // validate we remove the CollectionMaxSupply record when we destroy the collection - assert_ok!(Nfts::destroy( - RuntimeOrigin::signed(user_id), - collection_id, - Nfts::get_destroy_witness(&collection_id).unwrap() + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig { + mint_settings: MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::Transferable | ItemSetting::UnlockedMetadata + ), + ..Default::default() + }, + ..default_collection_config() + } )); - assert!(!CollectionMaxSupply::::contains_key(collection_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::from_disabled(ItemSetting::Transferable | ItemSetting::UnlockedMetadata) + .get_disabled() + ); }); } @@ -1099,20 +1194,8 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config() - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_2, - user_id, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1164,18 +1247,12 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1204,27 +1281,9 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_1, - user_1, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_2, - user_1, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_3, - user_1, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, None)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1308,7 +1367,7 @@ fn buy_item_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_1), collection_id, - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1322,21 +1381,17 @@ fn buy_item_should_work() { ); // unlock the collection - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), collection_id, - user_1, - user_1, - user_1, - user_1, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); // lock the transfer assert_ok!(Nfts::lock_item_transfer( RuntimeOrigin::signed(user_1), collection_id, - item_3 + item_3, )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1413,20 +1468,8 @@ fn create_cancel_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_2, - user_id, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None,)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None,)); // validate desired item and the collection exists assert_noop!( @@ -1567,17 +1610,16 @@ fn claim_swap_should_work() { RuntimeOrigin::signed(user_1), collection_id, item_1, - user_1, - default_item_config(), + None, )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_2, user_2, default_item_config(), )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_3, @@ -1588,10 +1630,9 @@ fn claim_swap_should_work() { RuntimeOrigin::signed(user_1), collection_id, item_4, - user_1, - default_item_config(), + None, )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_5, @@ -1753,7 +1794,7 @@ fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it let config = - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()); + collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into()); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); @@ -1761,7 +1802,7 @@ fn various_collection_settings() { assert!(config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); // no need to call .into() for multiple values - let config = CollectionConfig::disable_settings( + let config = collection_config_from_disabled_settings( CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); @@ -1783,35 +1824,46 @@ fn collection_locking_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); + let lock_config = + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()); + assert_noop!( + Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config.settings, + ), + Error::::WrongSetting + ); + // validate partial lock - let lock_config = CollectionConfig::disable_settings( + let lock_config = collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, ); assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - lock_config, + lock_config.settings, )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); assert_eq!(stored_config, lock_config); // validate full lock - let full_lock_config = CollectionConfig::disable_settings( - CollectionSetting::TransferableItems | - CollectionSetting::UnlockedMetadata | - CollectionSetting::UnlockedAttributes, - ); assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()), + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()), )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + let full_lock_config = collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes, + ); assert_eq!(stored_config, full_lock_config); }); } @@ -1819,7 +1871,7 @@ fn collection_locking_should_work() { #[test] fn pallet_level_feature_flags_should_work() { new_test_ext().execute_with(|| { - Features::set(&PalletFeatures::disable( + Features::set(&PalletFeatures::from_disabled( PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, )); @@ -1829,13 +1881,7 @@ fn pallet_level_feature_flags_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_id, - user_id, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None,)); // PalletFeature::Trading assert_noop!( diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 0122a817229ac..d57f62be97f39 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -36,8 +36,10 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< Option<::BlockNumber>, >::ApprovalsLimit, >; +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; pub(super) type ItemDetailsFor = - ItemDetails<::AccountId, DepositBalanceOf, ApprovalsOf>; + ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; pub(super) type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; pub(super) type ItemPrice = BalanceOf; @@ -47,6 +49,11 @@ pub(super) type ItemTipOf = ItemTip< ::AccountId, BalanceOf, >; +pub(super) type CollectionConfigFor = CollectionConfig< + BalanceOf, + ::BlockNumber, + >::CollectionId, +>; pub trait Incrementable { fn increment(&self) -> Self; @@ -95,14 +102,23 @@ impl CollectionDetails { /// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] -pub struct ItemDetails { +pub struct ItemDetails { /// The owner of this item. pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. pub(super) approvals: Approvals, /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. - pub(super) deposit: DepositBalance, + pub(super) deposit: Deposit, +} + +/// Information about the reserved item deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemDeposit { + /// A depositor account. + pub(super) account: AccountId, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] @@ -182,39 +198,104 @@ pub enum CollectionSetting { 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, } -pub(super) type CollectionSettings = BitFlags; -/// Wrapper type for `CollectionSettings` that implements `Codec`. +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct CollectionConfig(pub CollectionSettings); +pub struct CollectionSettings(pub BitFlags); -impl CollectionConfig { - pub fn all_settings_enabled() -> Self { +impl CollectionSettings { + pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn get_disabled_settings(&self) -> CollectionSettings { + pub fn get_disabled(&self) -> BitFlags { self.0 } + pub fn is_disabled(&self, setting: CollectionSetting) -> bool { + self.0.contains(setting) + } + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); + +/// 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, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum MintType { + /// 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), +} + +#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct MintSettings { + /// Whether anyone can mint or if minters are restricted to some subset. + pub(super) mint_type: MintType, + /// An optional price per mint. + pub(super) price: Option, + /// When the mint starts. + pub(super) start_block: Option, + /// When the mint ends. + pub(super) end_block: Option, + /// Default settings each item will get during the mint. + pub(super) default_item_settings: ItemSettings, +} + +impl Default for MintSettings { + fn default() -> Self { + Self { + mint_type: MintType::Issuer, + price: None, + start_block: None, + end_block: None, + default_item_settings: ItemSettings::all_enabled(), + } + } +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owner_of_item: ItemId, +} + +#[derive( + Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, +)] +pub struct CollectionConfig { + /// Collection's settings. + pub(super) settings: CollectionSettings, + /// Collection's max supply. + pub(super) max_supply: Option, + /// Default settings each item will get during the mint. + pub(super) mint_settings: MintSettings, +} + +impl CollectionConfig { pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { - !self.get_disabled_settings().contains(setting) + !self.settings.is_disabled(setting) } pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { - self.get_disabled_settings().contains(setting) - } - pub fn disable_settings(settings: CollectionSettings) -> Self { - Self(settings) + self.settings.is_disabled(setting) } pub fn enable_setting(&mut self, setting: CollectionSetting) { - self.0.remove(setting); + self.settings.0.remove(setting); } pub fn disable_setting(&mut self, setting: CollectionSetting) { - self.0.insert(setting); + self.settings.0.insert(setting); } } -impl_codec_bitflags!(CollectionConfig, u64, CollectionSetting); /// Support for up to 64 user-enabled features on an item. #[bitflags] @@ -228,39 +309,53 @@ pub enum ItemSetting { /// Attributes of this item can be modified. UnlockedAttributes, } -pub(super) type ItemSettings = BitFlags; -/// Wrapper type for `ItemSettings` that implements `Codec`. +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct ItemConfig(pub ItemSettings); +pub struct ItemSettings(pub BitFlags); -impl ItemConfig { - pub fn all_settings_enabled() -> Self { +impl ItemSettings { + pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn get_disabled_settings(&self) -> ItemSettings { + pub fn get_disabled(&self) -> BitFlags { self.0 } + pub fn is_disabled(&self, setting: ItemSetting) -> bool { + self.0.contains(setting) + } + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(ItemSettings, u64, ItemSetting); + +#[derive( + Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo, +)] +pub struct ItemConfig { + /// Item's settings. + pub(super) settings: ItemSettings, +} + +impl ItemConfig { pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { - !self.get_disabled_settings().contains(setting) + !self.settings.is_disabled(setting) } pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { - self.get_disabled_settings().contains(setting) + self.settings.is_disabled(setting) } pub fn has_disabled_settings(&self) -> bool { - !self.get_disabled_settings().is_empty() - } - pub fn disable_settings(settings: ItemSettings) -> Self { - Self(settings) + !self.settings.get_disabled().is_empty() } pub fn enable_setting(&mut self, setting: ItemSetting) { - self.0.remove(setting); + self.settings.0.remove(setting); } pub fn disable_setting(&mut self, setting: ItemSetting) { - self.0.insert(setting); + self.settings.0.insert(setting); } } -impl_codec_bitflags!(ItemConfig, u64, ItemSetting); /// Support for up to 64 system-enabled features on a collection. #[bitflags] @@ -275,8 +370,6 @@ pub enum PalletFeature { Approvals, /// Allow/disallow atomic items swap. Swaps, - /// Allow/disallow public mints. - PublicMints, } /// Wrapper type for `BitFlags` that implements `Codec`. @@ -287,7 +380,7 @@ impl PalletFeatures { pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn disable(features: BitFlags) -> Self { + pub fn from_disabled(features: BitFlags) -> Self { Self(features) } pub fn is_enabled(&self, feature: PalletFeature) -> bool { diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 5f6ee43a09ffe..f254726ca19f2 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -50,6 +50,7 @@ pub trait WeightInfo { fn force_create() -> Weight; fn destroy(n: u32, m: u32, a: u32, ) -> Weight; fn mint() -> Weight; + fn force_mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; fn redeposit(i: u32, ) -> Weight; @@ -58,7 +59,8 @@ pub trait WeightInfo { fn lock_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_collection_status() -> Weight; + fn force_collection_owner() -> Weight; + fn force_collection_config() -> Weight; fn lock_item_properties() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; @@ -71,6 +73,7 @@ pub trait WeightInfo { fn clear_all_transfer_approvals() -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; + fn update_mint_settings() -> Weight; fn set_price() -> Weight; fn buy_item() -> Weight; fn pay_tips(n: u32, ) -> Weight; @@ -137,6 +140,17 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemConfigOf (r:0 w:1) @@ -211,7 +225,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - fn force_collection_status() -> Weight { + fn force_collection_owner() -> Weight { + Weight::from_ref_time(28_468_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { Weight::from_ref_time(28_468_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) @@ -309,6 +331,13 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + fn update_mint_settings() -> Weight { + Weight::from_ref_time(26_358_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) @@ -419,6 +448,17 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemConfigOf (r:0 w:1) @@ -493,7 +533,15 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - fn force_collection_status() -> Weight { + fn force_collection_owner() -> Weight { + Weight::from_ref_time(28_468_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { Weight::from_ref_time(28_468_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) @@ -591,6 +639,13 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + fn update_mint_settings() -> Weight { + Weight::from_ref_time(26_358_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 850195852cf72..4f610d9b80a05 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -78,7 +78,12 @@ pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + fn mint_into( + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + _deposit_collection_owner: bool, + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -164,8 +169,19 @@ impl< ItemConfig, > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { - >::mint_into(&A::get(), item, who, config) + fn mint_into( + item: &Self::ItemId, + who: &AccountId, + config: &ItemConfig, + deposit_collection_owner: bool, + ) -> DispatchResult { + >::mint_into( + &A::get(), + item, + who, + config, + deposit_collection_owner, + ) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { >::burn(&A::get(), item, maybe_check_owner) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index d23e6d67573c7..0aec193f68fcb 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -168,6 +168,7 @@ pub trait Mutate: Inspect { _item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig, + _deposit_collection_owner: bool, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } From 8caecbb98d6af6f554be10675e977593937b1eea Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:19:36 +0200 Subject: [PATCH 21/60] [Uniques V2] Smart attributes (#12702) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Change the format of CollectionConfig to store more data * Move max supply to the CollectionConfig and allow to change it * Remove ItemConfig from the mint() function and use the one set in mint settings * Add different mint options * Allow to change the mint settings * Add a force_mint() method * Check mint params * Some optimisations * Cover with tests * Remove merge artifacts * Chore * Use the new has_role() method * Rework item deposits * More tests * Refactoring * Address comments * Refactor lock_collection() * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Private => Issuer * Add more tests * Fix benchmarks * Add benchmarks for new methods * [Uniques v2] Refactoring (#12570) * Move do_set_price() and do_buy_item() to buy_sell.rs * Move approvals to feature file * Move metadata to feature files * Move the rest of methods to feature files * Remove artifacts * Smart attributes * Split force_collection_status into 2 methods * Fix benchmarks * Fix benchmarks * Update deps * Fix merge artifact * Weights + benchmarks + docs * Change params order * Chore * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update docs * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Add PalletId * Chore * Add tests * More tests * Add doc * Update errors snapshots * Ensure we track the owner_deposit field correctly Co-authored-by: command-bot <> Co-authored-by: Squirrel --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/src/benchmarking.rs | 106 +++- frame/nfts/src/features/attributes.rs | 292 +++++++++-- .../src/features/create_delete_collection.rs | 17 +- frame/nfts/src/features/create_delete_item.rs | 1 + frame/nfts/src/features/metadata.rs | 10 +- frame/nfts/src/features/transfer.rs | 5 +- frame/nfts/src/impl_nonfungibles.rs | 11 +- frame/nfts/src/lib.rs | 143 +++++- frame/nfts/src/mock.rs | 1 + frame/nfts/src/tests.rs | 464 ++++++++++++++++-- frame/nfts/src/types.rs | 33 +- frame/nfts/src/weights.rs | 57 +++ frame/support/src/lib.rs | 4 +- frame/support/src/traits/tokens.rs | 4 +- frame/support/src/traits/tokens/misc.rs | 16 + .../src/traits/tokens/nonfungible_v2.rs | 35 +- .../src/traits/tokens/nonfungibles_v2.rs | 9 +- ...ev_mode_without_arg_max_encoded_len.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 6 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- 23 files changed, 1083 insertions(+), 145 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index be083efcc0706..5b6cb2ec574d3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1491,6 +1491,7 @@ parameter_types! { pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; pub const ApprovalsLimit: u32 = 20; + pub const ItemAttributesApprovalsLimit: u32 = 20; pub const MaxTips: u32 = 10; pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } @@ -1535,6 +1536,7 @@ impl pallet_nfts::Config for Runtime { type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; type ApprovalsLimit = ApprovalsLimit; + type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; type MaxTips = MaxTips; type MaxDeadlineDuration = MaxDeadlineDuration; type Features = Features; diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 61407abd9f985..5e1b0237ca3ec 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -114,6 +114,7 @@ fn add_item_attribute, I: 'static>( 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(), )); @@ -338,10 +339,38 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - add_item_metadata::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) + verify { + assert_last_event::( + 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::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) verify { - assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); + assert_last_event::( + Event::AttributeSet { + collection, + maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key, + value, + } + .into(), + ); } clear_attribute { @@ -349,9 +378,76 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); add_item_metadata::(item); let (key, ..) = add_item_attribute::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone()) + verify { + assert_last_event::( + Event::AttributeCleared { + collection, + maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key, + }.into(), + ); + } + + approve_item_attributes { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(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::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); + assert_last_event::( + Event::ItemAttributesApprovalAdded { + collection, + item, + delegate: target, + } + .into(), + ); + } + + cancel_item_attributes_approval { + let n in 0 .. 1_000; + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + Nfts::::approve_item_attributes( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + target_lookup.clone(), + )?; + T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + for i in 0..n { + let mut key = vec![0u8; T::KeyLimit::get() as usize]; + let mut s = Vec::from((i as u16).to_be_bytes()); + key.truncate(s.len()); + key.append(&mut s); + + Nfts::::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::( + Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate: target, + } + .into(), + ); } set_metadata { diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index 85c1e0b302d12..0d65a1169323b 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -20,9 +20,10 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { pub(crate) fn do_set_attribute( - maybe_check_owner: Option, + origin: T::AccountId, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { @@ -34,90 +35,273 @@ impl, I: 'static> Pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } + ensure!( + Self::is_valid_namespace( + &origin, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::NoPermission + ); let collection_config = Self::get_collection_config(&collection)?; - match maybe_item { - None => { - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::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::::LockedItemAttributes); + // 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::::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::::LockedItemAttributes); + }, }, - }; + _ => (), + } - let attribute = Attribute::::get((collection, maybe_item, &key)); + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); if attribute.is_none() { collection_details.attributes.saturating_inc(); } - let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - collection_details.total_deposit.saturating_reduce(old_deposit); + + let old_deposit = + attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); + let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); } - collection_details.total_deposit.saturating_accrue(deposit); - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + + // 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 old_deposit.account.is_some() && old_deposit.account != Some(origin.clone()) { + T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount); + T::Currency::reserve(&origin, deposit)?; + } else if deposit > old_deposit.amount { + T::Currency::reserve(&origin, deposit - old_deposit.amount)?; + } else if deposit < old_deposit.amount { + T::Currency::unreserve(&origin, old_deposit.amount - deposit); } - Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + // NOTE: we don't track the depositor in the CollectionOwner namespace as it's always a + // collection's owner. This simplifies the collection's transfer to another owner. + let deposit_owner = match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); + None + }, + _ => Some(origin), + }; + + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: deposit_owner, amount: deposit }), + ); Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); Ok(()) } - pub(crate) fn do_clear_attribute( - maybe_check_owner: Option, + pub(crate) fn do_force_set_attribute( + set_as: Option, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, + value: BoundedVec, ) -> DispatchResult { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + + let attribute = Attribute::::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(); } - if maybe_check_owner.is_some() { - match maybe_item { - None => { - let collection_config = Self::get_collection_config(&collection)?; + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: set_as, amount: Zero::zero() }), + ); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + ) -> DispatchResult { + if let Some((_, deposit)) = + Attribute::::take((collection, maybe_item, &namespace, &key)) + { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + if deposit.account != maybe_check_owner { ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::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(false, |c| c.has_disabled_setting(ItemSetting::UnlockedAttributes)); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - } + Self::is_valid_namespace( + &check_owner, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::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::::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(false, |c| { + c.has_disabled_setting(ItemSetting::UnlockedAttributes) + }); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }, + _ => (), + }; + } - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); - collection_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&collection_details.owner, deposit); + match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_reduce(deposit.amount); + T::Currency::unreserve(&collection_details.owner, deposit.amount); + }, + _ => (), + }; + if let Some(deposit_account) = deposit.account { + T::Currency::unreserve(&deposit_account, deposit.amount); + } Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace }); } Ok(()) } + + 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::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals + .try_insert(delegate.clone()) + .map_err(|_| Error::::ReachedApprovalLimit)?; + + Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate }); + Ok(()) + }) + } + + 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::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals.remove(&delegate); + + let mut attributes: u32 = 0; + let mut deposited: DepositBalanceOf = Zero::zero(); + for (_, (_, deposit)) in Attribute::::drain_prefix(( + &collection, + Some(item), + AttributeNamespace::Account(delegate.clone()), + )) { + attributes.saturating_inc(); + deposited = deposited.saturating_add(deposit.amount); + } + ensure!(attributes <= witness.account_attributes, Error::::BadWitness); + + if !deposited.is_zero() { + T::Currency::unreserve(&delegate, deposited); + } + + Self::deposit_event(Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate, + }); + Ok(()) + }) + } + + fn is_valid_namespace( + origin: &T::AccountId, + namespace: &AttributeNamespace, + collection: &T::CollectionId, + collection_owner: &T::AccountId, + maybe_item: &Option, + ) -> Result { + let mut result = false; + match namespace { + AttributeNamespace::CollectionOwner => result = origin == collection_owner, + AttributeNamespace::ItemOwner => + if let Some(item) = maybe_item { + let item_details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + result = origin == &item_details.owner + }, + AttributeNamespace::Account(account_id) => + if let Some(item) = maybe_item { + let approvals = ItemAttributesApprovalsOf::::get(&collection, &item); + result = account_id == origin && approvals.contains(&origin) + }, + _ => (), + }; + Ok(result) + } } diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs index b9530e88b18cd..86625bf49efb2 100644 --- a/frame/nfts/src/features/create_delete_collection.rs +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -35,7 +35,7 @@ impl, I: 'static> Pallet { collection, CollectionDetails { owner: owner.clone(), - total_deposit: deposit, + owner_deposit: deposit, items: 0, item_metadatas: 0, attributes: 0, @@ -90,12 +90,21 @@ impl, I: 'static> Pallet { PendingSwapOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); Self::clear_roles(&collection)?; - #[allow(deprecated)] - Attribute::::remove_prefix((&collection,), None); + + for (_, (_, deposit)) in Attribute::::drain_prefix((&collection,)) { + if !deposit.amount.is_zero() { + if let Some(account) = deposit.account { + T::Currency::unreserve(&account, deposit.amount); + } + } + } + CollectionAccount::::remove(&collection_details.owner, &collection); - T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + let _ = + ItemAttributesApprovalsOf::::clear_prefix(&collection, witness.items, None); Self::deposit_event(Event::Destroyed { collection }); diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs index 10670f4b10c1c..bae1d02c8ad6b 100644 --- a/frame/nfts/src/features/create_delete_item.rs +++ b/frame/nfts/src/features/create_delete_item.rs @@ -109,6 +109,7 @@ impl, I: 'static> Pallet { Account::::remove((&owner, &collection, &item)); ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); + ItemAttributesApprovalsOf::::remove(&collection, &item); // NOTE: if item's settings are not empty (e.g. item's metadata is locked) // then we keep the record and don't remove it diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs index 0b0a337197d9b..3a12dbe64f2f4 100644 --- a/frame/nfts/src/features/metadata.rs +++ b/frame/nfts/src/features/metadata.rs @@ -46,7 +46,7 @@ impl, I: 'static> Pallet { collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - collection_details.total_deposit.saturating_reduce(old_deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && maybe_check_owner.is_some() @@ -60,7 +60,7 @@ impl, I: 'static> Pallet { } else if deposit < old_deposit { T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - collection_details.total_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_accrue(deposit); *metadata = Some(ItemMetadata { deposit, data: data.clone() }); @@ -93,7 +93,7 @@ impl, I: 'static> Pallet { } let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; T::Currency::unreserve(&collection_details.owner, deposit); - collection_details.total_deposit.saturating_reduce(deposit); + collection_details.owner_deposit.saturating_reduce(deposit); Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::MetadataCleared { collection, item }); @@ -121,7 +121,7 @@ impl, I: 'static> Pallet { CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - details.total_deposit.saturating_reduce(old_deposit); + details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); if maybe_check_owner.is_some() && collection_config.is_setting_enabled(CollectionSetting::DepositRequired) @@ -135,7 +135,7 @@ impl, I: 'static> Pallet { } else if deposit < old_deposit { T::Currency::unreserve(&details.owner, old_deposit - deposit); } - details.total_deposit.saturating_accrue(deposit); + details.owner_deposit.saturating_accrue(deposit); Collection::::insert(&collection, details); diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs index 7ebad853902a9..7d6ae3553a361 100644 --- a/frame/nfts/src/features/transfer.rs +++ b/frame/nfts/src/features/transfer.rs @@ -100,11 +100,12 @@ impl, I: 'static> Pallet { T::Currency::repatriate_reserved( &details.owner, &owner, - details.total_deposit, + details.owner_deposit, Reserved, )?; CollectionAccount::::remove(&details.owner, &collection); CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); OwnershipAcceptance::::remove(&owner); @@ -150,7 +151,7 @@ impl, I: 'static> Pallet { T::Currency::repatriate_reserved( &details.owner, &owner, - details.total_deposit, + details.owner_deposit, Reserved, )?; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index b42147e6687d9..a9e05a6f41ce9 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -49,6 +49,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle fn attribute( collection: &Self::CollectionId, item: &Self::ItemId, + namespace: &AttributeNamespace<::AccountId>, key: &[u8], ) -> Option> { if key.is_empty() { @@ -56,7 +57,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((collection, Some(item), key)).map(|a| a.0.into()) + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) } } @@ -71,7 +72,13 @@ impl, I: 'static> Inspect<::AccountId> for Palle CollectionMetadataOf::::get(collection).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((collection, Option::::None, key)).map(|a| a.0.into()) + Attribute::::get(( + collection, + Option::::None, + AttributeNamespace::CollectionOwner, + key, + )) + .map(|a| a.0.into()) } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 0f3d3c89c2932..8de9f3103e7c2 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -44,11 +44,10 @@ pub mod macros; pub mod weights; use codec::{Decode, Encode}; -use frame_support::{ - traits::{ - tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, - }, - BoundedBTreeMap, +use frame_support::traits::{ + tokens::{AttributeNamespace, Locker}, + BalanceStatus::Reserved, + Currency, EnsureOriginWithArg, ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -156,6 +155,10 @@ pub mod pallet { #[pallet::constant] type ApprovalsLimit: Get; + /// The maximum attributes approvals an item could have. + #[pallet::constant] + type ItemAttributesApprovalsLimit: Get; + /// The max number of tips a user could send. #[pallet::constant] type MaxTips: Get; @@ -271,9 +274,10 @@ pub mod pallet { ( NMapKey, NMapKey>, + NMapKey>, NMapKey>, ), - (BoundedVec, DepositBalanceOf), + (BoundedVec, AttributeDepositOf), OptionQuery, >; @@ -289,6 +293,18 @@ pub mod pallet { OptionQuery, >; + /// Item attribute approvals. + #[pallet::storage] + pub(super) type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemAttributesApprovals, + ValueQuery, + >; + /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented by 1 whenever a new collection is created. #[pallet::storage] @@ -412,12 +428,26 @@ pub mod pallet { maybe_item: Option, key: BoundedVec, value: BoundedVec, + namespace: AttributeNamespace, }, /// Attribute metadata has been cleared for a `collection` or `item`. AttributeCleared { collection: T::CollectionId, maybe_item: Option, key: BoundedVec, + namespace: AttributeNamespace, + }, + /// A new approval to modify item attributes was added. + ItemAttributesApprovalAdded { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + }, + /// A new approval to modify item attributes was removed. + ItemAttributesApprovalRemoved { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, }, /// Ownership acceptance has changed for an account. OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, @@ -867,7 +897,7 @@ pub mod pallet { /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `collection`: The collection to be frozen. + /// - `collection`: The collection of the items to be reevaluated. /// - `items`: The items of the collection whose deposits will be reevaluated. /// /// NOTE: This exists as a best-effort function. Any items which are unknown or @@ -1208,15 +1238,20 @@ pub mod pallet { /// Set an attribute for a collection or item. /// - /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// `collection`. + /// Origin must be Signed and must conform to the namespace ruleset: + /// - `CollectionOwner` namespace could be modified by the `collection` owner only; + /// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item` + /// should be set in that case; + /// - `Account(AccountId)` namespace could be modified only when the `origin` was given a + /// permission to do so; /// - /// If the origin is Signed, then funds of signer are reserved according to the formula: - /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into + /// The funds of `origin` are reserved according to the formula: + /// `AttributeDepositBase + DepositPerByte * (key.len + value.len)` taking into /// account any already reserved funds. /// /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. /// @@ -1228,13 +1263,43 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) - .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) + let origin = ensure_signed(origin)?; + Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value) + } + + /// Force-set an attribute for a collection or item. + /// + /// Origin must be `ForceOrigin`. + /// + /// If the attribute already exists and it was set by another account, the deposit + /// will be returned to the previous owner. + /// + /// - `set_as`: An optional owner of the attribute. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. + /// - `value`: The value to which to set the attribute. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_set_attribute())] + pub fn force_set_attribute( + origin: OriginFor, + set_as: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_set_attribute(set_as, collection, maybe_item, namespace, key, value) } /// Clear an attribute for a collection or item. @@ -1246,6 +1311,7 @@ pub mod pallet { /// /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `maybe_item`: The identifier of the item whose metadata to clear. + /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// /// Emits `AttributeCleared`. @@ -1256,12 +1322,57 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, namespace, key) + } + + /// Approve item's attributes to be changed by a delegated third-party account. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: A collection of the item. + /// - `item`: The item that holds attributes. + /// - `delegate`: The account to delegate permission to change attributes of the item. + /// + /// Emits `ItemAttributesApprovalAdded` on success. + #[pallet::weight(T::WeightInfo::approve_item_attributes())] + pub fn approve_item_attributes( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_item_attributes(origin, collection, item, delegate) + } + + /// Cancel the previously provided approval to change item's attributes. + /// All the previously set attributes by the `delegate` will be removed. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: Collection that the item is contained within. + /// - `item`: The item that holds attributes. + /// - `delegate`: The previously approved account to remove. + /// + /// Emits `ItemAttributesApprovalRemoved` on success. + #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval())] + pub fn cancel_item_attributes_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness) } /// Set the metadata for an item. diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index bbd1625710500..f814b209d5f78 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -105,6 +105,7 @@ impl Config for Test { 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 Features = Features; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index b58c81b1d70f8..1e057a8b58d6d 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -25,6 +25,7 @@ use frame_support::{ traits::{tokens::nonfungibles_v2::Destroy, Currency, Get}, }; use pallet_balances::Error as BalancesError; +use sp_core::bounded::BoundedVec; use sp_std::prelude::*; fn items() -> Vec<(u64, u32, u32)> { @@ -67,11 +68,12 @@ macro_rules! bvec { } } -fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { +fn attributes(collection: u32) -> Vec<(Option, AttributeNamespace, Vec, Vec)> { let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) - .map(|(k, v)| (k.0, k.1.into(), v.0.into())) + .map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into())) .collect(); - s.sort(); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.0); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.2.clone()); s } @@ -81,6 +83,12 @@ fn approvals(collection_id: u32, item_id: u32) -> Vec<(u64, Option)> { s } +fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec { + let approvals = ItemAttributesApprovalsOf::::get(collection_id, item_id); + let s: Vec<_> = approvals.into_iter().collect(); + s +} + fn events() -> Vec> { let result = System::events() .into_iter() @@ -583,7 +591,7 @@ fn set_item_metadata_should_work() { } #[test] -fn set_attribute_should_work() { +fn set_collection_owner_attributes_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); @@ -594,34 +602,73 @@ fn set_attribute_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + bvec![0], + )); assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0]), - (Some(0), bvec![0], bvec![0]), - (Some(0), bvec![1], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 9); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0; 10], + )); assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0; 10]), - (Some(0), bvec![0], bvec![0]), - (Some(0), bvec![1], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 19); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 18); - assert_ok!(Nfts::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + )); assert_eq!( attributes(0), - vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + ] ); assert_eq!(Balances::reserved_balance(1), 16); @@ -633,27 +680,301 @@ fn set_attribute_should_work() { } #[test] -fn set_attribute_should_respect_lock() { +fn set_item_owner_attributes_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + + // can't set for the collection + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + None, + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + // can't set for the non-owned item + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 9); + + // validate an attribute can be updated + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 10], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 18); + + // validate only item's owner (or the root) can remove an attribute + assert_noop!( + Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_eq!(Balances::reserved_balance(2), 15); + + // transfer item + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 0, 3)); + + // validate the attribute are still here & the deposit belongs to the previous owner + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + let key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(2)); + assert_eq!(deposit.amount, 12); + + // on attribute update the deposit should be returned to the previous owner + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 11], + )); + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(3)); + assert_eq!(deposit.amount, 13); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::reserved_balance(3), 13); + + // validate attributes on item deletion + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 0, None)); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 11]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + )); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + )); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::reserved_balance(3), 0); + }); +} + +#[test] +fn set_external_account_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, collection_config_with_all_settings_enabled() )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(1), 0, 0, 2)); + + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(1), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::Account(2), bvec![0], bvec![0]), + (Some(0), AttributeNamespace::Account(2), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 6); + + // remove permission to set attributes + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(1), + 0, + 0, + 2, + CancelAttributesApprovalWitness { account_attributes: 2 }, + )); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(2), 0); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + }); +} + +#[test] +fn set_attribute_should_respect_lock() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled(), + )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0]), - (Some(0), bvec![0], bvec![0]), - (Some(1), bvec![0], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(1), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 11); @@ -666,16 +987,47 @@ fn set_attribute_should_respect_lock() { )); let e = Error::::LockedCollectionAttributes; - assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); let e = Error::::LockedItemAttributes; assert_noop!( - Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + ), e ); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); }); } @@ -1905,8 +2257,9 @@ fn pallet_level_feature_flags_should_work() { RuntimeOrigin::signed(user_id), collection_id, None, + AttributeNamespace::CollectionOwner, + bvec![0], bvec![0], - bvec![0] ), Error::::MethodDisabled ); @@ -1942,3 +2295,58 @@ fn group_roles_by_account_should_work() { assert_eq!(account_to_role, expect); }) } + +#[test] +fn add_remove_item_attributes_approval_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let user_4 = 4; + let collection_id = 0; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, None)); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2]); + + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_3, + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2, user_3]); + + assert_noop!( + Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_4, + ), + Error::::ReachedApprovalLimit + ); + + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + CancelAttributesApprovalWitness { account_attributes: 1 }, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3]); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index d57f62be97f39..c12ae39877d46 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -24,6 +24,7 @@ use enumflags2::{bitflags, BitFlags}; use frame_support::{ pallet_prelude::{BoundedVec, MaxEncodedLen}, traits::Get, + BoundedBTreeMap, BoundedBTreeSet, }; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; @@ -36,8 +37,12 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< Option<::BlockNumber>, >::ApprovalsLimit, >; +pub(super) type ItemAttributesApprovals = + BoundedBTreeSet<::AccountId, >::ItemAttributesApprovalsLimit>; pub(super) type ItemDepositOf = ItemDeposit, ::AccountId>; +pub(super) type AttributeDepositOf = + AttributeDeposit, ::AccountId>; pub(super) type ItemDetailsFor = ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; pub(super) type BalanceOf = @@ -65,9 +70,9 @@ impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); pub struct CollectionDetails { /// Collection's owner. pub(super) owner: AccountId, - /// The total balance deposited for the all storage associated with this collection. - /// Used by `destroy`. - pub(super) total_deposit: DepositBalance, + /// The total balance deposited by the owner for the all storage data associated with this + /// collection. Used by `destroy`. + pub(super) owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. pub(super) items: u32, /// The total number of outstanding item metadata of this collection. @@ -100,6 +105,13 @@ impl CollectionDetails { } } +/// Witness data for items mint transactions. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owner_of_item: ItemId, +} + /// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ItemDetails { @@ -173,6 +185,15 @@ pub struct PendingSwap { pub(super) deadline: Deadline, } +/// Information about the reserved attribute deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct AttributeDeposit { + /// A depositor account. + pub(super) account: Option, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum PriceDirection { Send, @@ -265,9 +286,9 @@ impl Default for MintSettings { - /// Provide the id of the item in a required collection. - pub owner_of_item: ItemId, +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, } #[derive( diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index f254726ca19f2..a7eb3773f2ae8 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -63,7 +63,10 @@ pub trait WeightInfo { fn force_collection_config() -> Weight; fn lock_item_properties() -> Weight; fn set_attribute() -> Weight; + fn force_set_attribute() -> Weight; fn clear_attribute() -> Weight; + fn approve_item_attributes() -> Weight; + fn cancel_item_attributes_approval() -> Weight; fn set_metadata() -> Weight; fn clear_metadata() -> Weight; fn set_collection_metadata() -> Weight; @@ -258,12 +261,39 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { Weight::from_ref_time(52_530_000 as u64) .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn approve_item_attributes() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn cancel_item_attributes_approval() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) @@ -566,12 +596,39 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { Weight::from_ref_time(52_530_000 as u64) .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn approve_item_attributes() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn cancel_item_attributes_approval() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 84e416e50544d..a5d7c36de2c6c 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -115,7 +115,7 @@ pub use sp_runtime::{ self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::TypeId; @@ -127,7 +127,7 @@ pub const LOG_TARGET: &str = "runtime::frame-support"; pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. -#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct PalletId(pub [u8; 8]); impl TypeId for PalletId { diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index b3b3b4b7d90b1..03a24bd3ba9c8 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -28,6 +28,6 @@ pub mod nonfungibles; pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ - AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, - Locker, WithdrawConsequence, WithdrawReasons, + AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, + ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 294d0e89c8b9e..f0b172841aa84 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -17,6 +17,7 @@ //! Miscellaneous types. +use crate::PalletId; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; @@ -126,6 +127,21 @@ pub enum BalanceStatus { Reserved, } +/// Attribute namespaces for non-fungible tokens. +#[derive( + Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] +pub enum AttributeNamespace { + /// An attribute was set by the pallet. + Pallet(PalletId), + /// 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), +} + bitflags::bitflags! { /// Reasons for moving funds out of an account. #[derive(Encode, Decode, MaxEncodedLen)] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 4f610d9b80a05..cd091791821ed 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -25,7 +25,10 @@ //! use. use super::nonfungibles_v2 as nonfungibles; -use crate::{dispatch::DispatchResult, traits::Get}; +use crate::{ + dispatch::DispatchResult, + traits::{tokens::misc::AttributeNamespace, Get}, +}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -42,15 +45,23 @@ pub trait Inspect { /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + fn attribute( + _item: &Self::ItemId, + _namespace: &AttributeNamespace, + _key: &[u8], + ) -> Option> { None } /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { - key.using_encoded(|d| Self::attribute(item, d)) + fn typed_attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::attribute(item, namespace, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -137,11 +148,19 @@ impl< fn owner(item: &Self::ItemId) -> Option { >::owner(&A::get(), item) } - fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { - >::attribute(&A::get(), item, key) + fn attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &[u8], + ) -> Option> { + >::attribute(&A::get(), item, namespace, key) } - fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { - >::typed_attribute(&A::get(), item, key) + fn typed_attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &K, + ) -> Option { + >::typed_attribute(&A::get(), item, namespace, key) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 0aec193f68fcb..5b93ca832d4f1 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -27,7 +27,10 @@ //! Implementations of these traits may be converted to implementations of corresponding //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. -use crate::dispatch::{DispatchError, DispatchResult}; +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::tokens::misc::AttributeNamespace, +}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -58,6 +61,7 @@ pub trait Inspect { fn attribute( _collection: &Self::CollectionId, _item: &Self::ItemId, + _namespace: &AttributeNamespace, _key: &[u8], ) -> Option> { None @@ -70,9 +74,10 @@ pub trait Inspect { fn typed_attribute( collection: &Self::CollectionId, item: &Self::ItemId, + namespace: &AttributeNamespace, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(collection, item, d)) + key.using_encoded(|d| Self::attribute(collection, item, namespace, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index a5ec31a9bb4e7..bb49e11679028 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 42ef5a34e4c30..999d8585c221a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 461d63ebb0d9c..e2870ffb9e86f 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index cce9fa70b3da5..d5b0c3b50a5ac 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 877485dda2084..6b174d13c5778 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From b4ff566c45755c89ae5a61849d87124a2980afe5 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 23 Nov 2022 11:32:40 +0200 Subject: [PATCH 22/60] [Uniques V2] Final improvements (#12736) * Use KeyPrefixIterator instead of Box * Change create_collection() * Restrict from claiming NFTs twice * Update Readme * Remove dead code * Refactoring * Update readme * Fix clippy --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/README.md | 112 +++++++++++------- frame/nfts/src/features/attributes.rs | 14 +++ frame/nfts/src/impl_nonfungibles.rs | 37 ++++-- frame/nfts/src/lib.rs | 49 ++++++-- frame/nfts/src/mock.rs | 3 + frame/nfts/src/tests.rs | 6 + frame/nfts/src/types.rs | 6 + .../src/traits/tokens/nonfungible_v2.rs | 17 ++- .../src/traits/tokens/nonfungibles_v2.rs | 20 +++- 10 files changed, 190 insertions(+), 76 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5b6cb2ec574d3..bf2e7cb9b8c7c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1519,6 +1519,7 @@ impl pallet_uniques::Config for Runtime { parameter_types! { pub Features: PalletFeatures = PalletFeatures::all_enabled(); + pub const NftsPalletId: PalletId = PalletId(*b"py/nfts_"); } impl pallet_nfts::Config for Runtime { @@ -1541,6 +1542,7 @@ impl pallet_nfts::Config for Runtime { type MaxDeadlineDuration = MaxDeadlineDuration; type Features = Features; type WeightInfo = pallet_nfts::weights::SubstrateWeight; + type PalletId = NftsPalletId; #[cfg(feature = "runtime-benchmarks")] type Helper = (); type CreateOrigin = AsEnsureOriginWithArg>; diff --git a/frame/nfts/README.md b/frame/nfts/README.md index 8a91a558b5b5f..7de4b9440e7f5 100644 --- a/frame/nfts/README.md +++ b/frame/nfts/README.md @@ -1,72 +1,100 @@ -# Uniques Module +# NFTs pallet -A simple, secure module for dealing with non-fungible assets. +A pallet for dealing with non-fungible assets. ## Overview -The Uniques module provides functionality for asset management of non-fungible asset classes, including: +The NFTs pallet provides functionality for non-fungible tokens' management, including: -* Asset Issuance -* Asset Transfer -* Asset Destruction +* Collection Creation +* NFT Minting +* NFT Transfers and Atomic Swaps +* NFT Trading methods +* Attributes Management +* NFT Burning -To use it in your runtime, you need to implement the assets [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). +To use it in your runtime, you need to implement [`nfts::Config`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/trait.Config.html). -The supported dispatchable functions are documented in the [`uniques::Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum. +The supported dispatchable functions are documented in the [`nfts::Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum. ### Terminology -* **Asset issuance:** The creation of a new asset instance. -* **Asset transfer:** The action of transferring an asset instance from one account to another. -* **Asset burning:** The destruction of an asset instance. -* **Non-fungible asset:** An asset for which each unit has unique characteristics. There is exactly - one instance of such an asset in existence and there is exactly one owning account. +* **Collection creation:** The creation of a new collection. +* **NFT minting:** The action of creating a new item within a collection. +* **NFT transfer:** The action of sending an item from one account to another. +* **Atomic swap:** The action of exchanging items between accounts without needing a 3rd party service. +* **NFT burning:** The destruction of an item. +* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly + one instance of such an item in existence and there is exactly one owning account (though that owning account could be a proxy account or multi-sig account). +* **Soul Bound NFT:** An item that is non-transferable from the account which it is minted into. ### Goals -The Uniques pallet in Substrate is designed to make the following possible: +The NFTs pallet in Substrate is designed to make the following possible: -* Allow accounts to permissionlessly create asset classes (collections of asset instances). -* Allow a named (permissioned) account to mint and burn unique assets within a class. -* Move asset instances between accounts permissionlessly. -* Allow a named (permissioned) account to freeze and unfreeze unique assets within a - class or the entire class. -* Allow the owner of an asset instance to delegate the ability to transfer the asset to some +* Allow accounts to permissionlessly create nft collections. +* Allow a named (permissioned) account to mint and burn unique items within a collection. +* Move items between accounts permissionlessly. +* Allow a named (permissioned) account to freeze and unfreeze items within a + collection or the entire collection. +* Allow the owner of an item to delegate the ability to transfer the item to some named third-party. +* Allow third-parties to store information in an NFT _without_ owning it (Eg. save game state). ## Interface ### Permissionless dispatchables -* `create`: Create a new asset class by placing a deposit. -* `transfer`: Transfer an asset instance to a new owner. -* `redeposit`: Update the deposit amount of an asset instance, potentially freeing funds. -* `approve_transfer`: Name a delegate who may authorise a transfer. + +* `create`: Create a new collection by placing a deposit. +* `mint`: Mint a new item within a collection (when the minting is public). +* `transfer`: Send an item to a new owner. +* `redeposit`: Update the deposit amount of an item, potentially freeing funds. +* `approve_transfer`: Name a delegate who may authorize a transfer. * `cancel_approval`: Revert the effects of a previous `approve_transfer`. +* `approve_item_attributes`: Name a delegate who may change item's attributes within a namespace. +* `cancel_item_attributes_approval`: Revert the effects of a previous `approve_item_attributes`. +* `set_price`: Set the price for an item. +* `buy_item`: Buy an item. +* `pay_tips`: Pay tips, could be used for paying the creator royalties. +* `create_swap`: Create an offer to swap an NFT for another NFT and optionally some fungibles. +* `cancel_swap`: Cancel previously created swap offer. +* `claim_swap`: Swap items in an atomic way. + ### Permissioned dispatchables -* `destroy`: Destroy an asset class. -* `mint`: Mint a new asset instance within an asset class. -* `burn`: Burn an asset instance within an asset class. -* `freeze`: Prevent an individual asset from being transferred. -* `thaw`: Revert the effects of a previous `freeze`. -* `freeze_class`: Prevent all asset within a class from being transferred. -* `thaw_class`: Revert the effects of a previous `freeze_class`. -* `transfer_ownership`: Alter the owner of an asset class, moving all associated deposits. -* `set_team`: Alter the permissioned accounts of an asset class. + +* `destroy`: Destroy a collection. This destroys all the items inside the collection and refunds the deposit. +* `force_mint`: Mint a new item within a collection. +* `burn`: Destroy an item within a collection. +* `lock_item_transfer`: Prevent an individual item from being transferred. +* `unlock_item_transfer`: Revert the effects of a previous `lock_item_transfer`. +* `clear_all_transfer_approvals`: Clears all transfer approvals set by calling the `approve_transfer`. +* `lock_collection`: Prevent all items within a collection from being transferred (making them all `soul bound`). +* `lock_item_properties`: Lock item's metadata or attributes. +* `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. (Ownership of individual items will not be affected.) +* `set_team`: Alter the permissioned accounts of a collection. +* `set_collection_max_supply`: Change the max supply of a collection. +* `update_mint_settings`: Update the minting settings for collection. + ### Metadata (permissioned) dispatchables -* `set_attribute`: Set a metadata attribute of an asset instance or class. -* `clear_attribute`: Remove a metadata attribute of an asset instance or class. -* `set_metadata`: Set general metadata of an asset instance. -* `clear_metadata`: Remove general metadata of an asset instance. -* `set_class_metadata`: Set general metadata of an asset class. -* `clear_class_metadata`: Remove general metadata of an asset class. + +* `set_attribute`: Set a metadata attribute of an item or collection. +* `clear_attribute`: Remove a metadata attribute of an item or collection. +* `set_metadata`: Set general metadata of an item (E.g. an IPFS address of an image url). +* `clear_metadata`: Remove general metadata of an item. +* `set_collection_metadata`: Set general metadata of a collection. +* `clear_collection_metadata`: Remove general metadata of a collection. + ### Force (i.e. governance) dispatchables -* `force_create`: Create a new asset class. -* `force_asset_status`: Alter the underlying characteristics of an asset class. -Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum +* `force_create`: Create a new collection (the collection id can not be chosen). +* `force_collection_owner`: Change collection's owner. +* `force_collection_config`: Change collection's config. +* `force_set_attribute`: Set an attribute. + +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum and its associated variants for documentation on each function. ## Related Modules diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index 0d65a1169323b..48e9c31d2a9bb 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -304,4 +304,18 @@ impl, I: 'static> Pallet { }; Ok(result) } + + /// A helper method to construct attribute's key. + pub fn construct_attribute_key( + key: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(key).map_err(|_| Error::::IncorrectData)?) + } + + /// A helper method to construct attribute's value. + pub fn construct_attribute_value( + value: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(value).map_err(|_| Error::::IncorrectData)?) + } } diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index a9e05a6f41ce9..574d256a7705b 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -20,6 +20,7 @@ use super::*; use frame_support::{ ensure, + storage::KeyPrefixIterator, traits::{tokens::nonfungibles_v2::*, Get}, BoundedSlice, }; @@ -104,24 +105,28 @@ impl, I: 'static> Create<::AccountId, Collection { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( - collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, config: &CollectionConfigFor, - ) -> DispatchResult { + ) -> Result { // DepositRequired can be disabled by calling the force_create() only ensure!( !config.has_disabled_setting(CollectionSetting::DepositRequired), Error::::WrongSetting ); + + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + Self::do_create_collection( - *collection, + collection, who.clone(), admin.clone(), *config, T::CollectionDeposit::get(), - Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, - ) + Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + )?; + Ok(collection) } } @@ -186,25 +191,31 @@ impl, I: 'static> Transfer for Pallet { } impl, I: 'static> InspectEnumerable for Pallet { + type CollectionsIterator = KeyPrefixIterator<>::CollectionId>; + type ItemsIterator = KeyPrefixIterator<>::ItemId>; + type OwnedIterator = + KeyPrefixIterator<(>::CollectionId, >::ItemId)>; + type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; + /// Returns an iterator of the collections in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn collections() -> Box> { - Box::new(CollectionMetadataOf::::iter_keys()) + fn collections() -> Self::CollectionsIterator { + Collection::::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) -> Box> { - Box::new(ItemMetadataOf::::iter_key_prefix(collection)) + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator { + Item::::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) -> Box> { - Box::new(Account::::iter_key_prefix((who,))) + fn owned(who: &T::AccountId) -> Self::OwnedIterator { + Account::::iter_key_prefix((who,)) } /// Returns an iterator of the items of `collection` owned by `who`. @@ -213,7 +224,7 @@ impl, I: 'static> InspectEnumerable for Pallet fn owned_in_collection( collection: &Self::CollectionId, who: &T::AccountId, - ) -> Box> { - Box::new(Account::::iter_key_prefix((who, collection))) + ) -> Self::OwnedInCollectionIterator { + Account::::iter_key_prefix((who, collection)) } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8de9f3103e7c2..f4d157d1d1cda 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -65,7 +65,7 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement, PalletId}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -171,6 +171,10 @@ pub mod pallet { #[pallet::constant] type Features: Get; + /// The pallet's id. + #[pallet::constant] + type PalletId: Get; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -583,6 +587,10 @@ pub mod pallet { MintNotStated, /// Mint has already ended. MintEnded, + /// The provided Item was already used for claiming. + AlreadyClaimed, + /// The provided data is incorrect. + IncorrectData, } #[pallet::call] @@ -756,16 +764,35 @@ pub mod pallet { ) }, MintType::HolderOf(collection_id) => { - let correct_witness = match witness_data { - Some(MintWitness { owner_of_item }) => - Account::::contains_key(( - &caller, - &collection_id, - &owner_of_item, - )), - None => false, - }; - ensure!(correct_witness, Error::::BadWitness) + let MintWitness { owner_of_item } = + witness_data.ok_or(Error::::BadWitness)?; + + let has_item = Account::::contains_key(( + &caller, + &collection_id, + &owner_of_item, + )); + ensure!(has_item, Error::::BadWitness); + + let attribute_key = Self::construct_attribute_key( + PalletAttributes::::UsedToClaim(collection) + .encode(), + )?; + + let key = ( + &collection_id, + Some(owner_of_item), + AttributeNamespace::Pallet(T::PalletId::get()), + &attribute_key, + ); + let already_claimed = Attribute::::contains_key(key.clone()); + ensure!(!already_claimed, Error::::AlreadyClaimed); + + let value = Self::construct_attribute_value(vec![0])?; + Attribute::::insert( + key, + (value, AttributeDeposit { account: None, amount: Zero::zero() }), + ); }, _ => {}, } diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index f814b209d5f78..78aebb9471481 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -23,6 +23,7 @@ use crate as pallet_nfts; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, + PalletId, }; use sp_core::H256; use sp_runtime::{ @@ -86,6 +87,7 @@ impl pallet_balances::Config for Test { parameter_types! { pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); + pub const NftsPalletId: PalletId = PalletId(*b"py/nfts_"); } impl Config for Test { @@ -110,6 +112,7 @@ impl Config for Test { type MaxDeadlineDuration = ConstU64<10000>; type Features = Features; type WeightInfo = (); + type PalletId = NftsPalletId; #[cfg(feature = "runtime-benchmarks")] type Helper = (); } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 1e057a8b58d6d..7cbd7ff6c36f7 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -276,6 +276,12 @@ fn mint_should_work() { 42, Some(MintWitness { owner_of_item: 43 }) )); + + // can't mint twice + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 46, Some(MintWitness { owner_of_item: 43 })), + Error::::AlreadyClaimed + ); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index c12ae39877d46..5f13fb72eb33f 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -291,6 +291,12 @@ pub struct CancelAttributesApprovalWitness { pub account_attributes: u32, } +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum PalletAttributes { + /// Marks an item as being used in order to claim another item. + UsedToClaim(CollectionId), +} + #[derive( Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, )] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index cd091791821ed..ab0e72b3c8286 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -76,11 +76,16 @@ pub trait Inspect { /// Interface for enumerating items in existence or owned by a given account over a collection /// of NFTs. pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// Returns an iterator of the items within a `collection` in existence. - fn items() -> Box>; + fn items() -> Self::ItemsIterator; /// Returns an iterator of the items of all collections owned by `who`. - fn owned(who: &AccountId) -> Box>; + fn owned(who: &AccountId) -> Self::OwnedIterator; } /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have @@ -173,10 +178,14 @@ impl< AccountId, > InspectEnumerable for ItemOf { - fn items() -> Box> { + type ItemsIterator = >::ItemsIterator; + type OwnedIterator = + >::OwnedInCollectionIterator; + + fn items() -> Self::ItemsIterator { >::items(&A::get()) } - fn owned(who: &AccountId) -> Box> { + fn owned(who: &AccountId) -> Self::OwnedIterator { >::owned_in_collection(&A::get(), who) } } diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 5b93ca832d4f1..09b4793832d7e 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -110,31 +110,39 @@ pub trait Inspect { /// Interface for enumerating items in existence or owned by a given account over many collections /// of NFTs. pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::collections`]. + type CollectionsIterator: Iterator; + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// The iterator type for [`Self::owned_in_collection`]. + type OwnedInCollectionIterator: Iterator; + /// Returns an iterator of the collections in existence. - fn collections() -> Box>; + fn collections() -> Self::CollectionsIterator; /// Returns an iterator of the items of a `collection` in existence. - fn items(collection: &Self::CollectionId) -> Box>; + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator; /// Returns an iterator of the items of all collections owned by `who`. - fn owned(who: &AccountId) -> Box>; + fn owned(who: &AccountId) -> Self::OwnedIterator; /// Returns an iterator of the items of `collection` owned by `who`. fn owned_in_collection( collection: &Self::CollectionId, who: &AccountId, - ) -> Box>; + ) -> Self::OwnedInCollectionIterator; } /// Trait for providing the ability to create collections of nonfungible items. pub trait Create: Inspect { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( - collection: &Self::CollectionId, who: &AccountId, admin: &AccountId, config: &CollectionConfig, - ) -> DispatchResult; + ) -> Result; } /// Trait for providing the ability to destroy collections of nonfungible items. From 4c1b7f763811a9392eaa7d21d8730e08f7b259ed Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 23 Nov 2022 16:28:20 +0200 Subject: [PATCH 23/60] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index f4d157d1d1cda..ea379c4a5aa0b 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -310,7 +310,7 @@ pub mod pallet { >; /// Stores the `CollectionId` that is going to be used for the next collection. - /// This gets incremented by 1 whenever a new collection is created. + /// This gets incremented whenever a new collection is created. #[pallet::storage] pub(super) type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; From beb8caa3c35760dd972ddb0400bf10914959c32a Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Fri, 25 Nov 2022 15:19:02 +0000 Subject: [PATCH 24/60] ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts --- frame/nfts/src/weights.rs | 936 +++++++++++++++++++++----------------- 1 file changed, 508 insertions(+), 428 deletions(-) diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index a7eb3773f2ae8..67f65b10e98e3 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -7,7 +7,7 @@ // 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 +// 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, @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -32,8 +32,10 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json // --pallet=pallet_nfts // --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/nfts/src/weights.rs // --template=./.maintain/frame-weight-template.hbs @@ -48,7 +50,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(n: u32, m: u32, a: u32, ) -> Weight; + fn destroy(n: u32, ) -> Weight; fn mint() -> Weight; fn force_mint() -> Weight; fn burn() -> Weight; @@ -66,7 +68,7 @@ pub trait WeightInfo { fn force_set_attribute() -> Weight; fn clear_attribute() -> Weight; fn approve_item_attributes() -> Weight; - fn cancel_item_attributes_approval() -> Weight; + fn cancel_item_attributes_approval(n: u32, ) -> Weight; fn set_metadata() -> Weight; fn clear_metadata() -> Weight; fn set_collection_metadata() -> Weight; @@ -89,669 +91,747 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(38_062_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 42_075 nanoseconds. + Weight::from_ref_time(42_614_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_917_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts Asset (r:1 w:0) - // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts ClassMetadataOf (r:0 w:1) + // Minimum execution time: 29_799 nanoseconds. + Weight::from_ref_time(30_511_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionMetadataOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionMaxSupply (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:20) - // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts CollectionAccount (r:0 w:1) + // Storage: Nfts ItemMetadataOf (r:0 w:20) // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(55_419_000 as u64) - // Standard Error: 18_623 - .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - .saturating_add(T::DbWeight::get().writes((5 as u64).saturating_mul(n as u64))) - } - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionMaxSupply (r:1 w:0) + fn destroy(n: u32, ) -> Weight { + // Minimum execution time: 65_846 nanoseconds. + Weight::from_ref_time(66_082_000) + // Standard Error: 27_878 + .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((5_u64).saturating_mul(n.into()))) + } // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(47_947_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Minimum execution time: 58_577 nanoseconds. + Weight::from_ref_time(59_058_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn force_mint() -> Weight { - Weight::from_ref_time(47_947_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Minimum execution time: 56_494 nanoseconds. + Weight::from_ref_time(57_565_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(47_193_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Minimum execution time: 59_393 nanoseconds. + Weight::from_ref_time(60_562_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(7)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(42_305_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 65_852 nanoseconds. + Weight::from_ref_time(66_308_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Asset (r:102 w:102) + // Storage: Nfts Item (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(26_327_000 as u64) - // Standard Error: 10_090 - .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) - } - // Storage: Nfts Class (r:1 w:0) + // Minimum execution time: 25_795 nanoseconds. + Weight::from_ref_time(26_128_000) + // Standard Error: 10_295 + .saturating_add(Weight::from_ref_time(11_202_286).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_transfer() -> Weight { - Weight::from_ref_time(28_194_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 29_090 nanoseconds. + Weight::from_ref_time(29_772_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn unlock_item_transfer() -> Weight { - Weight::from_ref_time(28_821_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_947 nanoseconds. + Weight::from_ref_time(29_559_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - Weight::from_ref_time(25_896_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 26_972 nanoseconds. + Weight::from_ref_time(27_803_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:2) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(32_728_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 32_165 nanoseconds. + Weight::from_ref_time(32_926_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:4) fn set_team() -> Weight { - Weight::from_ref_time(24_805_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 35_375 nanoseconds. + Weight::from_ref_time(35_950_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(5)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) fn force_collection_owner() -> Weight { - Weight::from_ref_time(28_468_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(27_855_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_config() -> Weight { - Weight::from_ref_time(28_468_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 23_990 nanoseconds. + Weight::from_ref_time(24_347_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_properties() -> Weight { - Weight::from_ref_time(27_377_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_481 nanoseconds. + Weight::from_ref_time(28_929_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(53_019_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 54_435 nanoseconds. + Weight::from_ref_time(55_237_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts Attribute (r:1 w:1) fn force_set_attribute() -> Weight { - Weight::from_ref_time(53_019_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 35_254 nanoseconds. + Weight::from_ref_time(35_941_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) fn clear_attribute() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 51_483 nanoseconds. + Weight::from_ref_time(52_915_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) fn approve_item_attributes() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) - fn cancel_item_attributes_approval() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Nfts Class (r:1 w:1) + // Minimum execution time: 27_929 nanoseconds. + Weight::from_ref_time(28_329_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Minimum execution time: 37_217 nanoseconds. + Weight::from_ref_time(37_692_000) + // Standard Error: 7_804 + .saturating_add(Weight::from_ref_time(7_344_173).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts InstanceMetadataOf (r:1 w:1) + // Storage: Nfts ItemMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(48_054_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 47_675 nanoseconds. + Weight::from_ref_time(48_282_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts InstanceMetadataOf (r:1 w:1) + // Storage: Nfts ItemMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(46_590_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 46_062 nanoseconds. + Weight::from_ref_time(46_854_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassMetadataOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(44_281_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 43_847 nanoseconds. + Weight::from_ref_time(44_792_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ClassMetadataOf (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(42_355_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 42_403 nanoseconds. + Weight::from_ref_time(42_811_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(33_170_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_880 nanoseconds. + Weight::from_ref_time(35_737_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn cancel_approval() -> Weight { - Weight::from_ref_time(31_121_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 31_606 nanoseconds. + Weight::from_ref_time(32_339_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(30_133_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 30_626 nanoseconds. + Weight::from_ref_time(31_043_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_421_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 27_276 nanoseconds. + Weight::from_ref_time(28_016_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts CollectionMaxSupply (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(26_358_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_366 nanoseconds. + Weight::from_ref_time(28_719_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts CollectionMaxSupply (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) fn update_mint_settings() -> Weight { - Weight::from_ref_time(26_358_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 27_292 nanoseconds. + Weight::from_ref_time(27_614_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts Item (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(33_607_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_133 nanoseconds. + Weight::from_ref_time(34_510_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(54_511_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 69_501 nanoseconds. + Weight::from_ref_time(70_342_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(6_015_000 as u64) - // Standard Error: 34_307 - .saturating_add(Weight::from_ref_time(4_308_600 as u64).saturating_mul(n as u64)) + // Minimum execution time: 4_754 nanoseconds. + Weight::from_ref_time(11_356_736) + // Standard Error: 38_352 + .saturating_add(Weight::from_ref_time(3_427_961).saturating_mul(n.into())) } - // Storage: Nfts Asset (r:2 w:0) + // Storage: Nfts Item (r:2 w:0) // Storage: Nfts PendingSwapOf (r:0 w:1) fn create_swap() -> Weight { - Weight::from_ref_time(30_330_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 31_371 nanoseconds. + Weight::from_ref_time(32_227_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts PendingSwapOf (r:1 w:1) - // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts Item (r:1 w:0) fn cancel_swap() -> Weight { - Weight::from_ref_time(30_516_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_114 nanoseconds. + Weight::from_ref_time(34_779_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:2 w:2) + // Storage: Nfts Item (r:2 w:2) // Storage: Nfts PendingSwapOf (r:1 w:2) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:2 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:4) // Storage: Nfts ItemPriceOf (r:0 w:2) fn claim_swap() -> Weight { - Weight::from_ref_time(66_191_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(10 as u64)) + // Minimum execution time: 97_965 nanoseconds. + Weight::from_ref_time(98_699_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(11)) } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(39_252_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 42_075 nanoseconds. + Weight::from_ref_time(42_614_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(27_479_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts Asset (r:1 w:0) - // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts ClassMetadataOf (r:0 w:1) + // Minimum execution time: 29_799 nanoseconds. + Weight::from_ref_time(30_511_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionMetadataOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionMaxSupply (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:20) - // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts CollectionAccount (r:0 w:1) + // Storage: Nfts ItemMetadataOf (r:0 w:20) // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(55_419_000 as u64) - // Standard Error: 18_623 - .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) - .saturating_add(RocksDbWeight::get().writes((5 as u64).saturating_mul(n as u64))) - } - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionMaxSupply (r:1 w:0) + fn destroy(n: u32, ) -> Weight { + // Minimum execution time: 65_846 nanoseconds. + Weight::from_ref_time(66_082_000) + // Standard Error: 27_878 + .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(5)) + .saturating_add(RocksDbWeight::get().writes((5_u64).saturating_mul(n.into()))) + } // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(47_947_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Minimum execution time: 58_577 nanoseconds. + Weight::from_ref_time(59_058_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn force_mint() -> Weight { - Weight::from_ref_time(47_947_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts Asset (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:0 w:1) + // Minimum execution time: 56_494 nanoseconds. + Weight::from_ref_time(57_565_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(47_193_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Minimum execution time: 59_393 nanoseconds. + Weight::from_ref_time(60_562_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(7)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(42_305_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 65_852 nanoseconds. + Weight::from_ref_time(66_308_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Asset (r:102 w:102) + // Storage: Nfts Item (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(26_327_000 as u64) - // Standard Error: 10_090 - .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) - } - // Storage: Nfts Class (r:1 w:0) + // Minimum execution time: 25_795 nanoseconds. + Weight::from_ref_time(26_128_000) + // Standard Error: 10_295 + .saturating_add(Weight::from_ref_time(11_202_286).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_transfer() -> Weight { - Weight::from_ref_time(28_194_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 29_090 nanoseconds. + Weight::from_ref_time(29_772_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn unlock_item_transfer() -> Weight { - Weight::from_ref_time(28_821_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_947 nanoseconds. + Weight::from_ref_time(29_559_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - Weight::from_ref_time(25_896_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 26_972 nanoseconds. + Weight::from_ref_time(27_803_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:2) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(32_728_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 32_165 nanoseconds. + Weight::from_ref_time(32_926_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(4)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:4) fn set_team() -> Weight { - Weight::from_ref_time(24_805_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 35_375 nanoseconds. + Weight::from_ref_time(35_950_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(5)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) fn force_collection_owner() -> Weight { - Weight::from_ref_time(28_468_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(27_855_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(3)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_config() -> Weight { - Weight::from_ref_time(28_468_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 23_990 nanoseconds. + Weight::from_ref_time(24_347_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_properties() -> Weight { - Weight::from_ref_time(27_377_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_481 nanoseconds. + Weight::from_ref_time(28_929_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(53_019_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 54_435 nanoseconds. + Weight::from_ref_time(55_237_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts Attribute (r:1 w:1) fn force_set_attribute() -> Weight { - Weight::from_ref_time(53_019_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 35_254 nanoseconds. + Weight::from_ref_time(35_941_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) fn clear_attribute() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 51_483 nanoseconds. + Weight::from_ref_time(52_915_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) fn approve_item_attributes() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) - fn cancel_item_attributes_approval() -> Weight { - Weight::from_ref_time(52_530_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Nfts Class (r:1 w:1) + // Minimum execution time: 27_929 nanoseconds. + Weight::from_ref_time(28_329_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Minimum execution time: 37_217 nanoseconds. + Weight::from_ref_time(37_692_000) + // Standard Error: 7_804 + .saturating_add(Weight::from_ref_time(7_344_173).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts InstanceMetadataOf (r:1 w:1) + // Storage: Nfts ItemMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(48_054_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 47_675 nanoseconds. + Weight::from_ref_time(48_282_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts InstanceMetadataOf (r:1 w:1) + // Storage: Nfts ItemMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(46_590_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 46_062 nanoseconds. + Weight::from_ref_time(46_854_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassMetadataOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(44_281_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 43_847 nanoseconds. + Weight::from_ref_time(44_792_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ClassMetadataOf (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(42_355_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 42_403 nanoseconds. + Weight::from_ref_time(42_811_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(33_170_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_880 nanoseconds. + Weight::from_ref_time(35_737_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn cancel_approval() -> Weight { - Weight::from_ref_time(31_121_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 31_606 nanoseconds. + Weight::from_ref_time(32_339_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Class (r:1 w:0) - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(30_133_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 30_626 nanoseconds. + Weight::from_ref_time(31_043_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(26_421_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 27_276 nanoseconds. + Weight::from_ref_time(28_016_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts CollectionMaxSupply (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(26_358_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_366 nanoseconds. + Weight::from_ref_time(28_719_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts CollectionMaxSupply (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) fn update_mint_settings() -> Weight { - Weight::from_ref_time(26_358_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 27_292 nanoseconds. + Weight::from_ref_time(27_614_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts Item (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(33_607_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_133 nanoseconds. + Weight::from_ref_time(34_510_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - Weight::from_ref_time(54_511_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 69_501 nanoseconds. + Weight::from_ref_time(70_342_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_477_000 as u64) - // Standard Error: 33_188 - .saturating_add(Weight::from_ref_time(4_285_339 as u64).saturating_mul(n as u64)) + // Minimum execution time: 4_754 nanoseconds. + Weight::from_ref_time(11_356_736) + // Standard Error: 38_352 + .saturating_add(Weight::from_ref_time(3_427_961).saturating_mul(n.into())) } - // Storage: Nfts Asset (r:2 w:0) + // Storage: Nfts Item (r:2 w:0) // Storage: Nfts PendingSwapOf (r:0 w:1) fn create_swap() -> Weight { - Weight::from_ref_time(30_330_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 31_371 nanoseconds. + Weight::from_ref_time(32_227_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts PendingSwapOf (r:1 w:1) - // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts Item (r:1 w:0) fn cancel_swap() -> Weight { - Weight::from_ref_time(30_516_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_114 nanoseconds. + Weight::from_ref_time(34_779_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Nfts Asset (r:2 w:2) + // Storage: Nfts Item (r:2 w:2) // Storage: Nfts PendingSwapOf (r:1 w:2) - // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:2 w:0) + // Storage: System Account (r:1 w:1) // Storage: Nfts Account (r:0 w:4) // Storage: Nfts ItemPriceOf (r:0 w:2) fn claim_swap() -> Weight { - Weight::from_ref_time(66_191_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(10 as u64)) + // Minimum execution time: 97_965 nanoseconds. + Weight::from_ref_time(98_699_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(11)) } } From 6e651a89c777bbb6f2bb4a5953eabab7457f1dc9 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 28 Nov 2022 10:59:09 +0200 Subject: [PATCH 25/60] Update docs --- frame/nfts/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index ea379c4a5aa0b..8e012f42aea3a 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1230,16 +1230,20 @@ pub mod pallet { Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } - /// Disallows changing the metadata of attributes of the item. + /// Disallows changing the metadata or attributes of the item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the /// `collection`. /// /// - `collection`: The collection if the `item`. /// - `item`: An item to be locked. - /// - `lock_config`: The config with the settings to be locked. + /// - `lock_metadata`: Specifies whether the metadata should be locked. + /// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace + /// should be locked. + /// + /// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace + /// only. When the metadata or attributes are locked, it won't be possible the unlock them. /// - /// Note: when the metadata or attributes are locked, it won't be possible the unlock them. /// Emits `ItemPropertiesLocked`. /// /// Weight: `O(1)` From b051fd8138d72d17e2e967897fb620ee2d523cbc Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 28 Nov 2022 16:43:27 +0200 Subject: [PATCH 26/60] Typo --- frame/nfts/src/lib.rs | 4 ++-- frame/nfts/src/tests.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8e012f42aea3a..21dd9b59de090 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -584,7 +584,7 @@ pub mod pallet { /// Some roles were not cleared. RolesNotCleared, /// Mint has not started yet. - MintNotStated, + MintNotStarted, /// Mint has already ended. MintEnded, /// The provided Item was already used for claiming. @@ -750,7 +750,7 @@ pub mod pallet { let now = frame_system::Pallet::::block_number(); if let Some(start_block) = mint_settings.start_block { - ensure!(start_block <= now, Error::::MintNotStated); + ensure!(start_block <= now, Error::::MintNotStarted); } if let Some(end_block) = mint_settings.end_block { ensure!(end_block >= now, Error::::MintEnded); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 7cbd7ff6c36f7..998250dc39ef7 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -242,7 +242,7 @@ fn mint_should_work() { System::set_block_number(1); assert_noop!( Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), - Error::::MintNotStated + Error::::MintNotStarted ); System::set_block_number(4); assert_noop!(Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), Error::::MintEnded); From 0556185149004efe49c33edddb9e5d97cd579cae Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 28 Nov 2022 16:43:58 +0200 Subject: [PATCH 27/60] Fix benchmarks --- frame/nfts/src/benchmarking.rs | 12 ++++++++++++ frame/nfts/src/lib.rs | 4 +++- frame/nfts/src/weights.rs | 18 +++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 5e1b0237ca3ec..4e392b147c4e8 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -171,12 +171,24 @@ benchmarks_instance_pallet! { destroy { let n in 0 .. 1_000; + let m in 0 .. 1_000; + let a in 0 .. 1_000; let (collection, caller, caller_lookup) = create_collection::(); add_collection_metadata::(); for i in 0..n { mint_item::(i as u16); + } + for i in 0..m { + if !Item::::contains_key(collection, T::Helper::item(i as u16)) { + mint_item::(i as u16); + } add_item_metadata::(T::Helper::item(i as u16)); + } + for i in 0..a { + if !Item::::contains_key(collection, T::Helper::item(i as u16)) { + mint_item::(i as u16); + } add_item_attribute::(T::Helper::item(i as u16)); } let witness = Collection::::get(collection).unwrap().destroy_witness(); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 21dd9b59de090..0182c54ea41f3 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1393,7 +1393,9 @@ pub mod pallet { /// - `delegate`: The previously approved account to remove. /// /// Emits `ItemAttributesApprovalRemoved` on success. - #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval())] + #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval( + witness.account_attributes + ))] pub fn cancel_item_attributes_approval( origin: OriginFor, collection: T::CollectionId, diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 67f65b10e98e3..2965f335c229e 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -50,7 +50,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(n: u32, ) -> Weight; + fn destroy(n: u32, m: u32, a: u32, ) -> Weight; fn mint() -> Weight; fn force_mint() -> Weight; fn burn() -> Weight; @@ -123,11 +123,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - fn destroy(n: u32, ) -> Weight { + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { // Minimum execution time: 65_846 nanoseconds. Weight::from_ref_time(66_082_000) // Standard Error: 27_878 .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m.into())) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -497,11 +503,17 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - fn destroy(n: u32, ) -> Weight { + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { // Minimum execution time: 65_846 nanoseconds. Weight::from_ref_time(66_082_000) // Standard Error: 27_878 .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m.into())) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a.into())) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(5)) From 992601b0abe266a907b26009be0d3d32120cdfc1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 28 Nov 2022 18:14:27 +0200 Subject: [PATCH 28/60] Add more docs --- frame/nfts/src/types.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 5f13fb72eb33f..4cb92d692d7f7 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -66,6 +66,7 @@ pub trait Incrementable { } impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); +/// Information about the collection. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Collection's owner. @@ -133,6 +134,7 @@ pub struct ItemDeposit { pub(super) 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(DepositBalance: MaxEncodedLen))] @@ -147,6 +149,7 @@ pub struct CollectionMetadata> { pub(super) data: BoundedVec, } +/// Information about the item's metadata. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(StringLimit))] #[codec(mel_bound(DepositBalance: MaxEncodedLen))] @@ -161,6 +164,7 @@ pub struct ItemMetadata> { pub(super) data: BoundedVec, } +/// Information about the tip. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemTip { /// A collection of the item. @@ -173,6 +177,7 @@ pub struct ItemTip { pub(super) amount: Amount, } +/// Information about the pending swap. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct PendingSwap { /// A collection of the item user wants to receive. @@ -194,12 +199,16 @@ pub struct AttributeDeposit { pub(super) amount: DepositBalance, } +/// Specifies whether the tokens will be send or received. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum PriceDirection { + /// Tokens will be send. Send, + /// Tokens will be received. Receive, } +/// Holds the details about the price. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct PriceWithDirection { /// An amount. @@ -259,6 +268,7 @@ pub enum MintType { HolderOf(CollectionId), } +/// Holds the information about minting. #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct MintSettings { /// Whether anyone can mint or if minters are restricted to some subset. @@ -285,18 +295,21 @@ impl Default for MintSettings { /// Marks an item as being used in order to claim another item. UsedToClaim(CollectionId), } +/// Collection's configuration. #[derive( Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, )] @@ -358,6 +371,7 @@ impl ItemSettings { impl_codec_bitflags!(ItemSettings, u64, ItemSetting); +/// Item's configuration. #[derive( Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo, )] From e907e15e0bda28b5c5db90e6a0bce3393fbc59f1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 8 Dec 2022 16:48:24 +0200 Subject: [PATCH 29/60] DepositRequired setting should affect only the attributes within the CollectionOwner namespace --- frame/nfts/src/features/attributes.rs | 4 ++- frame/nfts/src/tests.rs | 51 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index 48e9c31d2a9bb..da663d39a4ef5 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -74,7 +74,9 @@ impl, I: 'static> Pallet { attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) { + 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()); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 998250dc39ef7..1402ee9793fbe 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -938,6 +938,57 @@ fn set_external_account_attributes_should_work() { }); } +#[test] +fn validate_deposit_required_setting() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + + // with the disabled DepositRequired setting, only the collection's owner can set the + // attributes for free. + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(2), 0, 0, 3)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::Account(3), + bvec![2], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::reserved_balance(3), 3); + }); +} + #[test] fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { From 9f27e9140cab1d5ffcb023b1316ab61982ab49ec Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 13 Dec 2022 20:44:40 +0200 Subject: [PATCH 30/60] [NFTs] Implement missed methods to set the attributes from other pallets (#12919) * Implement missed methods to set the attributes from other pallets * Revert snapshots * Update snapshot * Update snapshot --- bin/node/runtime/src/lib.rs | 2 - frame/nfts/src/impl_nonfungibles.rs | 58 +++++++++++++++++++ frame/nfts/src/lib.rs | 8 +-- frame/nfts/src/mock.rs | 3 - frame/nfts/src/tests.rs | 12 +++- frame/support/src/lib.rs | 4 +- frame/support/src/traits/tokens/misc.rs | 3 +- ...ev_mode_without_arg_max_encoded_len.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 6 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- 12 files changed, 83 insertions(+), 25 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6b94f03115c0f..bcf4b24ae122c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1557,7 +1557,6 @@ impl pallet_uniques::Config for Runtime { parameter_types! { pub Features: PalletFeatures = PalletFeatures::all_enabled(); - pub const NftsPalletId: PalletId = PalletId(*b"py/nfts_"); } impl pallet_nfts::Config for Runtime { @@ -1580,7 +1579,6 @@ impl pallet_nfts::Config for Runtime { type MaxDeadlineDuration = MaxDeadlineDuration; type Features = Features; type WeightInfo = pallet_nfts::weights::SubstrateWeight; - type PalletId = NftsPalletId; #[cfg(feature = "runtime-benchmarks")] type Helper = (); type CreateOrigin = AsEnsureOriginWithArg>; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 574d256a7705b..9fa696cd5c5c7 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -178,6 +178,64 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig 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( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| { + >::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( + collection: &Self::CollectionId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| { + >::set_collection_attribute( + collection, k, v, + ) + }) + }) + } } impl, I: 'static> Transfer for Pallet { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 0182c54ea41f3..6267df71a9a20 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -65,7 +65,7 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::ExistenceRequirement, PalletId}; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -171,10 +171,6 @@ pub mod pallet { #[pallet::constant] type Features: Get; - /// The pallet's id. - #[pallet::constant] - type PalletId: Get; - #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -782,7 +778,7 @@ pub mod pallet { let key = ( &collection_id, Some(owner_of_item), - AttributeNamespace::Pallet(T::PalletId::get()), + AttributeNamespace::Pallet, &attribute_key, ); let already_claimed = Attribute::::contains_key(key.clone()); diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 78aebb9471481..f814b209d5f78 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -23,7 +23,6 @@ use crate as pallet_nfts; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, - PalletId, }; use sp_core::H256; use sp_runtime::{ @@ -87,7 +86,6 @@ impl pallet_balances::Config for Test { parameter_types! { pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); - pub const NftsPalletId: PalletId = PalletId(*b"py/nfts_"); } impl Config for Test { @@ -112,7 +110,6 @@ impl Config for Test { type MaxDeadlineDuration = ConstU64<10000>; type Features = Features; type WeightInfo = (); - type PalletId = NftsPalletId; #[cfg(feature = "runtime-benchmarks")] type Helper = (); } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 1402ee9793fbe..e02e77ebe7dce 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -22,7 +22,10 @@ use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, - traits::{tokens::nonfungibles_v2::Destroy, Currency, Get}, + traits::{ + tokens::nonfungibles_v2::{Destroy, Mutate}, + Currency, Get, + }, }; use pallet_balances::Error as BalancesError; use sp_core::bounded::BoundedVec; @@ -975,12 +978,19 @@ fn validate_deposit_required_setting() { bvec![2], bvec![0], )); + assert_ok!(::AccountId, ItemConfig>>::set_attribute( + &0, + &0, + &[3], + &[0], + )); assert_eq!( attributes(0), vec![ (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + (Some(0), AttributeNamespace::Pallet, bvec![3], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 0); diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index f11e33b669be0..efecbb75f9c62 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -115,7 +115,7 @@ pub use sp_runtime::{ self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, }; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::TypeId; @@ -127,7 +127,7 @@ pub const LOG_TARGET: &str = "runtime::frame-support"; pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct PalletId(pub [u8; 8]); impl TypeId for PalletId { diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index f0b172841aa84..f9876ef477b81 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -17,7 +17,6 @@ //! Miscellaneous types. -use crate::PalletId; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; @@ -133,7 +132,7 @@ pub enum BalanceStatus { )] pub enum AttributeNamespace { /// An attribute was set by the pallet. - Pallet(PalletId), + Pallet, /// An attribute was set by collection's owner. CollectionOwner, /// An attribute was set by item's owner. diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index bb49e11679028..a5ec31a9bb4e7 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 80 others + and 78 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 999d8585c221a..42ef5a34e4c30 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 278 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 161 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 278 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index e2870ffb9e86f..461d63ebb0d9c 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 278 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 161 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 278 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index d5b0c3b50a5ac..cce9fa70b3da5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 80 others + and 78 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 6b174d13c5778..877485dda2084 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 80 others + and 78 others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From ea37f258cdb18e01458fac535dada63b0db876d3 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 14 Dec 2022 09:52:25 +0200 Subject: [PATCH 31/60] Revert snapshot changes --- .../storage_ensure_span_are_ok_on_wrong_gen.stderr | 6 +++--- .../storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 42ef5a34e4c30..999d8585c221a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 461d63ebb0d9c..e2870ffb9e86f 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` From c057f02aee215d7ca2434b2dcfff792621c2e735 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 14 Dec 2022 14:37:14 +0200 Subject: [PATCH 32/60] Update snapshots --- .../storage_ensure_span_are_ok_on_wrong_gen.stderr | 6 +++--- .../storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +++--- .../test/tests/pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../tests/pallet_ui/storage_info_unsatisfied_nmap.stderr | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 999d8585c221a..ac5a1f46f8a6b 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 163 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index e2870ffb9e86f..e0c1609403c3a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 163 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index cce9fa70b3da5..b5443c6f327e4 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 877485dda2084..afc7aaa8768cf 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From 5aaa37f649063514766ce25ba62b6a2b56a1ca57 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 14 Dec 2022 14:52:32 +0200 Subject: [PATCH 33/60] Yet another snapshot update.. --- .../tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index a5ec31a9bb4e7..91bab62e2d353 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` From f07e7d08d9bc6d5895ec4ec4441c82aff89cd3bf Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:03:11 +0200 Subject: [PATCH 34/60] Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/support/src/traits/tokens/nonfungible_v2.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index ab0e72b3c8286..f939d9a6f3833 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -15,13 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for dealing with a single non-fungible collection of items. +//! Traits for dealing with a single non-fungible item. //! -//! This assumes a single level namespace identified by `Inspect::ItemId`, and could -//! reasonably be implemented by pallets which wants to expose a single collection of NFT-like +//! This assumes a single-level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets that want to expose a single collection of NFT-like //! objects. //! -//! For an NFT API which has dual-level namespacing, the traits in `nonfungibles` are better to +//! For an NFT API that has dual-level namespacing, the traits in `nonfungibles` are better to //! use. use super::nonfungibles_v2 as nonfungibles; From b1fe6f763b61caaac1ab16edde7cfa408edbfbfc Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:15:22 +0200 Subject: [PATCH 35/60] Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/support/src/traits/tokens/nonfungible_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index f939d9a6f3833..d843d18b0fcca 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -33,7 +33,7 @@ use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; -/// Trait for providing an interface to a read-only NFT-like set of items. +/// Trait for providing an interface to a read-only NFT-like item. pub trait Inspect { /// Type for identifying an item. type ItemId; From 917bb1e544df09b741c61e7a2431bf390b858211 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:19:31 +0200 Subject: [PATCH 36/60] Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/support/src/traits/tokens/nonfungible_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index d843d18b0fcca..cc8dafcbd332f 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -135,7 +135,7 @@ pub trait Transfer: Inspect { fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; } -/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying +/// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by identifying /// a single item. pub struct ItemOf< F: nonfungibles::Inspect, From 92f43f6da01efb471679850012e9088a80c78fbe Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:21:10 +0200 Subject: [PATCH 37/60] Update frame/support/src/traits/tokens/nonfungibles_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/support/src/traits/tokens/nonfungibles_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 09b4793832d7e..14b051df7980a 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -157,7 +157,7 @@ pub trait Destroy: Inspect { /// * `collection`: The `CollectionId` to be destroyed. /// * `witness`: Any witness data that needs to be provided to complete the operation /// successfully. - /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// * `maybe_check_owner`: An optional `AccountId` that can be used to authorize the destroy /// command. If not provided, we will not do any authorization checks before destroying the /// item. /// From 30985daa3be96d07cecb9fa568b8be12c191225d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:29:00 +0200 Subject: [PATCH 38/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 6267df71a9a20..81b8987265c89 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1533,7 +1533,7 @@ pub mod pallet { /// the `collection`. /// /// - `collection`: The identifier of the collection to change. - /// - `max_supply`: The maximum amount of items a collection could have. + /// - `max_supply`: The maximum number of items a collection could have. /// /// Emits `CollectionMaxSupplySet` event when successful. #[pallet::weight(T::WeightInfo::set_collection_max_supply())] From 0d1a442e914be23fbea273ff4ed95494cba7d732 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:29:20 +0200 Subject: [PATCH 39/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 81b8987265c89..f9d378194cd1d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1527,7 +1527,7 @@ pub mod pallet { Self::do_set_accept_ownership(who, maybe_collection) } - /// Set the maximum amount of items a collection could have. + /// Set the maximum number of items a collection could have. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of /// the `collection`. From d429d6e920294694a4a7bc1ebf7818004359da1d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:39:39 +0200 Subject: [PATCH 40/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index f9d378194cd1d..db3bc52786274 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -929,7 +929,7 @@ pub mod pallet { /// whose existing deposit is less than the refreshed deposit as it would only cost them, /// so it's of little consequence. /// - /// It will still return an error in the case that the collection is unknown of the signer + /// It will still return an error in the case that the collection is unknown or the signer /// is not permitted to call it. /// /// Weight: `O(items.len())` From 5de99c8460a5772c3d9d963f8893ee50f3a658d9 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:40:06 +0200 Subject: [PATCH 41/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index db3bc52786274..108060c23b989 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -455,7 +455,7 @@ pub mod pallet { CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, /// Mint settings for a collection had changed. CollectionMintSettingsUpdated { collection: T::CollectionId }, - /// Event gets emmited when the `NextCollectionId` gets incremented. + /// Event gets emitted when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { From dcbce57cc33d5da42248fb4c4b4f58918ec67e2c Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:41:20 +0200 Subject: [PATCH 42/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 108060c23b989..e95b3bea65d00 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -420,7 +420,7 @@ pub mod pallet { }, /// Metadata has been cleared for an item. MetadataCleared { collection: T::CollectionId, item: T::ItemId }, - /// Metadata has been cleared for an item. + /// The deposit for a set of `item`s within a `collection` has been updated. Redeposited { collection: T::CollectionId, successful_items: Vec }, /// New attribute metadata has been set for a `collection` or `item`. AttributeSet { From 1db5061013ba4207d99492c117008d93053d61fc Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:44:50 +0200 Subject: [PATCH 43/60] Update frame/support/src/traits/tokens/nonfungible_v2.rs --- frame/support/src/traits/tokens/nonfungible_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index cc8dafcbd332f..187127098db16 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -129,7 +129,7 @@ pub trait Mutate: Inspect { } } -/// Trait for providing a non-fungible set of items which can only be transferred. +/// Trait for transferring a non-fungible item. pub trait Transfer: Inspect { /// Transfer `item` into `destination` account. fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; From 7653b99b2b3031abe741da778114124e770d3b78 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:50:27 +0200 Subject: [PATCH 44/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index e95b3bea65d00..a4b425a78b590 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -457,14 +457,14 @@ pub mod pallet { CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emitted when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, - /// The price was set for the instance. + /// The price was set for the item. ItemPriceSet { collection: T::CollectionId, item: T::ItemId, price: ItemPrice, whitelisted_buyer: Option, }, - /// The price for the instance was removed. + /// The price for the item was removed. ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, /// An item was bought. ItemBought { From 46f645160b774b4c36053f6a142c8fdf633989ee Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:51:15 +0200 Subject: [PATCH 45/60] Update frame/support/src/traits/tokens/nonfungibles_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/support/src/traits/tokens/nonfungibles_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 14b051df7980a..d2f5f5529fa96 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -246,7 +246,7 @@ pub trait Mutate: Inspect { } } -/// Trait for providing a non-fungible sets of items which can only be transferred. +/// Trait for transferring non-fungible sets of items. pub trait Transfer: Inspect { /// Transfer `item` of `collection` into `destination` account. fn transfer( From 91ffabbff12fc72837544420b4539030d81fcd86 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:51:40 +0200 Subject: [PATCH 46/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index a4b425a78b590..8ef9b0b14fe48 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -553,7 +553,7 @@ pub mod pallet { MaxSupplyReached, /// The max supply is locked and can't be changed. MaxSupplyLocked, - /// The provided max supply is less to the amount of items a collection already has. + /// The provided max supply is less than the number of items a collection already has. MaxSupplyTooSmall, /// The given item ID is unknown. UnknownItem, From 8057d72ac27fe25e19d3ecfb62215d39fe074aba Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:51:59 +0200 Subject: [PATCH 47/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8ef9b0b14fe48..c033c4373f649 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -567,7 +567,7 @@ pub mod pallet { ReachedApprovalLimit, /// The deadline has already expired. DeadlineExpired, - /// The duration provided should be less or equal to MaxDeadlineDuration. + /// The duration provided should be less than or equal to `MaxDeadlineDuration`. WrongDuration, /// The method is disabled by system settings. MethodDisabled, From d0823006b297ea9537224a40457e43b9a527e0e1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:52:44 +0200 Subject: [PATCH 48/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index c033c4373f649..629f5baa97158 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -643,9 +643,8 @@ pub mod pallet { /// Unlike `create`, no funds are reserved. /// /// - `owner`: The owner of this collection of items. The owner has full superuser - /// permissions - /// over this item, but may later change and configure the permissions using - /// `transfer_ownership` and `set_team`. + /// permissions over this item, but may later change and configure the permissions using + /// `transfer_ownership` and `set_team`. /// /// Emits `ForceCreated` event when successful. /// From 8f18f088cfc1adf2fe5f92ff87c9a502d38af203 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:57:07 +0200 Subject: [PATCH 49/60] Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 629f5baa97158..4555997f5b904 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -571,7 +571,7 @@ pub mod pallet { WrongDuration, /// The method is disabled by system settings. MethodDisabled, - /// The provided is setting can't be set. + /// The provided setting can't be set. WrongSetting, /// Item's config already exists and should be equal to the provided one. InconsistentItemConfig, From 27e2f38d534abec384bc19eaf738e6cd9aa3afb2 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 20 Dec 2022 18:04:39 +0200 Subject: [PATCH 50/60] Address comments --- frame/nfts/src/benchmarking.rs | 6 +++--- frame/nfts/src/features/approvals.rs | 2 +- frame/nfts/src/features/metadata.rs | 4 ++-- frame/nfts/src/lib.rs | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 4e392b147c4e8..524b3662fe5b2 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -469,7 +469,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); }: _(SystemOrigin::Signed(caller), collection, item, data.clone()) verify { - assert_last_event::(Event::MetadataSet { collection, item, data }.into()); + assert_last_event::(Event::ItemMetadataSet { collection, item, data }.into()); } clear_metadata { @@ -478,7 +478,7 @@ benchmarks_instance_pallet! { add_item_metadata::(item); }: _(SystemOrigin::Signed(caller), collection, item) verify { - assert_last_event::(Event::MetadataCleared { collection, item }.into()); + assert_last_event::(Event::ItemMetadataCleared { collection, item }.into()); } set_collection_metadata { @@ -506,7 +506,7 @@ benchmarks_instance_pallet! { let deadline = T::BlockNumber::max_value(); }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs index 0cbceb9113d0c..cb5279fd949db 100644 --- a/frame/nfts/src/features/approvals.rs +++ b/frame/nfts/src/features/approvals.rs @@ -54,7 +54,7 @@ impl, I: 'static> Pallet { .map_err(|_| Error::::ReachedApprovalLimit)?; Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::ApprovedTransfer { + Self::deposit_event(Event::TransferApproved { collection, item, owner: details.owner, diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs index 3a12dbe64f2f4..942f377141a33 100644 --- a/frame/nfts/src/features/metadata.rs +++ b/frame/nfts/src/features/metadata.rs @@ -65,7 +65,7 @@ impl, I: 'static> Pallet { *metadata = Some(ItemMetadata { deposit, data: data.clone() }); Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data }); + Self::deposit_event(Event::ItemMetadataSet { collection, item, data }); Ok(()) }) } @@ -96,7 +96,7 @@ impl, I: 'static> Pallet { collection_details.owner_deposit.saturating_reduce(deposit); Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataCleared { collection, item }); + Self::deposit_event(Event::ItemMetadataCleared { collection, item }); Ok(()) }) } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 4555997f5b904..77353ce3359d2 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Unique (Items) Module +//! # Nfts Module //! //! A simple, secure module for dealing with non-fungible items. //! @@ -389,7 +389,7 @@ pub mod pallet { }, /// An `item` of a `collection` has been approved by the `owner` for transfer by /// a `delegate`. - ApprovedTransfer { + TransferApproved { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, @@ -413,13 +413,13 @@ pub mod pallet { /// Metadata has been cleared for a `collection`. CollectionMetadataCleared { collection: T::CollectionId }, /// New metadata has been set for an item. - MetadataSet { + ItemMetadataSet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, }, /// Metadata has been cleared for an item. - MetadataCleared { collection: T::CollectionId, item: T::ItemId }, + ItemMetadataCleared { collection: T::CollectionId, item: T::ItemId }, /// The deposit for a set of `item`s within a `collection` has been updated. Redeposited { collection: T::CollectionId, successful_items: Vec }, /// New attribute metadata has been set for a `collection` or `item`. @@ -1146,7 +1146,7 @@ pub mod pallet { /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the /// number of blocks after which the approval will expire /// - /// Emits `ApprovedTransfer` on success. + /// Emits `TransferApproved` on success. /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::approve_transfer())] @@ -1416,7 +1416,7 @@ pub mod pallet { /// - `item`: The identifier of the item whose metadata to set. /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// - /// Emits `MetadataSet`. + /// Emits `ItemMetadataSet`. /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::set_metadata())] @@ -1442,7 +1442,7 @@ pub mod pallet { /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `item`: The identifier of the item whose metadata to clear. /// - /// Emits `MetadataCleared`. + /// Emits `ItemMetadataCleared`. /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::clear_metadata())] @@ -1644,8 +1644,8 @@ pub mod pallet { /// - `desired_collection`: The collection of the desired item. /// - `desired_item`: The desired item an owner wants to receive. /// - `maybe_price`: The price an owner is willing to pay or receive for the desired `item`. - /// - `maybe_duration`: Optional deadline for the swap. Specified by providing the - /// number of blocks after which the swap will expire. + /// - `duration`: A deadline for the swap. Specified by providing the number of blocks + /// after which the swap will expire. /// /// Emits `SwapCreated` on success. #[pallet::weight(T::WeightInfo::create_swap())] From 56f228d97b6ae571000a6dae23d2c23fc453bd24 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:22:16 +0200 Subject: [PATCH 51/60] [NFTs] Add the new `owner` param to mint() method (#12997) * Add the new `owner` param to mint() method * Fmt * Address comments --- frame/nfts/src/benchmarking.rs | 19 +-- frame/nfts/src/features/create_delete_item.rs | 22 +-- frame/nfts/src/impl_nonfungibles.rs | 1 + frame/nfts/src/lib.rs | 30 ++-- frame/nfts/src/tests.rs | 153 +++++++++++++----- .../src/traits/tokens/nonfungible_v2.rs | 4 +- 6 files changed, 153 insertions(+), 76 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 524b3662fe5b2..6517445da672d 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -79,6 +79,7 @@ fn mint_item, I: 'static>( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, + caller_lookup.clone(), None, )); (item, caller, caller_lookup) @@ -174,7 +175,7 @@ benchmarks_instance_pallet! { let m in 0 .. 1_000; let a in 0 .. 1_000; - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); add_collection_metadata::(); for i in 0..n { mint_item::(i as u16); @@ -200,7 +201,7 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, None) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -222,7 +223,7 @@ benchmarks_instance_pallet! { } transfer { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); let target: T::AccountId = account("target", 0, SEED); @@ -235,7 +236,7 @@ benchmarks_instance_pallet! { redeposit { let i in 0 .. 5_000; - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); Nfts::::force_collection_config( SystemOrigin::Root.into(), @@ -248,7 +249,7 @@ benchmarks_instance_pallet! { } lock_item_transfer { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) verify { @@ -256,7 +257,7 @@ benchmarks_instance_pallet! { } unlock_item_transfer { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); Nfts::::lock_item_transfer( SystemOrigin::Signed(caller.clone()).into(), @@ -269,7 +270,7 @@ benchmarks_instance_pallet! { } lock_collection { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let lock_settings = CollectionSettings::from_disabled( CollectionSetting::TransferableItems | CollectionSetting::UnlockedMetadata | @@ -324,7 +325,7 @@ benchmarks_instance_pallet! { } force_collection_config { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_collection_config { collection, @@ -336,7 +337,7 @@ benchmarks_instance_pallet! { } lock_item_properties { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); let lock_metadata = true; let lock_attributes = true; diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs index bae1d02c8ad6b..7fd745b2bfff8 100644 --- a/frame/nfts/src/features/create_delete_item.rs +++ b/frame/nfts/src/features/create_delete_item.rs @@ -22,7 +22,8 @@ impl, I: 'static> Pallet { pub fn do_mint( collection: T::CollectionId, item: T::ItemId, - owner: T::AccountId, + depositor: T::AccountId, + mint_to: T::AccountId, item_config: ItemConfig, deposit_collection_owner: bool, with_details_and_config: impl FnOnce( @@ -45,9 +46,7 @@ impl, I: 'static> Pallet { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); } - let items = - collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; - collection_details.items = items; + collection_details.items.saturating_inc(); let collection_config = Self::get_collection_config(&collection)?; let deposit_amount = match collection_config @@ -58,11 +57,11 @@ impl, I: 'static> Pallet { }; let deposit_account = match deposit_collection_owner { true => collection_details.owner.clone(), - false => owner.clone(), + false => depositor, }; - let owner = owner.clone(); - Account::::insert((&owner, &collection, &item), ()); + let item_owner = mint_to.clone(); + Account::::insert((&item_owner, &collection, &item), ()); if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { ensure!(existing_config == item_config, Error::::InconsistentItemConfig); @@ -73,14 +72,17 @@ impl, I: 'static> Pallet { T::Currency::reserve(&deposit_account, deposit_amount)?; let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; - let details = - ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; + let details = ItemDetails { + owner: item_owner, + approvals: ApprovalsOf::::default(), + deposit, + }; Item::::insert(&collection, &item, details); Ok(()) }, )?; - Self::deposit_event(Event::Issued { collection, item, owner }); + Self::deposit_event(Event::Issued { collection, item, owner: mint_to }); Ok(()) } diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 9fa696cd5c5c7..edfc29710b7da 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -158,6 +158,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig *collection, *item, who.clone(), + who.clone(), *item_config, deposit_collection_owner, |_, _| Ok(()), diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 77353ce3359d2..8471670b58974 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -52,7 +52,7 @@ use frame_support::traits::{ use frame_system::Config as SystemConfig; use sp_runtime::{ traits::{Saturating, StaticLookup, Zero}, - ArithmeticError, RuntimeDebug, + RuntimeDebug, }; use sp_std::prelude::*; @@ -715,9 +715,12 @@ pub mod pallet { /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. + /// - `mint_to`: Account into which the item will be minted. /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned /// item_id from that collection needs to be provided within the witness data object. /// + /// Note: the deposit will be taken from the `origin` and not the `owner` of the `item`. + /// /// Emits `Issued` event when successful. /// /// Weight: `O(1)` @@ -726,9 +729,11 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, item: T::ItemId, + mint_to: AccountIdLookupOf, witness_data: Option>, ) -> DispatchResult { let caller = ensure_signed(origin)?; + let mint_to = T::Lookup::lookup(mint_to)?; let collection_config = Self::get_collection_config(&collection)?; let item_settings = collection_config.mint_settings.default_item_settings; @@ -738,9 +743,15 @@ pub mod pallet { collection, item, caller.clone(), + mint_to.clone(), item_config, false, |collection_details, collection_config| { + // Issuer can mint regardless of mint settings + if Self::has_role(&collection, &caller, CollectionRole::Issuer) { + return Ok(()) + } + let mint_settings = collection_config.mint_settings; let now = frame_system::Pallet::::block_number(); @@ -752,12 +763,7 @@ pub mod pallet { } match mint_settings.mint_type { - MintType::Issuer => { - ensure!( - Self::has_role(&collection, &caller, CollectionRole::Issuer), - Error::::NoPermission - ) - }, + MintType::Issuer => return Err(Error::::NoPermission.into()), MintType::HolderOf(collection_id) => { let MintWitness { owner_of_item } = witness_data.ok_or(Error::::BadWitness)?; @@ -813,7 +819,7 @@ pub mod pallet { /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. - /// - `owner`: An owner of the minted item. + /// - `mint_to`: Account into which the item will be minted. /// - `item_config`: A config of the new item. /// /// Emits `Issued` event when successful. @@ -824,13 +830,13 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, item: T::ItemId, - owner: AccountIdLookupOf, + mint_to: AccountIdLookupOf, item_config: ItemConfig, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let owner = T::Lookup::lookup(owner)?; + let mint_to = T::Lookup::lookup(mint_to)?; if let Some(check_origin) = maybe_check_origin { ensure!( @@ -838,7 +844,9 @@ pub mod pallet { Error::::NoPermission ); } - Self::do_mint(collection, item, owner, item_config, true, |_, _| Ok(())) + Self::do_mint(collection, item, mint_to.clone(), mint_to, item_config, true, |_, _| { + Ok(()) + }) } /// Destroy a single item. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index e02e77ebe7dce..18a3fd83b4de3 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -146,12 +146,13 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, None)); + // assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -176,7 +177,7 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 6); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, 1, None)); assert_eq!(items(), vec![(1, 0, 70), (10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -221,7 +222,7 @@ fn destroy_with_bad_witness_should_not_work() { )); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -230,7 +231,7 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -239,16 +240,24 @@ fn mint_should_work() { assert_ok!(Nfts::update_mint_settings( RuntimeOrigin::signed(1), 0, - MintSettings { start_block: Some(2), end_block: Some(3), ..Default::default() } + MintSettings { + start_block: Some(2), + end_block: Some(3), + mint_type: MintType::Public, + ..Default::default() + } )); System::set_block_number(1); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), + Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), Error::::MintNotStarted ); System::set_block_number(4); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), Error::::MintEnded); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), + Error::::MintEnded + ); // validate price assert_ok!(Nfts::update_mint_settings( @@ -257,7 +266,7 @@ fn mint_should_work() { MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } )); Balances::make_free_balance_be(&2, 100); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 2, None)); assert_eq!(Balances::total_balance(&2), 99); // validate types @@ -267,22 +276,29 @@ fn mint_should_work() { 1, MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } )); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(3), 1, 42, None), Error::::BadWitness); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 1, 42, None), Error::::BadWitness); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 1, 42, Some(MintWitness { owner_of_item: 42 })), + Nfts::mint(RuntimeOrigin::signed(3), 1, 42, 3, None), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, None), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, Some(MintWitness { owner_of_item: 42 })), Error::::BadWitness ); assert_ok!(Nfts::mint( RuntimeOrigin::signed(2), 1, 42, + 2, Some(MintWitness { owner_of_item: 43 }) )); // can't mint twice assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 1, 46, Some(MintWitness { owner_of_item: 43 })), + Nfts::mint(RuntimeOrigin::signed(2), 1, 46, 2, Some(MintWitness { owner_of_item: 43 })), Error::::AlreadyClaimed ); }); @@ -327,7 +343,7 @@ fn transfer_should_work() { fn locking_transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); @@ -355,7 +371,7 @@ fn locking_transfer_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -376,7 +392,7 @@ fn origin_guards_should_work() { Error::::NoPermission ); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 69, None), + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, None), Error::::NoPermission ); assert_noop!( @@ -421,7 +437,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_eq!(Balances::reserved_balance(&1), 1); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); @@ -451,7 +467,7 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, None)); assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); @@ -545,7 +561,7 @@ fn set_item_metadata_should_work() { 1, collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), @@ -609,7 +625,7 @@ fn set_collection_owner_attributes_should_work() { 1, collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), @@ -1009,8 +1025,8 @@ fn set_attribute_should_respect_lock() { 1, collection_config_with_all_settings_enabled(), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), @@ -1108,8 +1124,8 @@ fn preserve_config_for_frozen_items() { 1, collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); // if the item is not locked/frozen then the config gets deleted on item burn assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); @@ -1144,7 +1160,7 @@ fn preserve_config_for_frozen_items() { ..Default::default() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); }); } @@ -1158,7 +1174,7 @@ fn force_update_collection_should_work() { 1, collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); @@ -1171,7 +1187,7 @@ fn force_update_collection_should_work() { 0, collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); @@ -1283,7 +1299,7 @@ fn approval_lifecycle_works() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1, None)); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -1551,10 +1567,10 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id, None)); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, None), + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id, None), Error::::MaxSupplyReached ); }); @@ -1568,7 +1584,13 @@ fn mint_settings_should_work() { let item_id = 0; assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); assert_eq!( ItemConfigOf::::get(collection_id, item_id) .unwrap() @@ -1591,7 +1613,13 @@ fn mint_settings_should_work() { ..default_collection_config() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); assert_eq!( ItemConfigOf::::get(collection_id, item_id) .unwrap() @@ -1613,8 +1641,20 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + None, + )); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1671,7 +1711,13 @@ fn set_price_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1700,9 +1746,9 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1, None)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1887,8 +1933,20 @@ fn create_cancel_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None,)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None,)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + None, + )); // validate desired item and the collection exists assert_noop!( @@ -2028,7 +2086,7 @@ fn claim_swap_should_work() { assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, - item_1, + item_1,user_1, None, )); assert_ok!(Nfts::force_mint( @@ -2049,6 +2107,7 @@ fn claim_swap_should_work() { RuntimeOrigin::signed(user_1), collection_id, item_4, + user_1, None, )); assert_ok!(Nfts::force_mint( @@ -2300,7 +2359,13 @@ fn pallet_level_feature_flags_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None,)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); // PalletFeature::Trading assert_noop!( @@ -2374,7 +2439,7 @@ fn add_remove_item_attributes_approval_should_work() { let item_id = 0; assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, user_1, None)); assert_ok!(Nfts::approve_item_attributes( RuntimeOrigin::signed(user_1), collection_id, diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 187127098db16..a1b75e62e4db5 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -135,8 +135,8 @@ pub trait Transfer: Inspect { fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; } -/// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by identifying -/// a single item. +/// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by +/// identifying a single item. pub struct ItemOf< F: nonfungibles::Inspect, A: Get<>::CollectionId>, From a8c69a99f7cc71ed27d466d6d7b2ef73f35c40c7 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Thu, 22 Dec 2022 12:56:44 +0000 Subject: [PATCH 52/60] ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts --- frame/nfts/src/weights.rs | 384 +++++++++++++++++++------------------- 1 file changed, 193 insertions(+), 191 deletions(-) diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 2965f335c229e..f05f8ca514c3e 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -96,8 +96,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionAccount (r:0 w:1) fn create() -> Weight { - // Minimum execution time: 42_075 nanoseconds. - Weight::from_ref_time(42_614_000) + // Minimum execution time: 44_312 nanoseconds. + Weight::from_ref_time(44_871_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -107,37 +107,38 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionAccount (r:0 w:1) fn force_create() -> Weight { - // Minimum execution time: 29_799 nanoseconds. - Weight::from_ref_time(30_511_000) + // Minimum execution time: 31_654 nanoseconds. + Weight::from_ref_time(32_078_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:0) + // Storage: Nfts Item (r:1001 w:1000) + // Storage: Nfts Attribute (r:1001 w:1000) + // Storage: Nfts ItemMetadataOf (r:0 w:1000) // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionMetadataOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1000) + // Storage: Nfts Account (r:0 w:1000) // Storage: Nfts CollectionAccount (r:0 w:1) - // Storage: Nfts ItemMetadataOf (r:0 w:20) - // Storage: Nfts ItemConfigOf (r:0 w:20) - // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 65_846 nanoseconds. - Weight::from_ref_time(66_082_000) - // Standard Error: 27_878 - .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m.into())) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((5_u64).saturating_mul(n.into()))) + // Minimum execution time: 19_183_393 nanoseconds. + Weight::from_ref_time(17_061_526_855) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1003)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(3005)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) } // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Item (r:1 w:1) @@ -146,8 +147,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - // Minimum execution time: 58_577 nanoseconds. - Weight::from_ref_time(59_058_000) + // Minimum execution time: 57_753 nanoseconds. + Weight::from_ref_time(58_313_000) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -158,8 +159,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn force_mint() -> Weight { - // Minimum execution time: 56_494 nanoseconds. - Weight::from_ref_time(57_565_000) + // Minimum execution time: 56_429 nanoseconds. + Weight::from_ref_time(57_202_000) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -172,8 +173,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - // Minimum execution time: 59_393 nanoseconds. - Weight::from_ref_time(60_562_000) + // Minimum execution time: 59_681 nanoseconds. + Weight::from_ref_time(60_058_000) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -187,8 +188,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - // Minimum execution time: 65_852 nanoseconds. - Weight::from_ref_time(66_308_000) + // Minimum execution time: 66_085 nanoseconds. + Weight::from_ref_time(67_065_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -197,10 +198,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Item (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 25_795 nanoseconds. - Weight::from_ref_time(26_128_000) - // Standard Error: 10_295 - .saturating_add(Weight::from_ref_time(11_202_286).saturating_mul(i.into())) + // Minimum execution time: 25_949 nanoseconds. + Weight::from_ref_time(26_106_000) + // Standard Error: 10_326 + .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -208,24 +209,24 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_transfer() -> Weight { - // Minimum execution time: 29_090 nanoseconds. - Weight::from_ref_time(29_772_000) + // Minimum execution time: 30_080 nanoseconds. + Weight::from_ref_time(30_825_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn unlock_item_transfer() -> Weight { - // Minimum execution time: 28_947 nanoseconds. - Weight::from_ref_time(29_559_000) + // Minimum execution time: 30_612 nanoseconds. + Weight::from_ref_time(31_422_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - // Minimum execution time: 26_972 nanoseconds. - Weight::from_ref_time(27_803_000) + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(28_015_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -233,40 +234,40 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionAccount (r:0 w:2) fn transfer_ownership() -> Weight { - // Minimum execution time: 32_165 nanoseconds. - Weight::from_ref_time(32_926_000) + // Minimum execution time: 33_750 nanoseconds. + Weight::from_ref_time(34_139_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:0 w:4) fn set_team() -> Weight { - // Minimum execution time: 35_375 nanoseconds. - Weight::from_ref_time(35_950_000) + // Minimum execution time: 36_565 nanoseconds. + Weight::from_ref_time(37_464_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionAccount (r:0 w:2) fn force_collection_owner() -> Weight { - // Minimum execution time: 27_470 nanoseconds. - Weight::from_ref_time(27_855_000) + // Minimum execution time: 29_028 nanoseconds. + Weight::from_ref_time(29_479_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_config() -> Weight { - // Minimum execution time: 23_990 nanoseconds. - Weight::from_ref_time(24_347_000) + // Minimum execution time: 24_695 nanoseconds. + Weight::from_ref_time(25_304_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_properties() -> Weight { - // Minimum execution time: 28_481 nanoseconds. - Weight::from_ref_time(28_929_000) + // Minimum execution time: 28_910 nanoseconds. + Weight::from_ref_time(29_186_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -275,16 +276,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - // Minimum execution time: 54_435 nanoseconds. - Weight::from_ref_time(55_237_000) + // Minimum execution time: 56_407 nanoseconds. + Weight::from_ref_time(58_176_000) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts Attribute (r:1 w:1) fn force_set_attribute() -> Weight { - // Minimum execution time: 35_254 nanoseconds. - Weight::from_ref_time(35_941_000) + // Minimum execution time: 36_402 nanoseconds. + Weight::from_ref_time(37_034_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -292,16 +293,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) fn clear_attribute() -> Weight { - // Minimum execution time: 51_483 nanoseconds. - Weight::from_ref_time(52_915_000) + // Minimum execution time: 52_022 nanoseconds. + Weight::from_ref_time(54_059_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Nfts Item (r:1 w:0) // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) fn approve_item_attributes() -> Weight { - // Minimum execution time: 27_929 nanoseconds. - Weight::from_ref_time(28_329_000) + // Minimum execution time: 28_475 nanoseconds. + Weight::from_ref_time(29_162_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -311,10 +312,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Minimum execution time: 37_217 nanoseconds. - Weight::from_ref_time(37_692_000) - // Standard Error: 7_804 - .saturating_add(Weight::from_ref_time(7_344_173).saturating_mul(n.into())) + // Minimum execution time: 37_529 nanoseconds. + Weight::from_ref_time(38_023_000) + // Standard Error: 8_136 + .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,8 +326,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - // Minimum execution time: 47_675 nanoseconds. - Weight::from_ref_time(48_282_000) + // Minimum execution time: 49_300 nanoseconds. + Weight::from_ref_time(49_790_000) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -334,8 +335,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - // Minimum execution time: 46_062 nanoseconds. - Weight::from_ref_time(46_854_000) + // Minimum execution time: 47_248 nanoseconds. + Weight::from_ref_time(48_094_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -343,8 +344,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - // Minimum execution time: 43_847 nanoseconds. - Weight::from_ref_time(44_792_000) + // Minimum execution time: 44_137 nanoseconds. + Weight::from_ref_time(44_905_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -352,8 +353,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 42_403 nanoseconds. - Weight::from_ref_time(42_811_000) + // Minimum execution time: 43_005 nanoseconds. + Weight::from_ref_time(43_898_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -361,47 +362,47 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn approve_transfer() -> Weight { - // Minimum execution time: 34_880 nanoseconds. - Weight::from_ref_time(35_737_000) + // Minimum execution time: 36_344 nanoseconds. + Weight::from_ref_time(36_954_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn cancel_approval() -> Weight { - // Minimum execution time: 31_606 nanoseconds. - Weight::from_ref_time(32_339_000) + // Minimum execution time: 32_418 nanoseconds. + Weight::from_ref_time(33_029_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn clear_all_transfer_approvals() -> Weight { - // Minimum execution time: 30_626 nanoseconds. - Weight::from_ref_time(31_043_000) + // Minimum execution time: 31_448 nanoseconds. + Weight::from_ref_time(31_979_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_276 nanoseconds. - Weight::from_ref_time(28_016_000) + // Minimum execution time: 27_487 nanoseconds. + Weight::from_ref_time(28_080_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts CollectionConfigOf (r:1 w:1) // Storage: Nfts Collection (r:1 w:0) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 28_366 nanoseconds. - Weight::from_ref_time(28_719_000) + // Minimum execution time: 28_235 nanoseconds. + Weight::from_ref_time(28_967_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn update_mint_settings() -> Weight { - // Minimum execution time: 27_292 nanoseconds. - Weight::from_ref_time(27_614_000) + // Minimum execution time: 28_172 nanoseconds. + Weight::from_ref_time(28_636_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -410,8 +411,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - // Minimum execution time: 34_133 nanoseconds. - Weight::from_ref_time(34_510_000) + // Minimum execution time: 35_336 nanoseconds. + Weight::from_ref_time(36_026_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -424,31 +425,31 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - // Minimum execution time: 69_501 nanoseconds. - Weight::from_ref_time(70_342_000) + // Minimum execution time: 70_971 nanoseconds. + Weight::from_ref_time(72_036_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - // Minimum execution time: 4_754 nanoseconds. - Weight::from_ref_time(11_356_736) - // Standard Error: 38_352 - .saturating_add(Weight::from_ref_time(3_427_961).saturating_mul(n.into())) + // Minimum execution time: 5_151 nanoseconds. + Weight::from_ref_time(11_822_888) + // Standard Error: 38_439 + .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) } // Storage: Nfts Item (r:2 w:0) // Storage: Nfts PendingSwapOf (r:0 w:1) fn create_swap() -> Weight { - // Minimum execution time: 31_371 nanoseconds. - Weight::from_ref_time(32_227_000) + // Minimum execution time: 33_027 nanoseconds. + Weight::from_ref_time(33_628_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Nfts PendingSwapOf (r:1 w:1) // Storage: Nfts Item (r:1 w:0) fn cancel_swap() -> Weight { - // Minimum execution time: 34_114 nanoseconds. - Weight::from_ref_time(34_779_000) + // Minimum execution time: 35_890 nanoseconds. + Weight::from_ref_time(36_508_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -461,8 +462,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Account (r:0 w:4) // Storage: Nfts ItemPriceOf (r:0 w:2) fn claim_swap() -> Weight { - // Minimum execution time: 97_965 nanoseconds. - Weight::from_ref_time(98_699_000) + // Minimum execution time: 101_076 nanoseconds. + Weight::from_ref_time(101_863_000) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(11)) } @@ -476,8 +477,8 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionAccount (r:0 w:1) fn create() -> Weight { - // Minimum execution time: 42_075 nanoseconds. - Weight::from_ref_time(42_614_000) + // Minimum execution time: 44_312 nanoseconds. + Weight::from_ref_time(44_871_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(5)) } @@ -487,37 +488,38 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionAccount (r:0 w:1) fn force_create() -> Weight { - // Minimum execution time: 29_799 nanoseconds. - Weight::from_ref_time(30_511_000) + // Minimum execution time: 31_654 nanoseconds. + Weight::from_ref_time(32_078_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:0) + // Storage: Nfts Item (r:1001 w:1000) + // Storage: Nfts Attribute (r:1001 w:1000) + // Storage: Nfts ItemMetadataOf (r:0 w:1000) // Storage: Nfts CollectionRoleOf (r:0 w:1) // Storage: Nfts CollectionMetadataOf (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1000) + // Storage: Nfts Account (r:0 w:1000) // Storage: Nfts CollectionAccount (r:0 w:1) - // Storage: Nfts ItemMetadataOf (r:0 w:20) - // Storage: Nfts ItemConfigOf (r:0 w:20) - // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 65_846 nanoseconds. - Weight::from_ref_time(66_082_000) - // Standard Error: 27_878 - .saturating_add(Weight::from_ref_time(26_747_590).saturating_mul(n.into())) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m.into())) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(5)) - .saturating_add(RocksDbWeight::get().writes((5_u64).saturating_mul(n.into()))) + // Minimum execution time: 19_183_393 nanoseconds. + Weight::from_ref_time(17_061_526_855) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1003)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(3005)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) } // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Item (r:1 w:1) @@ -526,8 +528,8 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - // Minimum execution time: 58_577 nanoseconds. - Weight::from_ref_time(59_058_000) + // Minimum execution time: 57_753 nanoseconds. + Weight::from_ref_time(58_313_000) .saturating_add(RocksDbWeight::get().reads(5)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -538,8 +540,8 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:1 w:1) // Storage: Nfts Account (r:0 w:1) fn force_mint() -> Weight { - // Minimum execution time: 56_494 nanoseconds. - Weight::from_ref_time(57_565_000) + // Minimum execution time: 56_429 nanoseconds. + Weight::from_ref_time(57_202_000) .saturating_add(RocksDbWeight::get().reads(5)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -552,8 +554,8 @@ impl WeightInfo for () { // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn burn() -> Weight { - // Minimum execution time: 59_393 nanoseconds. - Weight::from_ref_time(60_562_000) + // Minimum execution time: 59_681 nanoseconds. + Weight::from_ref_time(60_058_000) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(7)) } @@ -567,8 +569,8 @@ impl WeightInfo for () { // Storage: Nfts ItemPriceOf (r:0 w:1) // Storage: Nfts PendingSwapOf (r:0 w:1) fn transfer() -> Weight { - // Minimum execution time: 65_852 nanoseconds. - Weight::from_ref_time(66_308_000) + // Minimum execution time: 66_085 nanoseconds. + Weight::from_ref_time(67_065_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(6)) } @@ -577,10 +579,10 @@ impl WeightInfo for () { // Storage: Nfts Item (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 25_795 nanoseconds. - Weight::from_ref_time(26_128_000) - // Standard Error: 10_295 - .saturating_add(Weight::from_ref_time(11_202_286).saturating_mul(i.into())) + // Minimum execution time: 25_949 nanoseconds. + Weight::from_ref_time(26_106_000) + // Standard Error: 10_326 + .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -588,24 +590,24 @@ impl WeightInfo for () { // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_transfer() -> Weight { - // Minimum execution time: 29_090 nanoseconds. - Weight::from_ref_time(29_772_000) + // Minimum execution time: 30_080 nanoseconds. + Weight::from_ref_time(30_825_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn unlock_item_transfer() -> Weight { - // Minimum execution time: 28_947 nanoseconds. - Weight::from_ref_time(29_559_000) + // Minimum execution time: 30_612 nanoseconds. + Weight::from_ref_time(31_422_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts CollectionRoleOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - // Minimum execution time: 26_972 nanoseconds. - Weight::from_ref_time(27_803_000) + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(28_015_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -613,40 +615,40 @@ impl WeightInfo for () { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionAccount (r:0 w:2) fn transfer_ownership() -> Weight { - // Minimum execution time: 32_165 nanoseconds. - Weight::from_ref_time(32_926_000) + // Minimum execution time: 33_750 nanoseconds. + Weight::from_ref_time(34_139_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:0 w:4) fn set_team() -> Weight { - // Minimum execution time: 35_375 nanoseconds. - Weight::from_ref_time(35_950_000) + // Minimum execution time: 36_565 nanoseconds. + Weight::from_ref_time(37_464_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionAccount (r:0 w:2) fn force_collection_owner() -> Weight { - // Minimum execution time: 27_470 nanoseconds. - Weight::from_ref_time(27_855_000) + // Minimum execution time: 29_028 nanoseconds. + Weight::from_ref_time(29_479_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_config() -> Weight { - // Minimum execution time: 23_990 nanoseconds. - Weight::from_ref_time(24_347_000) + // Minimum execution time: 24_695 nanoseconds. + Weight::from_ref_time(25_304_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item_properties() -> Weight { - // Minimum execution time: 28_481 nanoseconds. - Weight::from_ref_time(28_929_000) + // Minimum execution time: 28_910 nanoseconds. + Weight::from_ref_time(29_186_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -655,16 +657,16 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - // Minimum execution time: 54_435 nanoseconds. - Weight::from_ref_time(55_237_000) + // Minimum execution time: 56_407 nanoseconds. + Weight::from_ref_time(58_176_000) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts Attribute (r:1 w:1) fn force_set_attribute() -> Weight { - // Minimum execution time: 35_254 nanoseconds. - Weight::from_ref_time(35_941_000) + // Minimum execution time: 36_402 nanoseconds. + Weight::from_ref_time(37_034_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } @@ -672,16 +674,16 @@ impl WeightInfo for () { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) fn clear_attribute() -> Weight { - // Minimum execution time: 51_483 nanoseconds. - Weight::from_ref_time(52_915_000) + // Minimum execution time: 52_022 nanoseconds. + Weight::from_ref_time(54_059_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Nfts Item (r:1 w:0) // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) fn approve_item_attributes() -> Weight { - // Minimum execution time: 27_929 nanoseconds. - Weight::from_ref_time(28_329_000) + // Minimum execution time: 28_475 nanoseconds. + Weight::from_ref_time(29_162_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -691,10 +693,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Minimum execution time: 37_217 nanoseconds. - Weight::from_ref_time(37_692_000) - // Standard Error: 7_804 - .saturating_add(Weight::from_ref_time(7_344_173).saturating_mul(n.into())) + // Minimum execution time: 37_529 nanoseconds. + Weight::from_ref_time(38_023_000) + // Standard Error: 8_136 + .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2)) @@ -705,8 +707,8 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - // Minimum execution time: 47_675 nanoseconds. - Weight::from_ref_time(48_282_000) + // Minimum execution time: 49_300 nanoseconds. + Weight::from_ref_time(49_790_000) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(2)) } @@ -714,8 +716,8 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - // Minimum execution time: 46_062 nanoseconds. - Weight::from_ref_time(46_854_000) + // Minimum execution time: 47_248 nanoseconds. + Weight::from_ref_time(48_094_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(2)) } @@ -723,8 +725,8 @@ impl WeightInfo for () { // Storage: Nfts Collection (r:1 w:1) // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - // Minimum execution time: 43_847 nanoseconds. - Weight::from_ref_time(44_792_000) + // Minimum execution time: 44_137 nanoseconds. + Weight::from_ref_time(44_905_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(2)) } @@ -732,8 +734,8 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts CollectionMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 42_403 nanoseconds. - Weight::from_ref_time(42_811_000) + // Minimum execution time: 43_005 nanoseconds. + Weight::from_ref_time(43_898_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -741,47 +743,47 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn approve_transfer() -> Weight { - // Minimum execution time: 34_880 nanoseconds. - Weight::from_ref_time(35_737_000) + // Minimum execution time: 36_344 nanoseconds. + Weight::from_ref_time(36_954_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn cancel_approval() -> Weight { - // Minimum execution time: 31_606 nanoseconds. - Weight::from_ref_time(32_339_000) + // Minimum execution time: 32_418 nanoseconds. + Weight::from_ref_time(33_029_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts Item (r:1 w:1) // Storage: Nfts CollectionRoleOf (r:1 w:0) fn clear_all_transfer_approvals() -> Weight { - // Minimum execution time: 30_626 nanoseconds. - Weight::from_ref_time(31_043_000) + // Minimum execution time: 31_448 nanoseconds. + Weight::from_ref_time(31_979_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_276 nanoseconds. - Weight::from_ref_time(28_016_000) + // Minimum execution time: 27_487 nanoseconds. + Weight::from_ref_time(28_080_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts CollectionConfigOf (r:1 w:1) // Storage: Nfts Collection (r:1 w:0) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 28_366 nanoseconds. - Weight::from_ref_time(28_719_000) + // Minimum execution time: 28_235 nanoseconds. + Weight::from_ref_time(28_967_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts Collection (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:1) fn update_mint_settings() -> Weight { - // Minimum execution time: 27_292 nanoseconds. - Weight::from_ref_time(27_614_000) + // Minimum execution time: 28_172 nanoseconds. + Weight::from_ref_time(28_636_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -790,8 +792,8 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - // Minimum execution time: 34_133 nanoseconds. - Weight::from_ref_time(34_510_000) + // Minimum execution time: 35_336 nanoseconds. + Weight::from_ref_time(36_026_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -804,31 +806,31 @@ impl WeightInfo for () { // Storage: Nfts Account (r:0 w:2) // Storage: Nfts PendingSwapOf (r:0 w:1) fn buy_item() -> Weight { - // Minimum execution time: 69_501 nanoseconds. - Weight::from_ref_time(70_342_000) + // Minimum execution time: 70_971 nanoseconds. + Weight::from_ref_time(72_036_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(6)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - // Minimum execution time: 4_754 nanoseconds. - Weight::from_ref_time(11_356_736) - // Standard Error: 38_352 - .saturating_add(Weight::from_ref_time(3_427_961).saturating_mul(n.into())) + // Minimum execution time: 5_151 nanoseconds. + Weight::from_ref_time(11_822_888) + // Standard Error: 38_439 + .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) } // Storage: Nfts Item (r:2 w:0) // Storage: Nfts PendingSwapOf (r:0 w:1) fn create_swap() -> Weight { - // Minimum execution time: 31_371 nanoseconds. - Weight::from_ref_time(32_227_000) + // Minimum execution time: 33_027 nanoseconds. + Weight::from_ref_time(33_628_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Nfts PendingSwapOf (r:1 w:1) // Storage: Nfts Item (r:1 w:0) fn cancel_swap() -> Weight { - // Minimum execution time: 34_114 nanoseconds. - Weight::from_ref_time(34_779_000) + // Minimum execution time: 35_890 nanoseconds. + Weight::from_ref_time(36_508_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -841,8 +843,8 @@ impl WeightInfo for () { // Storage: Nfts Account (r:0 w:4) // Storage: Nfts ItemPriceOf (r:0 w:2) fn claim_swap() -> Weight { - // Minimum execution time: 97_965 nanoseconds. - Weight::from_ref_time(98_699_000) + // Minimum execution time: 101_076 nanoseconds. + Weight::from_ref_time(101_863_000) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(11)) } From 7df1e2cef75604ac0cb57d23ba465a9bf994672a Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:08:28 +0200 Subject: [PATCH 53/60] Update frame/nfts/src/common_functions.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/common_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs index b3cac7f69ec0e..9c0faeb6b7c77 100644 --- a/frame/nfts/src/common_functions.rs +++ b/frame/nfts/src/common_functions.rs @@ -25,7 +25,7 @@ impl, I: 'static> Pallet { Item::::get(collection, item).map(|i| i.owner) } - /// Get the owner of the item, if the item exists. + /// Get the owner of the collection, if the collection exists. pub fn collection_owner(collection: T::CollectionId) -> Option { Collection::::get(collection).map(|i| i.owner) } From 2323614a3570ab102f687bfbb920cd3bcf8797b5 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:08:37 +0200 Subject: [PATCH 54/60] Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 4cb92d692d7f7..be9951e635028 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -66,7 +66,7 @@ pub trait Incrementable { } impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); -/// Information about the collection. +/// Information about a collection. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Collection's owner. From 08f0ea22a9da3eafb3dea5b9fb0be529ad044ea5 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:08:49 +0200 Subject: [PATCH 55/60] Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index be9951e635028..e50be12e394c9 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -71,7 +71,7 @@ impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); pub struct CollectionDetails { /// Collection's owner. pub(super) owner: AccountId, - /// The total balance deposited by the owner for the all storage data associated with this + /// The total balance deposited by the owner for all the storage data associated with this /// collection. Used by `destroy`. pub(super) owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. From 66cbec6d856203a1d22c1ff09f4424d9717357b1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:09:14 +0200 Subject: [PATCH 56/60] Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index e50be12e394c9..662c64594f228 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -167,9 +167,9 @@ pub struct ItemMetadata> { /// Information about the tip. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemTip { - /// A collection of the item. + /// The collection of the item. pub(super) collection: CollectionId, - /// An item of which the tip is send for. + /// An item of which the tip is sent for. pub(super) item: ItemId, /// A sender of the tip. pub(super) receiver: AccountId, From 3218b20a1903cc6372dc76ea30f9e9269d84bd74 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:09:47 +0200 Subject: [PATCH 57/60] Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 662c64594f228..4bd80c18c8547 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -180,9 +180,9 @@ pub struct ItemTip { /// Information about the pending swap. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct PendingSwap { - /// A collection of the item user wants to receive. + /// The collection that contains the item that the user wants to receive. pub(super) desired_collection: CollectionId, - /// An item user wants to receive. + /// The item the user wants to receive. pub(super) desired_item: Option, /// A price for the desired `item` with the direction. pub(super) price: Option, From 40ecc6011775d13b25a0e46b689b75ec47bf2fe5 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:10:45 +0200 Subject: [PATCH 58/60] Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/nfts/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 4bd80c18c8547..58b1acaaedf42 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -199,10 +199,10 @@ pub struct AttributeDeposit { pub(super) amount: DepositBalance, } -/// Specifies whether the tokens will be send or received. +/// Specifies whether the tokens will be sent or received. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum PriceDirection { - /// Tokens will be send. + /// Tokens will be sent. Send, /// Tokens will be received. Receive, From 30c0f23ff431ac1b8afe045370725a1fc1316e1f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 23 Dec 2022 15:34:26 +0200 Subject: [PATCH 59/60] Add call indexes --- frame/nfts/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8471670b58974..2006d78959c4d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -606,6 +606,7 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, @@ -649,6 +650,7 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, @@ -686,6 +688,7 @@ pub mod pallet { /// - `n = witness.items` /// - `m = witness.item_metadatas` /// - `a = witness.attributes` + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( witness.items, witness.item_metadatas, @@ -724,6 +727,7 @@ pub mod pallet { /// Emits `Issued` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, @@ -825,6 +829,7 @@ pub mod pallet { /// Emits `Issued` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::force_mint())] pub fn force_mint( origin: OriginFor, @@ -862,6 +867,7 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: `check_owner.is_some()`. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, @@ -899,6 +905,7 @@ pub mod pallet { /// Emits `Transferred`. /// /// Weight: `O(1)` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -940,6 +947,7 @@ pub mod pallet { /// is not permitted to call it. /// /// Weight: `O(items.len())` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, @@ -999,6 +1007,7 @@ pub mod pallet { /// Emits `ItemTransferLocked`. /// /// Weight: `O(1)` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::lock_item_transfer())] pub fn lock_item_transfer( origin: OriginFor, @@ -1019,6 +1028,7 @@ pub mod pallet { /// Emits `ItemTransferUnlocked`. /// /// Weight: `O(1)` + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::unlock_item_transfer())] pub fn unlock_item_transfer( origin: OriginFor, @@ -1040,6 +1050,7 @@ pub mod pallet { /// Emits `CollectionLocked`. /// /// Weight: `O(1)` + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::lock_collection())] pub fn lock_collection( origin: OriginFor, @@ -1061,6 +1072,7 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, @@ -1085,6 +1097,7 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, @@ -1112,6 +1125,7 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_collection_owner())] pub fn force_collection_owner( origin: OriginFor, @@ -1133,6 +1147,7 @@ pub mod pallet { /// Emits `CollectionConfigChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::force_collection_config())] pub fn force_collection_config( origin: OriginFor, @@ -1157,6 +1172,7 @@ pub mod pallet { /// Emits `TransferApproved` on success. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, @@ -1193,6 +1209,7 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, @@ -1221,6 +1238,7 @@ pub mod pallet { /// Emits `AllApprovalsCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::clear_all_transfer_approvals())] pub fn clear_all_transfer_approvals( origin: OriginFor, @@ -1250,6 +1268,7 @@ pub mod pallet { /// Emits `ItemPropertiesLocked`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::lock_item_properties())] pub fn lock_item_properties( origin: OriginFor, @@ -1292,6 +1311,7 @@ pub mod pallet { /// Emits `AttributeSet`. /// /// Weight: `O(1)` + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -1322,6 +1342,7 @@ pub mod pallet { /// Emits `AttributeSet`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::force_set_attribute())] pub fn force_set_attribute( origin: OriginFor, @@ -1351,6 +1372,7 @@ pub mod pallet { /// Emits `AttributeCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -1374,6 +1396,7 @@ pub mod pallet { /// - `delegate`: The account to delegate permission to change attributes of the item. /// /// Emits `ItemAttributesApprovalAdded` on success. + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -1396,6 +1419,7 @@ pub mod pallet { /// - `delegate`: The previously approved account to remove. /// /// Emits `ItemAttributesApprovalRemoved` on success. + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval( witness.account_attributes ))] @@ -1427,6 +1451,7 @@ pub mod pallet { /// Emits `ItemMetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -1453,6 +1478,7 @@ pub mod pallet { /// Emits `ItemMetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -1480,6 +1506,7 @@ pub mod pallet { /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::set_collection_metadata())] pub fn set_collection_metadata( origin: OriginFor, @@ -1504,6 +1531,7 @@ pub mod pallet { /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(27)] #[pallet::weight(T::WeightInfo::clear_collection_metadata())] pub fn clear_collection_metadata( origin: OriginFor, @@ -1525,6 +1553,7 @@ pub mod pallet { /// ownership transferal. /// /// Emits `OwnershipAcceptanceChanged`. + #[pallet::call_index(28)] #[pallet::weight(T::WeightInfo::set_accept_ownership())] pub fn set_accept_ownership( origin: OriginFor, @@ -1543,6 +1572,7 @@ pub mod pallet { /// - `max_supply`: The maximum number of items a collection could have. /// /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::call_index(29)] #[pallet::weight(T::WeightInfo::set_collection_max_supply())] pub fn set_collection_max_supply( origin: OriginFor, @@ -1564,6 +1594,7 @@ pub mod pallet { /// - `mint_settings`: The new mint settings. /// /// Emits `CollectionMintSettingsUpdated` event when successful. + #[pallet::call_index(30)] #[pallet::weight(T::WeightInfo::update_mint_settings())] pub fn update_mint_settings( origin: OriginFor, @@ -1591,6 +1622,7 @@ pub mod pallet { /// /// Emits `ItemPriceSet` on success if the price is not `None`. /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::call_index(31)] #[pallet::weight(T::WeightInfo::set_price())] pub fn set_price( origin: OriginFor, @@ -1613,6 +1645,7 @@ pub mod pallet { /// - `bid_price`: The price the sender is willing to pay. /// /// Emits `ItemBought` on success. + #[pallet::call_index(32)] #[pallet::weight(T::WeightInfo::buy_item())] pub fn buy_item( origin: OriginFor, @@ -1631,6 +1664,7 @@ pub mod pallet { /// - `tips`: Tips array. /// /// Emits `TipSent` on every tip transfer. + #[pallet::call_index(33)] #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))] pub fn pay_tips( origin: OriginFor, @@ -1656,6 +1690,7 @@ pub mod pallet { /// after which the swap will expire. /// /// Emits `SwapCreated` on success. + #[pallet::call_index(34)] #[pallet::weight(T::WeightInfo::create_swap())] pub fn create_swap( origin: OriginFor, @@ -1687,6 +1722,7 @@ pub mod pallet { /// - `item`: The item an owner wants to give. /// /// Emits `SwapCancelled` on success. + #[pallet::call_index(35)] #[pallet::weight(T::WeightInfo::cancel_swap())] pub fn cancel_swap( origin: OriginFor, @@ -1709,6 +1745,7 @@ pub mod pallet { /// - `witness_price`: A price that was previously agreed on. /// /// Emits `SwapClaimed` on success. + #[pallet::call_index(36)] #[pallet::weight(T::WeightInfo::claim_swap())] pub fn claim_swap( origin: OriginFor, From f61854336690154685770b9b6f4020a42d602b1f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 23 Dec 2022 16:36:17 +0200 Subject: [PATCH 60/60] Update snapshots --- .../pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr | 4 ++-- .../storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index ac5a1f46f8a6b..364eb5e6d5bb1 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index e0c1609403c3a..371e90323d9cb 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder`