From ac35a80f3982160cc8dd5c2190e186766da8ceb9 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:23:51 +0700 Subject: [PATCH 01/79] chore: forked pallet nfts --- Cargo.lock | 28 +- Cargo.toml | 2 +- pallets/nfts/Cargo.toml | 56 + pallets/nfts/README.md | 108 + pallets/nfts/runtime-api/Cargo.toml | 25 + pallets/nfts/runtime-api/README.md | 3 + pallets/nfts/runtime-api/src/lib.rs | 59 + pallets/nfts/src/benchmarking.rs | 882 ++++ pallets/nfts/src/common_functions.rs | 81 + pallets/nfts/src/features/approvals.rs | 175 + pallets/nfts/src/features/atomic_swap.rs | 234 + pallets/nfts/src/features/attributes.rs | 525 +++ pallets/nfts/src/features/buy_sell.rs | 172 + .../src/features/create_delete_collection.rs | 153 + .../nfts/src/features/create_delete_item.rs | 273 ++ pallets/nfts/src/features/lock.rs | 167 + pallets/nfts/src/features/metadata.rs | 282 ++ pallets/nfts/src/features/mod.rs | 28 + pallets/nfts/src/features/roles.rs | 152 + pallets/nfts/src/features/settings.rs | 178 + pallets/nfts/src/features/transfer.rs | 234 + pallets/nfts/src/impl_nonfungibles.rs | 506 +++ pallets/nfts/src/lib.rs | 1933 ++++++++ pallets/nfts/src/macros.rs | 66 + pallets/nfts/src/migration.rs | 120 + pallets/nfts/src/mock.rs | 104 + pallets/nfts/src/tests.rs | 3877 +++++++++++++++++ pallets/nfts/src/types.rs | 548 +++ pallets/nfts/src/weights.rs | 1468 +++++++ 29 files changed, 12433 insertions(+), 6 deletions(-) create mode 100644 pallets/nfts/Cargo.toml create mode 100644 pallets/nfts/README.md create mode 100644 pallets/nfts/runtime-api/Cargo.toml create mode 100644 pallets/nfts/runtime-api/README.md create mode 100644 pallets/nfts/runtime-api/src/lib.rs create mode 100644 pallets/nfts/src/benchmarking.rs create mode 100644 pallets/nfts/src/common_functions.rs create mode 100644 pallets/nfts/src/features/approvals.rs create mode 100644 pallets/nfts/src/features/atomic_swap.rs create mode 100644 pallets/nfts/src/features/attributes.rs create mode 100644 pallets/nfts/src/features/buy_sell.rs create mode 100644 pallets/nfts/src/features/create_delete_collection.rs create mode 100644 pallets/nfts/src/features/create_delete_item.rs create mode 100644 pallets/nfts/src/features/lock.rs create mode 100644 pallets/nfts/src/features/metadata.rs create mode 100644 pallets/nfts/src/features/mod.rs create mode 100644 pallets/nfts/src/features/roles.rs create mode 100644 pallets/nfts/src/features/settings.rs create mode 100644 pallets/nfts/src/features/transfer.rs create mode 100644 pallets/nfts/src/impl_nonfungibles.rs create mode 100644 pallets/nfts/src/lib.rs create mode 100644 pallets/nfts/src/macros.rs create mode 100644 pallets/nfts/src/migration.rs create mode 100644 pallets/nfts/src/mock.rs create mode 100644 pallets/nfts/src/tests.rs create mode 100644 pallets/nfts/src/types.rs create mode 100644 pallets/nfts/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 3f1349eb..2c9ef33a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,7 +497,7 @@ dependencies = [ "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", - "pallet-nfts", + "pallet-nfts 30.0.0", "pallet-nfts-runtime-api", "pallet-proxy", "pallet-session", @@ -8230,7 +8230,7 @@ dependencies = [ "frame-system", "log", "pallet-assets", - "pallet-nfts", + "pallet-nfts 30.0.0", "parity-scale-codec", "scale-info", "sp-runtime", @@ -8256,13 +8256,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nfts" +version = "31.0.0" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", +] + [[package]] name = "pallet-nfts-runtime-api" version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0ca7a0446d2d3c27f726a016c6366218df2e0bfef9ed35886b252cfa9757f6c" dependencies = [ - "pallet-nfts", + "pallet-nfts 30.0.0", "parity-scale-codec", "sp-api", "sp-std", @@ -10838,7 +10856,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts", + "pallet-nfts 31.0.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", @@ -10982,7 +11000,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts", + "pallet-nfts 31.0.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", diff --git a/Cargo.toml b/Cargo.toml index c6e15c69..0730d05f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ substrate-wasm-builder = "23.0.0" # Local pallet-api = { path = "pallets/api", default-features = false } +pallet-nfts = { path = "pallets/nfts", default-features = false } pop-chain-extension = { path = "./extension", default-features = false } pop-primitives = { path = "./primitives", default-features = false } pop-runtime-common = { path = "runtime/common", default-features = false } @@ -86,7 +87,6 @@ pallet-contracts = { version = "35.0.0", default-features = false } pallet-message-queue = { version = "39.0.0", default-features = false } pallet-multisig = { version = "36.0.0", default-features = false } pallet-nft-fractionalization = { version = "18.0.0", default-features = false } -pallet-nfts = { version = "30.0.0", default-features = false } pallet-nfts-runtime-api = { version = "22.0.0", default-features = false } pallet-preimage = { version = "36.0.0", default-features = false } pallet-proxy = { version = "36.0.0", default-features = false } diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml new file mode 100644 index 00000000..19d35803 --- /dev/null +++ b/pallets/nfts/Cargo.toml @@ -0,0 +1,56 @@ +[package] +authors.workspace = true +description = "FRAME NFTs pallet (polkadot v1.15.0)" +edition.workspace = true +homepage = "https://substrate.io" +license = "Apache-2.0" +name = "pallet-nfts" +readme = "README.md" +repository.workspace = true +version = "31.0.0" + + +[package.metadata.docs.rs] +targets = [ "x86_64-unknown-linux-gnu" ] + +[dependencies] +codec = { workspace = true } +enumflags2 = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support.workspace = true +frame-system.workspace = true +log = { workspace = true } +scale-info = { features = [ "derive" ], workspace = true } +sp-core.workspace = true +sp-io.workspace = true +sp-runtime.workspace = true + +[dev-dependencies] +pallet-balances = { default-features = true, workspace = true } +sp-keystore = { default-features = true, workspace = true } + +[features] +default = [ "std" ] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "enumflags2/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/nfts/README.md b/pallets/nfts/README.md new file mode 100644 index 00000000..93ccf294 --- /dev/null +++ b/pallets/nfts/README.md @@ -0,0 +1,108 @@ +# NFTs pallet + +A pallet for dealing with non-fungible assets. + +## Overview + +The NFTs pallet provides functionality for non-fungible tokens' management, including: + +* Collection Creation +* NFT Minting +* NFT Transfers and Atomic Swaps +* NFT Trading methods +* Attributes Management +* NFT Burning + +To use it in your runtime, you need to implement +[`nfts::Config`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/trait.Config.html). + +The supported dispatchable functions are documented in the +[`nfts::Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum. + +### Terminology + +* **Collection creation:** The creation of a new collection. +* **NFT minting:** The action of creating a new item within a collection. +* **NFT transfer:** The action of sending an item from one account to another. +* **Atomic swap:** The action of exchanging items between accounts without needing a 3rd party service. +* **NFT burning:** The destruction of an item. +* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly one instance of + such an item in existence and there is exactly one owning account (though that owning account could be a proxy account + or multi-sig account). +* **Soul Bound NFT:** An item that is non-transferable from the account which it is minted into. + +### Goals + +The NFTs pallet in Substrate is designed to make the following possible: + +* Allow accounts to permissionlessly create nft collections. +* Allow a named (permissioned) account to mint and burn unique items within a collection. +* Move items between accounts permissionlessly. +* Allow a named (permissioned) account to freeze and unfreeze items within a collection or the entire collection. +* Allow the owner of an item to delegate the ability to transfer the item to some named third-party. +* Allow third-parties to store information in an NFT _without_ owning it (Eg. save game state). + +## Interface + +### Permissionless dispatchables + +* `create`: Create a new collection by placing a deposit. +* `mint`: Mint a new item within a collection (when the minting is public). +* `transfer`: Send an item to a new owner. +* `redeposit`: Update the deposit amount of an item, potentially freeing funds. +* `approve_transfer`: Name a delegate who may authorize a transfer. +* `cancel_approval`: Revert the effects of a previous `approve_transfer`. +* `approve_item_attributes`: Name a delegate who may change item's attributes within a namespace. +* `cancel_item_attributes_approval`: Revert the effects of a previous `approve_item_attributes`. +* `set_price`: Set the price for an item. +* `buy_item`: Buy an item. +* `pay_tips`: Pay tips, could be used for paying the creator royalties. +* `create_swap`: Create an offer to swap an NFT for another NFT and optionally some fungibles. +* `cancel_swap`: Cancel previously created swap offer. +* `claim_swap`: Swap items in an atomic way. + + +### Permissioned dispatchables + +* `destroy`: Destroy a collection. This destroys all the items inside the collection and refunds the deposit. +* `force_mint`: Mint a new item within a collection. +* `burn`: Destroy an item within a collection. +* `lock_item_transfer`: Prevent an individual item from being transferred. +* `unlock_item_transfer`: Revert the effects of a previous `lock_item_transfer`. +* `clear_all_transfer_approvals`: Clears all transfer approvals set by calling the `approve_transfer`. +* `lock_collection`: Prevent all items within a collection from being transferred (making them all `soul bound`). +* `lock_item_properties`: Lock item's metadata or attributes. +* `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. (Ownership of individual items + will not be affected.) +* `set_team`: Alter the permissioned accounts of a collection. +* `set_collection_max_supply`: Change the max supply of a collection. +* `update_mint_settings`: Update the minting settings for collection. + + +### Metadata (permissioned) dispatchables + +* `set_attribute`: Set a metadata attribute of an item or collection. +* `clear_attribute`: Remove a metadata attribute of an item or collection. +* `set_metadata`: Set general metadata of an item (E.g. an IPFS address of an image url). +* `clear_metadata`: Remove general metadata of an item. +* `set_collection_metadata`: Set general metadata of a collection. +* `clear_collection_metadata`: Remove general metadata of a collection. + + +### Force (i.e. governance) dispatchables + +* `force_create`: Create a new collection (the collection id can not be chosen). +* `force_collection_owner`: Change collection's owner. +* `force_collection_config`: Change collection's config. +* `force_set_attribute`: Set an attribute. + +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_nfts/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/pallets/nfts/runtime-api/Cargo.toml b/pallets/nfts/runtime-api/Cargo.toml new file mode 100644 index 00000000..51aac0f9 --- /dev/null +++ b/pallets/nfts/runtime-api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pallet-nfts-runtime-api" +version = "23.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "Runtime API for the FRAME NFTs pallet. (polkadot v1.15.0)" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = ["derive"], workspace = true } +pallet-nfts.workspace = true +sp-api.workspace = true + +[features] +default = ["std"] +std = ["codec/std", "pallet-nfts/std", "sp-api/std"] diff --git a/pallets/nfts/runtime-api/README.md b/pallets/nfts/runtime-api/README.md new file mode 100644 index 00000000..289036d2 --- /dev/null +++ b/pallets/nfts/runtime-api/README.md @@ -0,0 +1,3 @@ +RPC runtime API for the FRAME NFTs pallet. + +License: Apache-2.0 diff --git a/pallets/nfts/runtime-api/src/lib.rs b/pallets/nfts/runtime-api/src/lib.rs new file mode 100644 index 00000000..87faa790 --- /dev/null +++ b/pallets/nfts/runtime-api/src/lib.rs @@ -0,0 +1,59 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime API definition for the FRAME NFTs pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use codec::{Decode, Encode}; + +sp_api::decl_runtime_apis! { + pub trait NftsApi + where + AccountId: Encode + Decode, + CollectionId: Encode, + ItemId: Encode, + { + fn owner(collection: CollectionId, item: ItemId) -> Option; + + fn collection_owner(collection: CollectionId) -> Option; + + fn attribute( + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn custom_attribute( + account: AccountId, + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn system_attribute( + collection: CollectionId, + item: Option, + key: Vec, + ) -> Option>; + + fn collection_attribute(collection: CollectionId, key: Vec) -> Option>; + } +} diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs new file mode 100644 index 00000000..bc81096b --- /dev/null +++ b/pallets/nfts/src/benchmarking.rs @@ -0,0 +1,882 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Nfts pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use enumflags2::{BitFlag, BitFlags}; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError, +}; +use frame_support::{ + assert_ok, + traits::{EnsureOrigin, Get, UnfilteredDispatchable}, + BoundedVec, +}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; +use sp_runtime::traits::{Bounded, One}; + +use crate::Pallet as Nfts; + +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_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + default_collection_config::() + )); + (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_ok!(Nfts::::set_collection_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + )); + (caller, caller_lookup) +} + +fn mint_item, I: 'static>( + index: u16, +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let item = T::Helper::item(index); + let collection = T::Helper::collection(0); + let caller = Collection::::get(collection).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let item_exists = Item::::contains_key(&collection, &item); + let item_config = ItemConfigOf::::get(&collection, &item); + if item_exists { + return (item, caller, caller_lookup) + } else if let Some(item_config) = item_config { + assert_ok!(Nfts::::force_mint( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + caller_lookup.clone(), + item_config, + )); + } else { + assert_ok!(Nfts::::mint( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + caller_lookup.clone(), + None, + )); + } + (item, caller, caller_lookup) +} + +fn lock_item, I: 'static>( + index: u16, +) -> (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()); + let item = T::Helper::item(index); + assert_ok!(Nfts::::lock_item_transfer( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + )); + (item, caller, caller_lookup) +} + +fn burn_item, I: 'static>( + index: u16, +) -> (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()); + let item = T::Helper::item(index); + assert_ok!(Nfts::::burn( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + )); + (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_ok!(Nfts::::set_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + )); + (caller, caller_lookup) +} + +fn add_item_attribute, 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_ok!(Nfts::::set_attribute( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + Some(item), + AttributeNamespace::CollectionOwner, + key.clone(), + vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + )); + (key, caller, caller_lookup) +} + +fn add_collection_attribute, I: 'static>( + i: u16, +) -> (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<_, _> = make_filled_vec(i, T::KeyLimit::get() as usize).try_into().unwrap(); + assert_ok!(Nfts::::set_attribute( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + None, + AttributeNamespace::CollectionOwner, + key.clone(), + vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + )); + (key, caller, caller_lookup) +} + +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + 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); +} + +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() } +} + +fn make_filled_vec(value: u16, length: usize) -> Vec { + let mut vec = vec![0u8; length]; + let mut s = Vec::from(value.to_be_bytes()); + vec.truncate(length - s.len()); + vec.append(&mut s); + vec +} + +benchmarks_instance_pallet! { + create { + let collection = T::Helper::collection(0); + let origin = T::CreateOrigin::try_successful_origin(&collection) + .map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); + whitelist_account!(caller); + let admin = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let call = Call::::create { admin, config: default_collection_config::() }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into()); + } + + force_create { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + }: _(SystemOrigin::Root, caller_lookup, default_collection_config::()) + verify { + assert_last_event::(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into()); + } + + destroy { + let m in 0 .. 1_000; + let c in 0 .. 1_000; + let a in 0 .. 1_000; + + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + for i in 0..m { + mint_item::(i as u16); + add_item_metadata::(T::Helper::item(i as u16)); + lock_item::(i as u16); + burn_item::(i as u16); + } + for i in 0..c { + mint_item::(i as u16); + lock_item::(i as u16); + burn_item::(i as u16); + } + for i in 0..a { + add_collection_attribute::(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, 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()); + } + + burn { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); + } + + transfer { + 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()); + 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()); + } + + redeposit { + let i in 0 .. 5_000; + 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(), + collection, + make_collection_config::(CollectionSetting::DepositRequired.into()), + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) + verify { + assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); + } + + lock_item_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) + verify { + assert_last_event::(Event::ItemTransferLocked { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); + } + + unlock_item_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + Nfts::::lock_item_transfer( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::ItemTransferUnlocked { collection, item }.into()); + } + + lock_collection { + let (collection, caller, _) = create_collection::(); + let lock_settings = CollectionSettings::from_disabled( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes | + CollectionSetting::UnlockedMaxSupply, + ); + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) + verify { + assert_last_event::(Event::CollectionLocked { 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(); + Nfts::::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 = Some(T::Lookup::unlookup(account("target", 0, SEED))); + let target1 = Some(T::Lookup::unlookup(account("target", 1, SEED))); + let target2 = Some(T::Lookup::unlookup(account("target", 2, SEED))); + }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) + verify { + assert_last_event::(Event::TeamChanged{ + collection, + issuer: Some(account("target", 0, SEED)), + admin: Some(account("target", 1, SEED)), + freezer: Some(account("target", 2, SEED)), + }.into()); + } + + force_collection_owner { + let (collection, _, _) = create_collection::(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let call = Call::::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, _) = create_collection::(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let call = Call::::force_collection_config { + collection, + config: make_collection_config::(CollectionSetting::DepositRequired.into()), + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::CollectionConfigChanged { collection }.into()); + } + + lock_item_properties { + let (collection, caller, _) = 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 { + 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::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), + namespace: AttributeNamespace::CollectionOwner, + 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), 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::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 key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); + 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 { + 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()) + verify { + assert_last_event::(Event::ItemMetadataSet { collection, item, data }.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::ItemMetadataCleared { collection, item }.into()); + } + + set_collection_metadata { + let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller), collection, data.clone()) + verify { + assert_last_event::(Event::CollectionMetadataSet { collection, data }.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()); + let deadline = BlockNumberFor::::max_value(); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + verify { + assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.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(); + let deadline = BlockNumberFor::::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 = BlockNumberFor::::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()); + 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()); + } + + 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); + 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(); + Nfts::::set_price(origin, collection, item, Some(price), Some(buyer_lookup))?; + T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price) + verify { + assert_last_event::(Event::ItemBought { + collection, + item, + price, + seller, + buyer, + }.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()); + } + } + + 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()); + 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)?; + 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()); + } + + mint_pre_signed { + let n in 0 .. T::MaxAttributesPerCall::get() as u32; + let (caller_public, caller) = T::Helper::signer(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + + let collection = T::Helper::collection(0); + let item = T::Helper::item(0); + assert_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + default_collection_config::() + )); + + let metadata = vec![0u8; T::StringLimit::get() as usize]; + let mut attributes = vec![]; + let attribute_value = vec![0u8; T::ValueLimit::get() as usize]; + for i in 0..n { + let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); + attributes.push((attribute_key, attribute_value.clone())); + } + let mint_data = PreSignedMint { + collection, + item, + attributes, + metadata: metadata.clone(), + only_account: None, + deadline: One::one(), + mint_price: Some(DepositBalanceOf::::min_value()), + }; + let message = Encode::encode(&mint_data); + let signature = T::Helper::sign(&caller_public, &message); + + let target: T::AccountId = account("target", 0, SEED); + T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(target.clone()), Box::new(mint_data), signature.into(), caller) + verify { + let metadata: BoundedVec<_, _> = metadata.try_into().unwrap(); + assert_last_event::(Event::ItemMetadataSet { collection, item, data: metadata }.into()); + } + + set_attributes_pre_signed { + let n in 0 .. T::MaxAttributesPerCall::get() as u32; + let (collection, _, _) = create_collection::(); + + let item_owner: T::AccountId = account("item_owner", 0, SEED); + let item_owner_lookup = T::Lookup::unlookup(item_owner.clone()); + + let (signer_public, signer) = T::Helper::signer(); + + T::Currency::make_free_balance_be(&item_owner, DepositBalanceOf::::max_value()); + + let item = T::Helper::item(0); + assert_ok!(Nfts::::force_mint( + SystemOrigin::Root.into(), + collection, + item, + item_owner_lookup.clone(), + default_item_config(), + )); + + let mut attributes = vec![]; + let attribute_value = vec![0u8; T::ValueLimit::get() as usize]; + for i in 0..n { + let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); + attributes.push((attribute_key, attribute_value.clone())); + } + let pre_signed_data = PreSignedAttributes { + collection, + item, + attributes, + namespace: AttributeNamespace::Account(signer.clone()), + deadline: One::one(), + }; + let message = Encode::encode(&pre_signed_data); + let signature = T::Helper::sign(&signer_public, &message); + + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(item_owner.clone()), pre_signed_data, signature.into(), signer.clone()) + verify { + assert_last_event::( + Event::PreSignedAttributesSet { + collection, + item, + namespace: AttributeNamespace::Account(signer.clone()), + } + .into(), + ); + } + + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs new file mode 100644 index 00000000..2c4778c1 --- /dev/null +++ b/pallets/nfts/src/common_functions.rs @@ -0,0 +1,81 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Various pieces of common functionality. + +use crate::*; +use alloc::vec::Vec; +use frame_support::pallet_prelude::*; + +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 collection, if the collection exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + + /// Validates the signature of the given data with the provided signer's account ID. + /// + /// # Errors + /// + /// This function returns a [`WrongSignature`](crate::Error::WrongSignature) error if the + /// signature is invalid or the verification process fails. + pub fn validate_signature( + data: &Vec, + signature: &T::OffchainSignature, + signer: &T::AccountId, + ) -> DispatchResult { + if signature.verify(&**data, &signer) { + return Ok(()) + } + + // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into + // , that's why we support both wrapped and raw versions. + let prefix = b""; + let suffix = b""; + let mut wrapped: Vec = Vec::with_capacity(data.len() + prefix.len() + suffix.len()); + wrapped.extend(prefix); + wrapped.extend(data); + wrapped.extend(suffix); + + ensure!(signature.verify(&*wrapped, &signer), Error::::WrongSignature); + + Ok(()) + } + + pub(crate) fn set_next_collection_id(collection: T::CollectionId) { + let next_id = collection.increment(); + NextCollectionId::::set(next_id); + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get() + .or(T::CollectionId::initial_value()) + .expect("Failed to get next collection ID") + } +} diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs new file mode 100644 index 00000000..053fa671 --- /dev/null +++ b/pallets/nfts/src/features/approvals.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper functions for the approval logic implemented in the NFTs pallet. +//! The bitflag [`PalletFeature::Approvals`] needs to be set in [`Config::Features`] for NFTs +//! to have the functionality defined in this module. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Approves the transfer of an item to a delegate. + /// + /// This function is used to approve the transfer of the specified `item` in the `collection` to + /// a `delegate`. If `maybe_check_origin` is specified, the function ensures that the + /// `check_origin` account is the owner of the item, granting them permission to approve the + /// transfer. The `delegate` is the account that will be allowed to take control of the item. + /// Optionally, a `deadline` can be specified to set a time limit for the approval. The + /// `deadline` is expressed in block numbers and is added to the current block number to + /// determine the absolute deadline for the approval. After approving the transfer, the function + /// emits the `TransferApproved` event. + /// + /// - `maybe_check_origin`: The optional account that is required to be the owner of the item, + /// granting permission to approve the transfer. If `None`, no permission check is performed. + /// - `collection`: The identifier of the collection containing the item to be transferred. + /// - `item`: The identifier of the item to be transferred. + /// - `delegate`: The account that will be allowed to take control of the item. + /// - `maybe_deadline`: The optional deadline (in block numbers) specifying the time limit for + /// the approval. + pub(crate) fn do_approve_transfer( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + maybe_deadline: Option>, + ) -> 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 { + ensure!(check_origin == details.owner, 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::TransferApproved { + collection, + item, + owner: details.owner, + delegate, + deadline, + }); + + Ok(()) + } + + /// Cancels the approval for the transfer of an item to a delegate. + /// + /// This function is used to cancel the approval for the transfer of the specified `item` in the + /// `collection` to a `delegate`. If `maybe_check_origin` is specified, the function ensures + /// that the `check_origin` account is the owner of the item or that the approval is past its + /// deadline, granting permission to cancel the approval. After canceling the approval, the + /// function emits the `ApprovalCancelled` event. + /// + /// - `maybe_check_origin`: The optional account that is required to be the owner of the item or + /// that the approval is past its deadline, granting permission to cancel the approval. If + /// `None`, no permission check is performed. + /// - `collection`: The identifier of the collection containing the item. + /// - `item`: The identifier of the item. + /// - `delegate`: The account that was previously allowed to take control of the item. + pub(crate) fn do_cancel_approval( + maybe_check_origin: Option, + 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 { + ensure!(check_origin == details.owner, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + /// Clears all transfer approvals for an item. + /// + /// This function is used to clear all transfer approvals for the specified `item` in the + /// `collection`. If `maybe_check_origin` is specified, the function ensures that the + /// `check_origin` account is the owner of the item, granting permission to clear all transfer + /// approvals. After clearing all approvals, the function emits the `AllApprovalsCancelled` + /// event. + /// + /// - `maybe_check_origin`: The optional account that is required to be the owner of the item, + /// granting permission to clear all transfer approvals. If `None`, no permission check is + /// performed. + /// - `collection`: The collection ID containing the item. + /// - `item`: The item ID for which transfer approvals will be cleared. + pub(crate) fn do_clear_all_transfer_approvals( + maybe_check_origin: Option, + 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 { + ensure!(check_origin == details.owner, Error::::NoPermission); + } + + details.approvals.clear(); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::AllApprovalsCancelled { + collection, + item, + owner: details.owner, + }); + + Ok(()) + } +} diff --git a/pallets/nfts/src/features/atomic_swap.rs b/pallets/nfts/src/features/atomic_swap.rs new file mode 100644 index 00000000..830283b7 --- /dev/null +++ b/pallets/nfts/src/features/atomic_swap.rs @@ -0,0 +1,234 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper functions for performing atomic swaps implemented in the NFTs +//! pallet. +//! The bitflag [`PalletFeature::Swaps`] needs to be set in [`Config::Features`] for NFTs +//! to have the functionality defined in this module. + +use crate::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + /// Creates a new swap offer for the specified item. + /// + /// This function is used to create a new swap offer for the specified item. The `caller` + /// account must be the owner of the item. The swap offer specifies the `offered_collection`, + /// `offered_item`, `desired_collection`, `maybe_desired_item`, `maybe_price`, and `duration`. + /// The `duration` specifies the deadline by which the swap must be claimed. If + /// `maybe_desired_item` is `Some`, the specified item is expected in return for the swap. If + /// `maybe_desired_item` is `None`, it indicates that any item from the `desired_collection` can + /// be offered in return. The `maybe_price` specifies an optional price for the swap. If + /// specified, the other party must offer the specified `price` or higher for the swap. After + /// creating the swap, the function emits the `SwapCreated` event. + /// + /// - `caller`: The account creating the swap offer, which must be the owner of the item. + /// - `offered_collection_id`: The collection ID containing the offered item. + /// - `offered_item_id`: The item ID offered for the swap. + /// - `desired_collection_id`: The collection ID containing the desired item (if any). + /// - `maybe_desired_item_id`: The ID of the desired item (if any). + /// - `maybe_price`: The optional price for the swap. + /// - `duration`: The duration (in block numbers) specifying the deadline for the swap claim. + pub(crate) fn do_create_swap( + caller: T::AccountId, + offered_collection_id: T::CollectionId, + offered_item_id: T::ItemId, + desired_collection_id: T::CollectionId, + maybe_desired_item_id: Option, + maybe_price: Option>>, + duration: frame_system::pallet_prelude::BlockNumberFor, + ) -> 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) + .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(()) + } + /// Cancels the specified swap offer. + /// + /// This function is used to cancel the specified swap offer created by the `caller` account. If + /// the swap offer's deadline has not yet passed, the `caller` must be the owner of the offered + /// item; otherwise, anyone can cancel an expired offer. + /// After canceling the swap offer, the function emits the `SwapCancelled` event. + /// + /// - `caller`: The account canceling the swap offer. + /// - `offered_collection_id`: The collection ID containing the offered item. + /// - `offered_item_id`: The item ID offered for the swap. + pub(crate) fn do_cancel_swap( + caller: T::AccountId, + offered_collection_id: T::CollectionId, + offered_item_id: T::ItemId, + ) -> DispatchResult { + let swap = PendingSwapOf::::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(()) + } + + /// Claims the specified swap offer. + /// + /// This function is used to claim a swap offer specified by the `send_collection_id`, + /// `send_item_id`, `receive_collection_id`, and `receive_item_id`. The `caller` account must be + /// the owner of the item specified by `send_collection_id` and `send_item_id`. If the claimed + /// swap has an associated `price`, it will be transferred between the owners of the two items + /// based on the `price.direction`. After the swap is completed, the function emits the + /// `SwapClaimed` event. + /// + /// - `caller`: The account claiming the swap offer, which must be the owner of the sent item. + /// - `send_collection_id`: The identifier of the collection containing the item being sent. + /// - `send_item_id`: The identifier of the item being sent for the swap. + /// - `receive_collection_id`: The identifier of the collection containing the item being + /// received. + /// - `receive_item_id`: The identifier of the item being received in the swap. + /// - `witness_price`: The optional witness price for the swap (price that was offered in the + /// swap). + pub(crate) fn do_claim_swap( + caller: T::AccountId, + send_collection_id: T::CollectionId, + send_item_id: T::ItemId, + receive_collection_id: T::CollectionId, + receive_item_id: T::ItemId, + witness_price: Option>>, + ) -> 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) + .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/pallets/nfts/src/features/attributes.rs b/pallets/nfts/src/features/attributes.rs new file mode 100644 index 00000000..28f7bd2c --- /dev/null +++ b/pallets/nfts/src/features/attributes.rs @@ -0,0 +1,525 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to configure attributes for items and collections in the +//! NFTs pallet. +//! The bitflag [`PalletFeature::Attributes`] needs to be set in [`Config::Features`] for NFTs +//! to have the functionality defined in this module. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Sets the attribute of an item or a collection. + /// + /// This function is used to set an attribute for an item or a collection. It checks the + /// provided `namespace` and verifies the permission of the caller to perform the action. The + /// `collection` and `maybe_item` parameters specify the target for the attribute. + /// + /// - `origin`: The account attempting to set the attribute. + /// - `collection`: The identifier of the collection to which the item belongs, or the + /// collection itself if setting a collection attribute. + /// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if + /// setting a collection attribute. + /// - `namespace`: The namespace in which the attribute is being set. It can be either + /// `CollectionOwner`, `ItemOwner`, or `Account` (pre-approved external address). + /// - `key`: The key of the attribute. It should be a vector of bytes within the limits defined + /// by `T::KeyLimit`. + /// - `value`: The value of the attribute. It should be a vector of bytes within the limits + /// defined by `T::ValueLimit`. + /// - `depositor`: The account that is paying the deposit for the attribute. + /// + /// Note: For the `CollectionOwner` namespace, the collection/item must have the + /// `UnlockedAttributes` setting enabled. + /// The deposit for setting an attribute is based on the `T::DepositPerByte` and + /// `T::AttributeDepositBase` configuration. + pub(crate) fn do_set_attribute( + origin: T::AccountId, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + depositor: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + ensure!( + Self::is_valid_namespace(&origin, &namespace, &collection, &maybe_item)?, + Error::::NoPermission + ); + + let collection_config = Self::get_collection_config(&collection)?; + // for the `CollectionOwner` namespace we need to check if the collection/item is not locked + match namespace { + AttributeNamespace::CollectionOwner => match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::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 mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + let attribute_exists = attribute.is_some(); + if !attribute_exists { + collection_details.attributes.saturating_inc(); + } + + let old_deposit = + attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); + + let mut deposit = Zero::zero(); + // disabled DepositRequired setting only affects the CollectionOwner namespace + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) || + namespace != AttributeNamespace::CollectionOwner + { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + + let is_collection_owner_namespace = namespace == AttributeNamespace::CollectionOwner; + let is_depositor_collection_owner = + is_collection_owner_namespace && collection_details.owner == depositor; + + // NOTE: in the CollectionOwner namespace if the depositor is `None` that means the deposit + // was paid by the collection's owner. + let old_depositor = + if is_collection_owner_namespace && old_deposit.account.is_none() && attribute_exists { + Some(collection_details.owner.clone()) + } else { + old_deposit.account + }; + let depositor_has_changed = old_depositor != Some(depositor.clone()); + + // NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace. + // When the new owner updates the same attribute, we will update the depositor record + // and return the deposit to the previous owner. + if depositor_has_changed { + if let Some(old_depositor) = old_depositor { + T::Currency::unreserve(&old_depositor, old_deposit.amount); + } + T::Currency::reserve(&depositor, deposit)?; + } else if deposit > old_deposit.amount { + T::Currency::reserve(&depositor, deposit - old_deposit.amount)?; + } else if deposit < old_deposit.amount { + T::Currency::unreserve(&depositor, old_deposit.amount - deposit); + } + + if is_depositor_collection_owner { + if !depositor_has_changed { + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); + } + collection_details.owner_deposit.saturating_accrue(deposit); + } + + let new_deposit_owner = match is_depositor_collection_owner { + true => None, + false => Some(depositor), + }; + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: new_deposit_owner, amount: deposit }), + ); + + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); + Ok(()) + } + + /// Sets the attribute of an item or a collection without performing deposit checks. + /// + /// This function is used to force-set an attribute for an item or a collection without + /// performing the deposit checks. It bypasses the deposit requirement and should only be used + /// in specific situations where deposit checks are not necessary or handled separately. + /// + /// - `set_as`: The account that would normally pay for the deposit. + /// - `collection`: The identifier of the collection to which the item belongs, or the + /// collection itself if setting a collection attribute. + /// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if + /// setting a collection attribute. + /// - `namespace`: The namespace in which the attribute is being set. It can be either + /// `CollectionOwner`, `ItemOwner`, or `Account` (pre-approved external address). + /// - `key`: The key of the attribute. It should be a vector of bytes within the limits defined + /// by `T::KeyLimit`. + /// - `value`: The value of the attribute. It should be a vector of bytes within the limits + /// defined by `T::ValueLimit`. + pub(crate) fn do_force_set_attribute( + set_as: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + 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(); + } + + 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(()) + } + + /// Sets multiple attributes for an item or a collection. + /// + /// This function checks the pre-signed data is valid and updates the attributes of an item or + /// collection. It is limited by [`Config::MaxAttributesPerCall`] to prevent excessive storage + /// consumption in a single transaction. + /// + /// - `origin`: The account initiating the transaction. + /// - `data`: The data containing the details of the pre-signed attributes to be set. + /// - `signer`: The account of the pre-signed attributes signer. + pub(crate) fn do_set_attributes_pre_signed( + origin: T::AccountId, + data: PreSignedAttributesOf, + signer: T::AccountId, + ) -> DispatchResult { + let PreSignedAttributes { collection, item, attributes, namespace, deadline } = data; + + ensure!( + attributes.len() <= T::MaxAttributesPerCall::get() as usize, + Error::::MaxAttributesLimitReached + ); + + let now = frame_system::Pallet::::block_number(); + ensure!(deadline >= now, Error::::DeadlineExpired); + + let item_details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(item_details.owner == origin, Error::::NoPermission); + + // Only the CollectionOwner and Account() namespaces could be updated in this way. + // For the Account() namespace we check and set the approval if it wasn't set before. + match &namespace { + AttributeNamespace::CollectionOwner => {}, + AttributeNamespace::Account(account) => { + ensure!(account == &signer, Error::::NoPermission); + let approvals = ItemAttributesApprovalsOf::::get(&collection, &item); + if !approvals.contains(account) { + Self::do_approve_item_attributes( + origin.clone(), + collection, + item, + account.clone(), + )?; + } + }, + _ => return Err(Error::::WrongNamespace.into()), + } + + for (key, value) in attributes { + Self::do_set_attribute( + signer.clone(), + collection, + Some(item), + namespace.clone(), + Self::construct_attribute_key(key)?, + Self::construct_attribute_value(value)?, + origin.clone(), + )?; + } + Self::deposit_event(Event::PreSignedAttributesSet { collection, item, namespace }); + Ok(()) + } + + /// Clears an attribute of an item or a collection. + /// + /// This function allows clearing an attribute from an item or a collection. It verifies the + /// permission of the caller to perform the action based on the provided `namespace` and + /// `depositor` account. The deposit associated with the attribute, if any, will be unreserved. + /// + /// - `maybe_check_origin`: An optional account that acts as an additional security check when + /// clearing the attribute. This can be `None` if no additional check is required. + /// - `collection`: The identifier of the collection to which the item belongs, or the + /// collection itself if clearing a collection attribute. + /// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if + /// clearing a collection attribute. + /// - `namespace`: The namespace in which the attribute is being cleared. It can be either + /// `CollectionOwner`, `ItemOwner`, or `Account`. + /// - `key`: The key of the attribute to be cleared. It should be a vector of bytes within the + /// limits defined by `T::KeyLimit`. + pub(crate) fn do_clear_attribute( + maybe_check_origin: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + ) -> DispatchResult { + let (_, deposit) = Attribute::::take((collection, maybe_item, &namespace, &key)) + .ok_or(Error::::AttributeNotFound)?; + + if let Some(check_origin) = &maybe_check_origin { + // validate the provided namespace when it's not a root call and the caller is not + // the same as the `deposit.account` (e.g. the deposit was paid by different account) + if deposit.account != maybe_check_origin { + ensure!( + Self::is_valid_namespace(&check_origin, &namespace, &collection, &maybe_item)?, + Error::::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(None, |c| { + Some(c.has_disabled_setting(ItemSetting::UnlockedAttributes)) + }); + if let Some(is_locked) = maybe_is_locked { + ensure!(!is_locked, Error::::LockedItemAttributes); + // Only the collection's admin can clear attributes in that namespace. + // e.g. in off-chain mints, the attribute's depositor will be the item's + // owner, that's why we need to do this extra check. + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + }, + }, + _ => (), + }; + } + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + collection_details.attributes.saturating_dec(); + + match deposit.account { + Some(deposit_account) => { + T::Currency::unreserve(&deposit_account, deposit.amount); + }, + None if namespace == AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_reduce(deposit.amount); + T::Currency::unreserve(&collection_details.owner, deposit.amount); + }, + _ => (), + } + + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace }); + + Ok(()) + } + + /// Approves a delegate to set attributes on behalf of the item's owner. + /// + /// This function allows the owner of an item to approve a delegate to set attributes in the + /// `Account(delegate)` namespace. The maximum number of approvals is determined by + /// the configuration `T::MaxAttributesApprovals`. + /// + /// - `check_origin`: The account of the item's owner attempting to approve the delegate. + /// - `collection`: The identifier of the collection to which the item belongs. + /// - `item`: The identifier of the item for which the delegate is being approved. + /// - `delegate`: The account that is being approved to set attributes on behalf of the item's + /// owner. + pub(crate) fn do_approve_item_attributes( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::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(()) + }) + } + + /// Cancels the approval of an item's attributes by a delegate. + /// + /// This function allows the owner of an item to cancel the approval of a delegate to set + /// attributes in the `Account(delegate)` namespace. The delegate's approval is removed, in + /// addition to attributes the `delegate` previously created, and any unreserved deposit + /// is returned. The number of attributes that the delegate has set for the item must + /// not exceed the `account_attributes` provided in the `witness`. + /// This function is used to prevent unintended or malicious cancellations. + /// + /// - `check_origin`: The account of the item's owner attempting to cancel the delegate's + /// approval. + /// - `collection`: The identifier of the collection to which the item belongs. + /// - `item`: The identifier of the item for which the delegate's approval is being canceled. + /// - `delegate`: The account whose approval is being canceled. + /// - `witness`: The witness containing the number of attributes set by the delegate for the + /// item. + pub(crate) fn do_cancel_item_attributes_approval( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::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(()) + }) + } + + /// A helper method to check whether an attribute namespace is valid. + fn is_valid_namespace( + origin: &T::AccountId, + namespace: &AttributeNamespace, + collection: &T::CollectionId, + maybe_item: &Option, + ) -> Result { + let mut result = false; + match namespace { + AttributeNamespace::CollectionOwner => + result = Self::has_role(&collection, &origin, CollectionRole::Admin), + AttributeNamespace::ItemOwner => + if let Some(item) = maybe_item { + let item_details = + Item::::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) + } + + /// A helper method to construct an attribute's key. + /// + /// # Errors + /// + /// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the + /// provided attribute `key` is too long. + pub fn construct_attribute_key( + key: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(key).map_err(|_| Error::::IncorrectData)?) + } + + /// A helper method to construct an attribute's value. + /// + /// # Errors + /// + /// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the + /// provided `value` is too long. + pub fn construct_attribute_value( + value: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(value).map_err(|_| Error::::IncorrectData)?) + } + + /// A helper method to check whether a system attribute is set for a given item. + /// + /// # Errors + /// + /// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the + /// provided pallet attribute is too long. + pub fn has_system_attribute( + collection: &T::CollectionId, + item: &T::ItemId, + attribute_key: PalletAttributes, + ) -> Result { + let attribute = ( + &collection, + Some(item), + AttributeNamespace::Pallet, + &Self::construct_attribute_key(attribute_key.encode())?, + ); + Ok(Attribute::::contains_key(attribute)) + } +} diff --git a/pallets/nfts/src/features/buy_sell.rs b/pallets/nfts/src/features/buy_sell.rs new file mode 100644 index 00000000..d6ec6f50 --- /dev/null +++ b/pallets/nfts/src/features/buy_sell.rs @@ -0,0 +1,172 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper functions to perform the buy and sell functionalities of the NFTs +//! pallet. +//! The bitflag [`PalletFeature::Trading`] needs to be set in the [`Config::Features`] for NFTs +//! to have the functionality defined in this module. + +use crate::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + /// Pays the specified tips to the corresponding receivers. + /// + /// This function is used to pay tips from the `sender` account to multiple receivers. The tips + /// are specified as a `BoundedVec` of `ItemTipOf` with a maximum length of `T::MaxTips`. For + /// each tip, the function transfers the `amount` to the `receiver` account. The sender is + /// responsible for ensuring the validity of the provided tips. + /// + /// - `sender`: The account that pays the tips. + /// - `tips`: A `BoundedVec` containing the tips to be paid, where each tip contains the + /// `collection`, `item`, `receiver`, and `amount`. + pub(crate) fn do_pay_tips( + sender: T::AccountId, + tips: BoundedVec, T::MaxTips>, + ) -> DispatchResult { + for tip in tips { + let ItemTip { collection, item, receiver, amount } = tip; + T::Currency::transfer(&sender, &receiver, amount, KeepAlive)?; + Self::deposit_event(Event::TipSent { + collection, + item, + sender: sender.clone(), + receiver, + amount, + }); + } + Ok(()) + } + + /// Sets the price and whitelists a buyer for an item in the specified collection. + /// + /// This function is used to set the price and whitelist a buyer for an item in the + /// specified `collection`. The `sender` account must be the owner of the item. The item's price + /// and the whitelisted buyer can be set to allow trading the item. If `price` is `None`, the + /// item will be marked as not for sale. + /// + /// - `collection`: The identifier of the collection containing the item. + /// - `item`: The identifier of the item for which the price and whitelist information will be + /// set. + /// - `sender`: The account that sets the price and whitelist information for the item. + /// - `price`: The optional price for the item. + /// - `whitelisted_buyer`: The optional account that is whitelisted to buy the item at the set + /// price. + pub(crate) fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + 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(()) + } + + /// Buys the specified item from the collection. + /// + /// This function is used to buy an item from the specified `collection`. The `buyer` account + /// will attempt to buy the item with the provided `bid_price`. The item's current owner will + /// receive the bid price if it is equal to or higher than the item's set price. If + /// `whitelisted_buyer` is specified in the item's price information, only that account is + /// allowed to buy the item. If the item is not for sale, or the bid price is too low, the + /// function will return an error. + /// + /// - `collection`: The identifier of the collection containing the item to be bought. + /// - `item`: The identifier of the item to be bought. + /// - `buyer`: The account that attempts to buy the item. + /// - `bid_price`: The bid price offered by the buyer for the item. + pub(crate) fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> 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/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs new file mode 100644 index 00000000..f03df7fd --- /dev/null +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -0,0 +1,153 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to perform functionality associated with creating and +//! destroying collections for the NFTs pallet. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Create a new collection with the given `collection`, `owner`, `admin`, `config`, `deposit`, + /// and `event`. + /// + /// This function creates a new collection with the provided parameters. It reserves the + /// required deposit from the owner's account, sets the collection details, assigns admin roles, + /// and inserts the provided configuration. Finally, it emits the specified event upon success. + /// + /// # Errors + /// + /// This function returns a [`CollectionIdInUse`](crate::Error::CollectionIdInUse) error if the + /// collection ID is already in use. + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + config: CollectionConfigFor, + deposit: DepositBalanceOf, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + owner_deposit: deposit, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }, + ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); + + CollectionConfigOf::::insert(&collection, config); + CollectionAccount::::insert(&owner, &collection, ()); + + Self::deposit_event(event); + + if let Some(max_supply) = config.max_supply { + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + } + + Ok(()) + } + + /// Destroy the specified collection with the given `collection`, `witness`, and + /// `maybe_check_owner`. + /// + /// This function destroys the specified collection if it exists and meets the necessary + /// conditions. It checks the provided `witness` against the actual collection details and + /// removes the collection along with its associated metadata, attributes, and configurations. + /// The necessary deposits are returned to the corresponding accounts, and the roles and + /// configurations for the collection are cleared. Finally, it emits the `Destroyed` event upon + /// successful destruction. + /// + /// # Errors + /// + /// This function returns a dispatch error in the following cases: + /// - If the collection ID is not found + /// ([`UnknownCollection`](crate::Error::UnknownCollection)). + /// - If the provided `maybe_check_owner` does not match the actual owner + /// ([`NoPermission`](crate::Error::NoPermission)). + /// - If the collection is not empty (contains items) + /// ([`CollectionNotEmpty`](crate::Error::CollectionNotEmpty)). + /// - If the `witness` does not match the actual collection details + /// ([`BadWitness`](crate::Error::BadWitness)). + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> 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 == 0, Error::::CollectionNotEmpty); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!( + collection_details.item_configs == witness.item_configs, + Error::::BadWitness + ); + + for (_, metadata) in ItemMetadataOf::::drain_prefix(&collection) { + if let Some(depositor) = metadata.deposit.account { + T::Currency::unreserve(&depositor, metadata.deposit.amount); + } + } + + CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; + + 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.owner_deposit); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.item_configs, None); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + item_metadatas: collection_details.item_metadatas, + item_configs: collection_details.item_configs, + attributes: collection_details.attributes, + }) + }) + } +} diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs new file mode 100644 index 00000000..37f64ae1 --- /dev/null +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -0,0 +1,273 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to perform functionality associated with minting and burning +//! items for the NFTs pallet. + +use crate::*; +use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; + +impl, I: 'static> Pallet { + /// Mint a new unique item with the given `collection`, `item`, and other minting configuration + /// details. + /// + /// This function performs the minting of a new unique item. It checks if the item does not + /// already exist in the given collection, and if the max supply limit (if configured) is not + /// reached. It also reserves the required deposit for the item and sets the item details + /// accordingly. + /// + /// # Errors + /// + /// This function returns a dispatch error in the following cases: + /// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)). + /// - If the item already exists in the collection + /// ([`AlreadyExists`](crate::Error::AlreadyExists)). + /// - If the item configuration already exists + /// ([`InconsistentItemConfig`](crate::Error::InconsistentItemConfig)). + /// - If the max supply limit (if configured) for the collection is reached + /// ([`MaxSupplyReached`](crate::Error::MaxSupplyReached)). + /// - If any error occurs in the `with_details_and_config` closure. + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + maybe_depositor: Option, + mint_to: T::AccountId, + item_config: ItemConfig, + 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); + } + + collection_details.items.saturating_inc(); + + let collection_config = Self::get_collection_config(&collection)?; + let deposit_amount = match collection_config + .is_setting_enabled(CollectionSetting::DepositRequired) + { + true => T::ItemDeposit::get(), + false => Zero::zero(), + }; + let deposit_account = match maybe_depositor { + None => collection_details.owner.clone(), + Some(depositor) => depositor, + }; + + let item_owner = mint_to.clone(); + Account::::insert((&item_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); + collection_details.item_configs.saturating_inc(); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = ItemDetails { + owner: item_owner, + approvals: ApprovalsOf::::default(), + deposit, + }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner: mint_to }); + Ok(()) + } + + /// Mints a new item using a pre-signed message. + /// + /// This function allows minting a new item using a pre-signed message. The minting process is + /// similar to the regular minting process, but it is performed by a pre-authorized account. The + /// `mint_to` account receives the newly minted item. The minting process is configurable + /// through the provided `mint_data`. The attributes, metadata, and price of the item are set + /// according to the provided `mint_data`. The `with_details_and_config` closure is called to + /// validate the provided `collection_details` and `collection_config` before minting the item. + /// + /// - `mint_to`: The account that receives the newly minted item. + /// - `mint_data`: The pre-signed minting data containing the `collection`, `item`, + /// `attributes`, `metadata`, `deadline`, `only_account`, and `mint_price`. + /// - `signer`: The account that is authorized to mint the item using the pre-signed message. + pub(crate) fn do_mint_pre_signed( + mint_to: T::AccountId, + mint_data: PreSignedMintOf, + signer: T::AccountId, + ) -> DispatchResult { + let PreSignedMint { + collection, + item, + attributes, + metadata, + deadline, + only_account, + mint_price, + } = mint_data; + let metadata = Self::construct_metadata(metadata)?; + + ensure!( + attributes.len() <= T::MaxAttributesPerCall::get() as usize, + Error::::MaxAttributesLimitReached + ); + if let Some(account) = only_account { + ensure!(account == mint_to, Error::::WrongOrigin); + } + + let now = frame_system::Pallet::::block_number(); + ensure!(deadline >= now, Error::::DeadlineExpired); + + ensure!( + Self::has_role(&collection, &signer, CollectionRole::Issuer), + Error::::NoPermission + ); + + let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? }; + Self::do_mint( + collection, + item, + Some(mint_to.clone()), + mint_to.clone(), + item_config, + |collection_details, _| { + if let Some(price) = mint_price { + T::Currency::transfer( + &mint_to, + &collection_details.owner, + price, + ExistenceRequirement::KeepAlive, + )?; + } + Ok(()) + }, + )?; + let admin_account = Self::find_account_by_role(&collection, CollectionRole::Admin); + if let Some(admin_account) = admin_account { + for (key, value) in attributes { + Self::do_set_attribute( + admin_account.clone(), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + Self::construct_attribute_key(key)?, + Self::construct_attribute_value(value)?, + mint_to.clone(), + )?; + } + if !metadata.len().is_zero() { + Self::do_set_item_metadata( + Some(admin_account.clone()), + collection, + item, + metadata, + Some(mint_to.clone()), + )?; + } + } + Ok(()) + } + + /// Burns the specified item with the given `collection`, `item`, and `with_details`. + /// + /// # Errors + /// + /// This function returns a dispatch error in the following cases: + /// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)). + /// - If the item is locked ([`ItemLocked`](crate::Error::ItemLocked)). + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + ensure!( + !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, + Error::::ItemLocked + ); + let item_config = Self::get_item_config(&collection, &item)?; + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the config record and don't remove it + let remove_config = !item_config.has_disabled_settings(); + let owner = Collection::::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(); + + if remove_config { + collection_details.item_configs.saturating_dec(); + } + + // Clear the metadata if it's not locked. + if item_config.is_setting_enabled(ItemSetting::UnlockedMetadata) { + if let Some(metadata) = ItemMetadataOf::::take(&collection, &item) { + let depositor_account = + metadata.deposit.account.unwrap_or(collection_details.owner.clone()); + + T::Currency::unreserve(&depositor_account, metadata.deposit.amount); + collection_details.item_metadatas.saturating_dec(); + + if depositor_account == collection_details.owner { + collection_details + .owner_deposit + .saturating_reduce(metadata.deposit.amount); + } + } + } + + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + ItemAttributesApprovalsOf::::remove(&collection, &item); + + if remove_config { + ItemConfigOf::::remove(&collection, &item); + } + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } +} diff --git a/pallets/nfts/src/features/lock.rs b/pallets/nfts/src/features/lock.rs new file mode 100644 index 00000000..1c3c9c86 --- /dev/null +++ b/pallets/nfts/src/features/lock.rs @@ -0,0 +1,167 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to configure locks on collections and items for the NFTs +//! pallet. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Locks a collection with specified settings. + /// + /// The origin must be the owner of the collection to lock it. This function disables certain + /// settings on the collection. The only setting that can't be disabled is `DepositRequired`. + /// + /// Note: it's possible only to lock the setting, but not to unlock it after. + + /// + /// - `origin`: The origin of the transaction, representing the account attempting to lock the + /// collection. + /// - `collection`: The identifier of the collection to be locked. + /// - `lock_settings`: The collection settings to be locked. + pub(crate) fn do_lock_collection( + origin: T::AccountId, + collection: T::CollectionId, + lock_settings: CollectionSettings, + ) -> DispatchResult { + ensure!(Self::collection_owner(collection) == Some(origin), Error::::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)?; + + for setting in lock_settings.get_disabled() { + config.disable_setting(setting); + } + + Self::deposit_event(Event::::CollectionLocked { collection }); + Ok(()) + }) + } + + /// Locks the transfer of an item within a collection. + /// + /// The origin must have the `Freezer` role within the collection to lock the transfer of the + /// item. This function disables the `Transferable` setting on the item, preventing it from + /// being transferred to other accounts. + /// + /// - `origin`: The origin of the transaction, representing the account attempting to lock the + /// item transfer. + /// - `collection`: The identifier of the collection to which the item belongs. + /// - `item`: The identifier of the item to be locked for transfer. + pub(crate) fn do_lock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::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(()) + } + + /// Unlocks the transfer of an item within a collection. + /// + /// The origin must have the `Freezer` role within the collection to unlock the transfer of the + /// item. This function enables the `Transferable` setting on the item, allowing it to be + /// transferred to other accounts. + /// + /// - `origin`: The origin of the transaction, representing the account attempting to unlock the + /// item transfer. + /// - `collection`: The identifier of the collection to which the item belongs. + /// - `item`: The identifier of the item to be unlocked for transfer. + pub(crate) fn do_unlock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::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(()) + } + + /// Locks the metadata and attributes of an item within a collection. + /// + /// The origin must have the `Admin` role within the collection to lock the metadata and + /// attributes of the item. This function disables the `UnlockedMetadata` and + /// `UnlockedAttributes` settings on the item, preventing modifications to its metadata and + /// attributes. + /// + /// - `maybe_check_origin`: An optional origin representing the account attempting to lock the + /// item properties. If provided, this account must have the `Admin` role within the + /// collection. If `None`, no permission check is performed, and the function can be called + /// from any origin. + /// - `collection`: The identifier of the collection to which the item belongs. + /// - `item`: The identifier of the item to be locked for properties. + /// - `lock_metadata`: A boolean indicating whether to lock the metadata of the item. + /// - `lock_attributes`: A boolean indicating whether to lock the attributes of the item. + pub(crate) fn do_lock_item_properties( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::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/pallets/nfts/src/features/metadata.rs b/pallets/nfts/src/features/metadata.rs new file mode 100644 index 00000000..26006160 --- /dev/null +++ b/pallets/nfts/src/features/metadata.rs @@ -0,0 +1,282 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to configure the metadata of collections and items. + +use crate::*; +use alloc::vec::Vec; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Sets the metadata for a specific item within a collection. + /// + /// - `maybe_check_origin`: An optional account ID that is allowed to set the metadata. If + /// `None`, it's considered the root account. + /// - `collection`: The ID of the collection to which the item belongs. + /// - `item`: The ID of the item to set the metadata for. + /// - `data`: The metadata to set for the item. + /// - `maybe_depositor`: An optional account ID that will provide the deposit for the metadata. + /// If `None`, the collection's owner provides the deposit. + /// + /// Emits `ItemMetadataSet` event upon successful setting of the metadata. + /// Returns `Ok(())` on success, or one of the following dispatch errors: + /// - `UnknownCollection`: The specified collection does not exist. + /// - `UnknownItem`: The specified item does not exist within the collection. + /// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified. + /// - `NoPermission`: The caller does not have the required permission to set the metadata. + /// - `DepositExceeded`: The deposit amount exceeds the maximum allowed value. + pub(crate) fn do_set_item_metadata( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + maybe_depositor: Option, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + is_root || item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + + 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(ItemMetadataDeposit { account: None, amount: Zero::zero() }, |m| m.deposit); + + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && !is_root + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + + let depositor = maybe_depositor.clone().unwrap_or(collection_details.owner.clone()); + let old_depositor = old_deposit.account.unwrap_or(collection_details.owner.clone()); + + if depositor != old_depositor { + T::Currency::unreserve(&old_depositor, old_deposit.amount); + T::Currency::reserve(&depositor, deposit)?; + } else if deposit > old_deposit.amount { + T::Currency::reserve(&depositor, deposit - old_deposit.amount)?; + } else if deposit < old_deposit.amount { + T::Currency::unreserve(&depositor, old_deposit.amount - deposit); + } + + if maybe_depositor.is_none() { + collection_details.owner_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); + } + + *metadata = Some(ItemMetadata { + deposit: ItemMetadataDeposit { account: maybe_depositor, amount: deposit }, + data: data.clone(), + }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::ItemMetadataSet { collection, item, data }); + Ok(()) + }) + } + + /// Clears the metadata for a specific item within a collection. + /// + /// - `maybe_check_origin`: An optional account ID that is allowed to clear the metadata. If + /// `None`, it's considered the root account. + /// - `collection`: The ID of the collection to which the item belongs. + /// - `item`: The ID of the item for which to clear the metadata. + /// + /// Emits `ItemMetadataCleared` event upon successful clearing of the metadata. + /// Returns `Ok(())` on success, or one of the following dispatch errors: + /// - `UnknownCollection`: The specified collection does not exist. + /// - `MetadataNotFound`: The metadata for the specified item was not found. + /// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified. + /// - `NoPermission`: The caller does not have the required permission to clear the metadata. + pub(crate) fn do_clear_item_metadata( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); + let metadata = ItemMetadataOf::::take(collection, item) + .ok_or(Error::::MetadataNotFound)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let depositor_account = + metadata.deposit.account.unwrap_or(collection_details.owner.clone()); + + // NOTE: if the item was previously burned, the ItemConfigOf record might not exist + let is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); + + ensure!(is_root || !is_locked, Error::::LockedItemMetadata); + + collection_details.item_metadatas.saturating_dec(); + T::Currency::unreserve(&depositor_account, metadata.deposit.amount); + + if depositor_account == collection_details.owner { + collection_details.owner_deposit.saturating_reduce(metadata.deposit.amount); + } + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::ItemMetadataCleared { collection, item }); + + Ok(()) + } + + /// Sets the metadata for a specific collection. + /// + /// - `maybe_check_origin`: An optional account ID that is allowed to set the collection + /// metadata. If `None`, it's considered the root account. + /// - `collection`: The ID of the collection for which to set the metadata. + /// - `data`: The metadata to set for the collection. + /// + /// Emits `CollectionMetadataSet` event upon successful setting of the metadata. + /// Returns `Ok(())` on success, or one of the following dispatch errors: + /// - `UnknownCollection`: The specified collection does not exist. + /// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be + /// modified. + /// - `NoPermission`: The caller does not have the required permission to set the metadata. + pub(crate) fn do_set_collection_metadata( + maybe_check_origin: Option, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + is_root || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.owner_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if !is_root && collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&details.owner, old_deposit - deposit); + } + details.owner_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); + Ok(()) + }) + } + + /// Clears the metadata for a specific collection. + /// + /// - `maybe_check_origin`: An optional account ID that is allowed to clear the collection + /// metadata. If `None`, it's considered the root account. + /// - `collection`: The ID of the collection for which to clear the metadata. + /// + /// Emits `CollectionMetadataCleared` event upon successful clearing of the metadata. + /// Returns `Ok(())` on success, or one of the following dispatch errors: + /// - `UnknownCollection`: The specified collection does not exist. + /// - `MetadataNotFound`: The metadata for the collection was not found. + /// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be + /// modified. + /// - `NoPermission`: The caller does not have the required permission to clear the metadata. + pub(crate) fn do_clear_collection_metadata( + maybe_check_origin: Option, + collection: T::CollectionId, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let collection_config = Self::get_collection_config(&collection)?; + + ensure!( + maybe_check_origin.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); + details.owner_deposit.saturating_reduce(deposit); + Collection::::insert(&collection, details); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } + + /// A helper method to construct metadata. + /// + /// # Errors + /// + /// This function returns an [`IncorrectMetadata`](crate::Error::IncorrectMetadata) dispatch + /// error if the provided metadata is too long. + pub fn construct_metadata( + metadata: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(metadata).map_err(|_| Error::::IncorrectMetadata)?) + } +} diff --git a/pallets/nfts/src/features/mod.rs b/pallets/nfts/src/features/mod.rs new file mode 100644 index 00000000..752feaf5 --- /dev/null +++ b/pallets/nfts/src/features/mod.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod approvals; +pub mod atomic_swap; +pub mod attributes; +pub mod buy_sell; +pub mod create_delete_collection; +pub mod create_delete_item; +pub mod lock; +pub mod metadata; +pub mod roles; +pub mod settings; +pub mod transfer; diff --git a/pallets/nfts/src/features/roles.rs b/pallets/nfts/src/features/roles.rs new file mode 100644 index 00000000..aa6394f7 --- /dev/null +++ b/pallets/nfts/src/features/roles.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to configure account roles for existing collections. + +use crate::*; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Set the team roles for a specific collection. + /// + /// - `maybe_check_owner`: An optional account ID used to check ownership permission. If `None`, + /// it is considered as the root. + /// - `collection`: The ID of the collection for which to set the team roles. + /// - `issuer`: An optional account ID representing the issuer role. + /// - `admin`: An optional account ID representing the admin role. + /// - `freezer`: An optional account ID representing the freezer role. + /// + /// This function allows the owner or the root (when `maybe_check_owner` is `None`) to set the + /// team roles for a specific collection. The root can change the role from `None` to + /// `Some(account)`, but other roles can only be updated by the root or an account with an + /// existing role in the collection. + pub(crate) fn do_set_team( + maybe_check_owner: Option, + collection: T::CollectionId, + issuer: Option, + admin: Option, + freezer: Option, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + let is_root = maybe_check_owner.is_none(); + if let Some(check_origin) = maybe_check_owner { + ensure!(check_origin == details.owner, Error::::NoPermission); + } + + let roles_map = [ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]; + + // only root can change the role from `None` to `Some(account)` + if !is_root { + for (account, role) in roles_map.iter() { + if account.is_some() { + ensure!( + Self::find_account_by_role(&collection, *role).is_some(), + Error::::NoPermission + ); + } + } + } + + let roles = roles_map + .into_iter() + .filter_map(|(account, role)| account.map(|account| (account, role))) + .collect(); + + let account_to_role = Self::group_roles_by_account(roles); + + // Delete the previous records. + Self::clear_roles(&collection)?; + + // Insert new records. + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); + Ok(()) + }) + } + + /// Clears all the roles in a specified collection. + /// + /// - `collection_id`: A collection to clear the roles in. + /// + /// This function clears all the roles associated with the given `collection_id`. It throws an + /// error if some of the roles were left in storage, indicating that the maximum number of roles + /// may need to be adjusted. + pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> { + let res = CollectionRoleOf::::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 `true` if the account has the specified role, `false` otherwise. + pub(crate) fn has_role( + collection_id: &T::CollectionId, + account_id: &T::AccountId, + role: CollectionRole, + ) -> bool { + CollectionRoleOf::::get(&collection_id, &account_id) + .map_or(false, |roles| roles.has_role(role)) + } + + /// Finds the account by a provided role within a collection. + /// + /// - `collection_id`: A collection to check the role in. + /// - `role`: A role to find the account for. + /// + /// Returns `Some(T::AccountId)` if the record was found, `None` otherwise. + pub(crate) fn find_account_by_role( + collection_id: &T::CollectionId, + role: CollectionRole, + ) -> Option { + CollectionRoleOf::::iter_prefix(&collection_id).into_iter().find_map( + |(account, roles)| if roles.has_role(role) { Some(account.clone()) } else { None }, + ) + } + + /// Groups provided roles by account, given one account could have multiple roles. + /// + /// - `input`: A vector of (Account, Role) tuples. + /// + /// Returns a grouped vector of `(Account, Roles)` tuples. + pub fn group_roles_by_account( + input: Vec<(T::AccountId, CollectionRole)>, + ) -> Vec<(T::AccountId, CollectionRoles)> { + let mut result = BTreeMap::new(); + for (account, role) in input.into_iter() { + result.entry(account).or_insert(CollectionRoles::none()).add_role(role); + } + result.into_iter().collect() + } +} diff --git a/pallets/nfts/src/features/settings.rs b/pallets/nfts/src/features/settings.rs new file mode 100644 index 00000000..d4f7533f --- /dev/null +++ b/pallets/nfts/src/features/settings.rs @@ -0,0 +1,178 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module provides helper methods to configure collection settings for the NFTs pallet. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Forcefully change the configuration of a collection. + /// + /// - `collection`: The ID of the collection for which to update the configuration. + /// - `config`: The new collection configuration to set. + /// + /// This function allows for changing the configuration of a collection without any checks. + /// It updates the collection configuration and emits a `CollectionConfigChanged` event. + pub(crate) fn do_force_collection_config( + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + ensure!(Collection::::contains_key(&collection), Error::::UnknownCollection); + CollectionConfigOf::::insert(&collection, config); + Self::deposit_event(Event::CollectionConfigChanged { collection }); + Ok(()) + } + + /// Set the maximum supply for a collection. + /// + /// - `maybe_check_owner`: An optional account ID used to check permissions. + /// - `collection`: The ID of the collection for which to set the maximum supply. + /// - `max_supply`: The new maximum supply to set for the collection. + /// + /// This function checks if the setting `UnlockedMaxSupply` is enabled in the collection + /// configuration. If it is not enabled, it returns an `Error::MaxSupplyLocked`. If + /// `maybe_check_owner` is `Some(owner)`, it checks if the caller of the function is the + /// owner of the collection. If the caller is not the owner and the `maybe_check_owner` + /// parameter is provided, it returns an `Error::NoPermission`. + /// + /// It also checks if the new maximum supply is greater than the current number of items in + /// the collection, and if not, it returns an `Error::MaxSupplyTooSmall`. If all checks pass, + /// it updates the collection configuration with the new maximum supply and emits a + /// `CollectionMaxSupplySet` event. + pub(crate) fn do_set_collection_max_supply( + maybe_check_owner: Option, + 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(()) + }) + } + + /// Update the mint settings for a collection. + /// + /// - `maybe_check_origin`: An optional account ID used to check issuer permissions. + /// - `collection`: The ID of the collection for which to update the mint settings. + /// - `mint_settings`: The new mint settings to set for the collection. + /// + /// This function updates the mint settings for a collection. If `maybe_check_origin` is + /// `Some(origin)`, it checks if the caller of the function has the `CollectionRole::Issuer` + /// for the given collection. If the caller doesn't have the required permission and + /// `maybe_check_origin` is provided, it returns an `Error::NoPermission`. If all checks + /// pass, it updates the collection configuration with the new mint settings and emits a + /// `CollectionMintSettingsUpdated` event. + pub(crate) fn do_update_mint_settings( + maybe_check_origin: Option, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + frame_system::pallet_prelude::BlockNumberFor, + T::CollectionId, + >, + ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Issuer), + 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(()) + }) + } + + /// Get the configuration for a specific collection. + /// + /// - `collection_id`: The ID of the collection for which to retrieve the configuration. + /// + /// This function attempts to fetch the configuration (`CollectionConfigFor`) associated + /// with the given `collection_id`. If the configuration exists, it returns `Ok(config)`, + /// otherwise, it returns a `DispatchError` with `Error::NoConfig`. + pub(crate) fn get_collection_config( + collection_id: &T::CollectionId, + ) -> Result, DispatchError> { + let config = + CollectionConfigOf::::get(&collection_id).ok_or(Error::::NoConfig)?; + Ok(config) + } + + /// Get the configuration for a specific item within a collection. + /// + /// - `collection_id`: The ID of the collection to which the item belongs. + /// - `item_id`: The ID of the item for which to retrieve the configuration. + /// + /// This function attempts to fetch the configuration (`ItemConfig`) associated with the given + /// `collection_id` and `item_id`. If the configuration exists, it returns `Ok(config)`, + /// otherwise, it returns a `DispatchError` with `Error::UnknownItem`. + pub(crate) fn get_item_config( + collection_id: &T::CollectionId, + item_id: &T::ItemId, + ) -> Result { + let config = ItemConfigOf::::get(&collection_id, &item_id) + .ok_or(Error::::UnknownItem)?; + Ok(config) + } + + /// Get the default item settings for a specific collection. + /// + /// - `collection_id`: The ID of the collection for which to retrieve the default item settings. + /// + /// This function fetches the `default_item_settings` from the collection configuration + /// associated with the given `collection_id`. If the collection configuration exists, it + /// returns `Ok(default_item_settings)`, otherwise, it returns a `DispatchError` with + /// `Error::NoConfig`. + pub(crate) fn get_default_item_settings( + collection_id: &T::CollectionId, + ) -> Result { + let collection_config = Self::get_collection_config(collection_id)?; + Ok(collection_config.mint_settings.default_item_settings) + } + + /// Check if a specified pallet feature is enabled. + /// + /// - `feature`: The feature to check. + /// + /// This function checks if the given `feature` is enabled in the runtime using the + /// pallet's `T::Features::get()` function. It returns `true` if the feature is enabled, + /// otherwise it returns `false`. + pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool { + let features = T::Features::get(); + return features.is_enabled(feature) + } +} diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs new file mode 100644 index 00000000..bba83448 --- /dev/null +++ b/pallets/nfts/src/features/transfer.rs @@ -0,0 +1,234 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains helper methods to perform the transfer functionalities +//! of the NFTs pallet. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + /// Transfer an NFT to the specified destination account. + /// + /// - `collection`: The ID of the collection to which the NFT belongs. + /// - `item`: The ID of the NFT to transfer. + /// - `dest`: The destination account to which the NFT will be transferred. + /// - `with_details`: A closure that provides access to the collection and item details, + /// allowing customization of the transfer process. + /// + /// This function performs the actual transfer of an NFT to the destination account. + /// It checks various conditions like item lock status and transferability settings + /// for the collection and item before transferring the NFT. + /// + /// # Errors + /// + /// This function returns a dispatch error in the following cases: + /// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)). + /// - If the item ID is invalid ([`UnknownItem`](crate::Error::UnknownItem)). + /// - If the item is locked or transferring it is disabled + /// ([`ItemLocked`](crate::Error::ItemLocked)). + /// - If the collection or item is non-transferable + /// ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)). + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + // Retrieve collection details. + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + // Ensure the item is not locked. + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + // Ensure the item is not transfer disabled on the system level attribute. + ensure!( + !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, + Error::::ItemLocked + ); + + // Retrieve collection config and check if items are transferable. + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + // Retrieve item config and check if the item is transferable. + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + // Retrieve the item details. + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + // Perform the transfer with custom details using the provided closure. + with_details(&collection_details, &mut details)?; + + // Update account ownership information. + 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 their second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + + // Update item details. + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + // Emit `Transferred` event. + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + /// Transfer ownership of a collection to another account. + /// + /// - `origin`: The account requesting the transfer. + /// - `collection`: The ID of the collection to transfer ownership. + /// - `owner`: The new account that will become the owner of the collection. + /// + /// This function transfers the ownership of a collection to the specified account. + /// It performs checks to ensure that the `origin` is the current owner and that the + /// new owner is an acceptable account based on the collection's acceptance settings. + pub(crate) fn do_transfer_ownership( + origin: T::AccountId, + collection: T::CollectionId, + new_owner: T::AccountId, + ) -> DispatchResult { + // Check if the new owner is acceptable based on the collection's acceptance settings. + let acceptable_collection = OwnershipAcceptance::::get(&new_owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + // Try to retrieve and mutate the collection details. + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + // Check if the `origin` is the current owner of the collection. + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == new_owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &new_owner, + details.owner_deposit, + Reserved, + )?; + + // Update account ownership information. + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + + details.owner = new_owner.clone(); + OwnershipAcceptance::::remove(&new_owner); + frame_system::Pallet::::dec_consumers(&new_owner); + + // Emit `OwnerChanged` event. + Self::deposit_event(Event::OwnerChanged { collection, new_owner }); + Ok(()) + }) + } + /// Set or unset the ownership acceptance for an account regarding a specific collection. + /// + /// - `who`: The account for which to set or unset the ownership acceptance. + /// - `maybe_collection`: An optional collection ID to set the ownership acceptance. + /// + /// If `maybe_collection` is `Some(collection)`, then the account `who` will accept + /// ownership transfers for the specified collection. If `maybe_collection` is `None`, + /// then the account `who` will unset the ownership acceptance, effectively refusing + /// ownership transfers for any collection. + pub(crate) fn do_set_accept_ownership( + who: T::AccountId, + maybe_collection: Option, + ) -> DispatchResult { + let exists = OwnershipAcceptance::::contains_key(&who); + match (exists, 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); + } + + // Emit `OwnershipAcceptanceChanged` event. + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } + + /// Forcefully change the owner of a collection. + /// + /// - `collection`: The ID of the collection to change ownership. + /// - `owner`: The new account that will become the owner of the collection. + /// + /// This function allows for changing the ownership of a collection without any checks. + /// It moves the deposit to the new owner, updates the collection's owner, and emits + /// an `OwnerChanged` event. + pub(crate) fn do_force_collection_owner( + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + // Try to retrieve and mutate the collection details. + Collection::::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.owner_deposit, + Reserved, + )?; + + // Update collection accounts and set the new owner. + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + + // Emit `OwnerChanged` event. + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } +} diff --git a/pallets/nfts/src/impl_nonfungibles.rs b/pallets/nfts/src/impl_nonfungibles.rs new file mode 100644 index 00000000..c90655aa --- /dev/null +++ b/pallets/nfts/src/impl_nonfungibles.rs @@ -0,0 +1,506 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementations for `nonfungibles` traits. + +use super::*; +use frame_support::{ + ensure, + storage::KeyPrefixIterator, + traits::{tokens::nonfungibles_v2::*, Get}, + BoundedSlice, +}; +use sp_runtime::{DispatchError, DispatchResult}; + +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 namespace = AttributeNamespace::CollectionOwner; + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + } + + /// Returns the custom attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + account: &T::AccountId, + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> Option> { + let namespace = Account::::get((account, collection, item)) + .map(|_| AttributeNamespace::ItemOwner) + .unwrap_or_else(|| AttributeNamespace::Account(account.clone())); + + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + + /// Returns the system attribute value of `item` of `collection` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the system attribute value of `collection` + /// corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute( + collection: &Self::CollectionId, + item: Option<&Self::ItemId>, + key: &[u8], + ) -> Option> { + let namespace = AttributeNamespace::Pallet; + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, item, namespace, key)).map(|a| a.0.into()) + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// When `key` is empty, we return the item metadata value. + /// + /// By default this is `None`; no attributes are defined. + fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option> { + 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, + AttributeNamespace::CollectionOwner, + key, + )) + .map(|a| a.0.into()) + } + } + + /// Returns `true` if the `item` of `collection` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { + use PalletAttributes::TransferDisabled; + match Self::has_system_attribute(&collection, &item, TransferDisabled) { + Ok(transfer_disabled) if transfer_disabled => return false, + _ => (), + } + match ( + CollectionConfigOf::::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> InspectRole<::AccountId> for Pallet { + fn is_issuer(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Issuer) + } + fn is_admin(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Admin) + } + fn is_freezer(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Freezer) + } +} + +impl, I: 'static> Create<::AccountId, CollectionConfigFor> + for Pallet +{ + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + who: &T::AccountId, + admin: &T::AccountId, + config: &CollectionConfigFor, + ) -> Result { + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); + + let collection = NextCollectionId::::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::::UnknownCollection)?; + + Self::do_create_collection( + collection, + who.clone(), + admin.clone(), + *config, + T::CollectionDeposit::get(), + Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + )?; + + Self::set_next_collection_id(collection); + + Ok(collection) + } + + /// Create a collection of nonfungible items with `collection` Id to be owned by `who` and + /// managed by `admin`. Should be only used for applications that do not have an + /// incremental order for the collection IDs and is a replacement for the auto id creation. + /// + /// + /// SAFETY: This function can break the pallet if it is used in combination with the auto + /// increment functionality, as it can claim a value in the ID sequence. + fn create_collection_with_id( + collection: T::CollectionId, + who: &T::AccountId, + admin: &T::AccountId, + config: &CollectionConfigFor, + ) -> Result<(), DispatchError> { + // 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(), + Event::Created { 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, ItemConfig> for Pallet { + fn mint_into( + collection: &Self::CollectionId, + item: &Self::ItemId, + who: &T::AccountId, + item_config: &ItemConfig, + deposit_collection_owner: bool, + ) -> DispatchResult { + Self::do_mint( + *collection, + *item, + match deposit_collection_owner { + true => None, + false => Some(who.clone()), + }, + who.clone(), + *item_config, + |_, _| Ok(()), + ) + } + + fn burn( + collection: &Self::CollectionId, + item: &Self::ItemId, + maybe_check_owner: Option<&T::AccountId>, + ) -> DispatchResult { + Self::do_burn(*collection, *item, |d| { + if let Some(check_owner) = maybe_check_owner { + if &d.owner != check_owner { + return Err(Error::::NoPermission.into()) + } + } + Ok(()) + }) + } + + fn set_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + value: &[u8], + ) -> DispatchResult { + Self::do_force_set_attribute( + None, + *collection, + Some(*item), + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + Self::construct_attribute_value(value.to_vec())?, + ) + } + + fn set_typed_attribute( + 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, + ) + }) + }) + } + + fn set_item_metadata( + who: Option<&T::AccountId>, + collection: &Self::CollectionId, + item: &Self::ItemId, + data: &[u8], + ) -> DispatchResult { + Self::do_set_item_metadata( + who.cloned(), + *collection, + *item, + Self::construct_metadata(data.to_vec())?, + None, + ) + } + + fn set_collection_metadata( + who: Option<&T::AccountId>, + collection: &Self::CollectionId, + data: &[u8], + ) -> DispatchResult { + Self::do_set_collection_metadata( + who.cloned(), + *collection, + Self::construct_metadata(data.to_vec())?, + ) + } + + fn clear_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> DispatchResult { + Self::do_clear_attribute( + None, + *collection, + Some(*item), + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + ) + } + + fn clear_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| { + >::clear_attribute(collection, item, k) + }) + } + + fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult { + Self::do_clear_attribute( + None, + *collection, + None, + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + ) + } + + fn clear_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| { + >::clear_collection_attribute(collection, k) + }) + } + + fn clear_item_metadata( + who: Option<&T::AccountId>, + collection: &Self::CollectionId, + item: &Self::ItemId, + ) -> DispatchResult { + Self::do_clear_item_metadata(who.cloned(), *collection, *item) + } + + fn clear_collection_metadata( + who: Option<&T::AccountId>, + collection: &Self::CollectionId, + ) -> DispatchResult { + Self::do_clear_collection_metadata(who.cloned(), *collection) + } +} + +impl, 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(())) + } + + fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { + let transfer_disabled = + Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?; + // Can't lock the item twice + if transfer_disabled { + return Err(Error::::ItemLocked.into()) + } + + >::set_attribute( + collection, + item, + &PalletAttributes::::TransferDisabled.encode(), + &[], + ) + } + + fn enable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { + >::clear_attribute( + collection, + item, + &PalletAttributes::::TransferDisabled.encode(), + ) + } +} + +impl, I: 'static> Trading> for Pallet { + fn buy_item( + collection: &Self::CollectionId, + item: &Self::ItemId, + buyer: &T::AccountId, + bid_price: &ItemPrice, + ) -> DispatchResult { + Self::do_buy_item(*collection, *item, buyer.clone(), *bid_price) + } + + fn set_price( + collection: &Self::CollectionId, + item: &Self::ItemId, + sender: &T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + Self::do_set_price(*collection, *item, sender.clone(), price, whitelisted_buyer) + } + + fn item_price(collection: &Self::CollectionId, item: &Self::ItemId) -> Option> { + ItemPriceOf::::get(collection, item).map(|a| a.0) + } +} + +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() -> 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) -> 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) -> Self::OwnedIterator { + 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, + ) -> Self::OwnedInCollectionIterator { + Account::::iter_key_prefix((who, collection)) + } +} diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs new file mode 100644 index 00000000..4e5493a3 --- /dev/null +++ b/pallets/nfts/src/lib.rs @@ -0,0 +1,1933 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Nfts 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; +pub mod migration; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; + +mod common_functions; +/// A library providing the feature set of this pallet. It contains modules with helper methods that +/// perform storage updates and checks required by this pallet's dispatchables. To use pallet level +/// features, make sure to set appropriate bitflags for [`Config::Features`] in your runtime +/// configuration trait. +mod features; +mod impl_nonfungibles; +mod types; + +pub mod macros; +pub mod weights; + +extern crate alloc; + +use alloc::{boxed::Box, vec, vec::Vec}; +use codec::{Decode, Encode}; +use frame_support::traits::{ + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, Incrementable, + ReservableCurrency, +}; +use frame_system::Config as SystemConfig; +use sp_runtime::{ + traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, + RuntimeDebug, +}; + +pub use pallet::*; +pub use types::*; +pub use weights::WeightInfo; + +/// The log target of this pallet. +pub const LOG_TARGET: &'static str = "runtime::nfts"; + +/// A type alias for the account ID type used in the dispatchable functions of this pallet. +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; + use frame_system::pallet_prelude::*; + + /// The in-code storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn collection(i: u16) -> CollectionId; + fn item(i: u16) -> ItemId; + fn signer() -> (Public, AccountId); + fn sign(signer: &Public, message: &[u8]) -> Signature; + } + #[cfg(feature = "runtime-benchmarks")] + impl + BenchmarkHelper< + CollectionId, + ItemId, + sp_runtime::MultiSigner, + sp_runtime::AccountId32, + sp_runtime::MultiSignature, + > for () + where + CollectionId: From, + ItemId: From, + { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> ItemId { + i.into() + } + fn signer() -> (sp_runtime::MultiSigner, sp_runtime::AccountId32) { + let public = sp_io::crypto::sr25519_generate(0.into(), None); + let account = sp_runtime::MultiSigner::Sr25519(public).into_account(); + (public.into(), account) + } + fn sign(signer: &sp_runtime::MultiSigner, message: &[u8]) -> sp_runtime::MultiSignature { + sp_runtime::MultiSignature::Sr25519( + sp_io::crypto::sr25519_sign(0.into(), &signer.clone().try_into().unwrap(), message) + .unwrap(), + ) + } + } + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Identifier for the collection of item. + /// + /// SAFETY: The functions in the `Incrementable` trait are fallible. If the functions + /// of the implementation both return `None`, the automatic CollectionId generation + /// should not be used. So the `create` and `force_create` extrinsics and the + /// `create_collection` function will return an `UnknownCollection` Error. Instead use + /// the `create_collection_with_id` function. However, if the `Incrementable` trait + /// implementation has an incremental order, the `create_collection_with_id` function + /// should not be used as it can claim a value in the ID sequence. + type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; + + /// 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< + Self::RuntimeOrigin, + Self::CollectionId, + Success = Self::AccountId, + >; + + /// 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; + + /// The maximum approvals an item could have. + #[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; + + /// The max duration in blocks for deadlines. + #[pallet::constant] + type MaxDeadlineDuration: Get>; + + /// The max number of attributes a user could set per call. + #[pallet::constant] + type MaxAttributesPerCall: Get; + + /// Disables some of pallet's features. + #[pallet::constant] + type Features: Get; + + /// Off-Chain signature type. + /// + /// Can verify whether an `Self::OffchainPublic` created a signature. + type OffchainSignature: Verify + Parameter; + + /// Off-Chain public key. + /// + /// Must identify as an on-chain `Self::AccountId`. + type OffchainPublic: IdentifyAccount; + + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type Helper: BenchmarkHelper< + Self::CollectionId, + Self::ItemId, + Self::OffchainPublic, + Self::AccountId, + Self::OffchainSignature, + >; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Details of a collection. + #[pallet::storage] + pub type Collection, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionDetails>, + >; + + /// The collection, if any, of which an account is willing to take ownership. + #[pallet::storage] + pub type OwnershipAcceptance, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; + + /// 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 type Account, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, // owner + NMapKey, + NMapKey, + ), + (), + OptionQuery, + >; + + /// The collections owned by any given account; set out this way so that collections owned by + /// a single account can be enumerated. + #[pallet::storage] + pub type CollectionAccount, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::CollectionId, + (), + OptionQuery, + >; + + /// The items in existence and their ownership details. + #[pallet::storage] + /// Stores collection roles as per account. + pub 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] + pub type Item, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemDetails, ApprovalsOf>, + OptionQuery, + >; + + /// Metadata of a collection. + #[pallet::storage] + pub type CollectionMetadataOf, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionMetadata, T::StringLimit>, + OptionQuery, + >; + + /// Metadata of an item. + #[pallet::storage] + pub type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemMetadata, T::StringLimit>, + OptionQuery, + >; + + /// Attributes of a collection. + #[pallet::storage] + pub type Attribute, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, + NMapKey>, + NMapKey>, + NMapKey>, + ), + (BoundedVec, AttributeDepositOf), + OptionQuery, + >; + + /// A price of an item. + #[pallet::storage] + pub type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + (ItemPrice, Option), + OptionQuery, + >; + + /// Item attribute approvals. + #[pallet::storage] + pub 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 whenever a new collection is created. + #[pallet::storage] + pub type NextCollectionId, I: 'static = ()> = + StorageValue<_, T::CollectionId, OptionQuery>; + + /// Handles all the pending swaps. + #[pallet::storage] + pub type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + PendingSwap< + T::CollectionId, + T::ItemId, + PriceWithDirection>, + BlockNumberFor, + >, + OptionQuery, + >; + + /// Config of a collection. + #[pallet::storage] + pub type CollectionConfigOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + + /// Config of an item. + #[pallet::storage] + pub 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 = ()> { + /// 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 }, + /// 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. + TeamChanged { + collection: T::CollectionId, + issuer: Option, + admin: Option, + freezer: Option, + }, + /// An `item` of a `collection` has been approved by the `owner` for transfer by + /// a `delegate`. + TransferApproved { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + deadline: Option>, + }, + /// 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, + }, + /// All approvals of an item got cancelled. + AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// 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`. + CollectionMetadataCleared { collection: T::CollectionId }, + /// New metadata has been set for an item. + ItemMetadataSet { + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + }, + /// Metadata has been cleared for an item. + 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`. + AttributeSet { + collection: T::CollectionId, + 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 }, + /// 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 emitted when the `NextCollectionId` gets incremented. + NextCollectionIdIncremented { next_id: Option }, + /// The price was set for the item. + ItemPriceSet { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + whitelisted_buyer: Option, + }, + /// The price for the item 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, + }, + /// A tip was sent. + TipSent { + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + 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: BlockNumberFor, + }, + /// The swap was cancelled. + SwapCancelled { + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + desired_item: Option, + price: Option>>, + deadline: BlockNumberFor, + }, + /// 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: BlockNumberFor, + }, + /// New attributes have been set for an `item` of the `collection`. + PreSignedAttributesSet { + collection: T::CollectionId, + item: T::ItemId, + namespace: AttributeNamespace, + }, + /// A new attribute in the `Pallet` namespace was set for the `collection` or an `item` + /// within that `collection`. + PalletAttributeSet { + collection: T::CollectionId, + item: Option, + attribute: PalletAttributes, + value: BoundedVec, + }, + } + + #[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 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, + /// The witness data given does not match the current state of the chain. + BadWitness, + /// Collection ID is already taken. + CollectionIdInUse, + /// Items within that collection are non-transferable. + ItemsNonTransferable, + /// 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 acceptance of the collection. + Unaccepted, + /// 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 is locked and can't be changed. + MaxSupplyLocked, + /// The provided max supply is less than the number of items a collection already has. + MaxSupplyTooSmall, + /// The given item ID is unknown. + UnknownItem, + /// Swap doesn't exist. + UnknownSwap, + /// The given item has no metadata set. + MetadataNotFound, + /// The provided attribute can't be found. + AttributeNotFound, + /// 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 than or equal to `MaxDeadlineDuration`. + WrongDuration, + /// The method is disabled by system settings. + MethodDisabled, + /// The provided 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, + /// Some roles were not cleared. + RolesNotCleared, + /// Mint has not started yet. + MintNotStarted, + /// Mint has already ended. + MintEnded, + /// The provided Item was already used for claiming. + AlreadyClaimed, + /// The provided data is incorrect. + IncorrectData, + /// The extrinsic was sent by the wrong origin. + WrongOrigin, + /// The provided signature is incorrect. + WrongSignature, + /// The provided metadata might be too long. + IncorrectMetadata, + /// Can't set more attributes per one call. + MaxAttributesLimitReached, + /// The provided namespace isn't supported in this call. + WrongNamespace, + /// Can't delete non-empty collections. + CollectionNotEmpty, + /// The witness data should be provided. + WitnessRequired, + } + + #[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. + /// + /// `CollectionDeposit` funds of sender are reserved. + /// + /// Parameters: + /// - `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::call_index(0)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create( + origin: OriginFor, + admin: AccountIdLookupOf, + config: CollectionConfigFor, + ) -> DispatchResult { + let collection = NextCollectionId::::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::::UnknownCollection)?; + + 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(), + Event::Created { collection, creator: owner, owner: admin }, + )?; + + Self::set_next_collection_id(collection); + Ok(()) + } + + /// 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. + /// + /// - `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::call_index(1)] + #[pallet::weight(T::WeightInfo::force_create())] + pub fn force_create( + origin: OriginFor, + owner: AccountIdLookupOf, + config: CollectionConfigFor, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + + let collection = NextCollectionId::::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::::UnknownCollection)?; + + Self::do_create_collection( + collection, + owner.clone(), + owner.clone(), + config, + Zero::zero(), + Event::ForceCreated { collection, owner }, + )?; + + Self::set_next_collection_id(collection); + Ok(()) + } + + /// 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`. + /// + /// NOTE: The collection must have 0 items to be destroyed. + /// + /// - `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(m + c + a)` where: + /// - `m = witness.item_metadatas` + /// - `c = witness.item_configs` + /// - `a = witness.attributes` + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::destroy( + witness.item_metadatas, + witness.item_configs, + witness.attributes, + ))] + pub fn destroy( + origin: OriginFor, + collection: T::CollectionId, + witness: DestroyWitness, + ) -> DispatchResultWithPostInfo { + 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( + details.item_metadatas, + details.item_configs, + details.attributes, + )) + .into()) + } + + /// Mint an item of a particular collection. + /// + /// The origin must be Signed and the sender must comply with the `mint_settings` rules. + /// + /// - `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. If + /// the mint price is set, then it should be additionally confirmed in the `witness_data`. + /// + /// Note: the deposit will be taken from the `origin` and not the `owner` of the `item`. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::mint())] + pub fn mint( + 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 item_config = + ItemConfig { settings: Self::get_default_item_settings(&collection)? }; + + Self::do_mint( + collection, + item, + Some(caller.clone()), + mint_to.clone(), + item_config, + |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::::MintNotStarted); + } + 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 MintWitness { owned_item, .. } = + witness_data.clone().ok_or(Error::::WitnessRequired)?; + let owned_item = owned_item.ok_or(Error::::BadWitness)?; + + let owns_item = Account::::contains_key(( + &caller, + &collection_id, + &owned_item, + )); + ensure!(owns_item, Error::::BadWitness); + + let pallet_attribute = + PalletAttributes::::UsedToClaim(collection); + + let key = ( + &collection_id, + Some(owned_item), + AttributeNamespace::Pallet, + &Self::construct_attribute_key(pallet_attribute.encode())?, + ); + let already_claimed = Attribute::::contains_key(key.clone()); + ensure!(!already_claimed, Error::::AlreadyClaimed); + + let attribute_value = Self::construct_attribute_value(vec![])?; + Attribute::::insert( + key, + ( + attribute_value.clone(), + AttributeDeposit { account: None, amount: Zero::zero() }, + ), + ); + Self::deposit_event(Event::PalletAttributeSet { + collection: collection_id, + item: Some(owned_item), + attribute: pallet_attribute, + value: attribute_value, + }); + }, + _ => {}, + } + + if let Some(price) = mint_settings.price { + let MintWitness { mint_price, .. } = + witness_data.clone().ok_or(Error::::WitnessRequired)?; + let mint_price = mint_price.ok_or(Error::::BadWitness)?; + ensure!(mint_price >= price, Error::::BadWitness); + 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. + /// - `mint_to`: Account into which the item will be minted. + /// - `item_config`: A config of the new item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::force_mint())] + pub fn force_mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + 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 mint_to = T::Lookup::lookup(mint_to)?; + + if let Some(check_origin) = maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Issuer), + Error::::NoPermission + ); + } + Self::do_mint(collection, item, None, mint_to, item_config, |_, _| Ok(())) + } + + /// Destroy a single item. + /// + /// The origin must conform to `ForceOrigin` or must be Signed and the signing account must + /// be the owner of the `item`. + /// + /// - `collection`: The collection of the item to be burned. + /// - `item`: The item to be burned. + /// + /// Emits `Burned`. + /// + /// Weight: `O(1)` + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::burn())] + pub fn burn( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + + Self::do_burn(collection, item, |details| { + if let Some(check_origin) = maybe_check_origin { + ensure!(details.owner == check_origin, Error::::NoPermission); + } + Ok(()) + }) + } + + /// Move an item from the sender account to another. + /// + /// Origin must be Signed and the signing account must be either: + /// - 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 to be transferred. + /// - `dest`: The account to receive ownership of the item. + /// + /// Emits `Transferred`. + /// + /// Weight: `O(1)` + #[pallet::call_index(6)] + #[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, |_, details| { + if details.owner != origin { + 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(()) + }) + } + + /// Re-evaluate the deposits on some items. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `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 + /// 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 or the signer + /// 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, + collection: T::CollectionId, + items: Vec, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.owner == origin, Error::::NoPermission); + + 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()); + for item in items.into_iter() { + let mut details = match Item::::get(&collection, &item) { + Some(x) => x, + None => continue, + }; + let old = details.deposit.amount; + if old > deposit { + T::Currency::unreserve(&details.deposit.account, old - deposit); + } else if deposit > old { + 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 + } + } else { + continue + } + details.deposit.amount = deposit; + Item::::insert(&collection, &item, &details); + successful.push(item); + } + + 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 changed. + /// - `item`: The item to become non-transferable. + /// + /// Emits `ItemTransferLocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(8)] + #[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)?; + 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 changed. + /// - `item`: The item to become transferable. + /// + /// Emits `ItemTransferUnlocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(9)] + #[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)?; + Self::do_unlock_item_transfer(origin, collection, item) + } + + /// Disallows specified settings for the whole collection. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection 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`. + /// + /// Weight: `O(1)` + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::lock_collection())] + pub fn lock_collection( + origin: OriginFor, + collection: T::CollectionId, + lock_settings: CollectionSettings, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_lock_collection(origin, collection, lock_settings) + } + + /// 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::call_index(11)] + #[pallet::weight(T::WeightInfo::transfer_ownership())] + pub fn transfer_ownership( + origin: OriginFor, + collection: T::CollectionId, + new_owner: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let new_owner = T::Lookup::lookup(new_owner)?; + Self::do_transfer_ownership(origin, collection, new_owner) + } + + /// Change the Issuer, Admin and Freezer of a collection. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// Note: by setting the role to `None` only the `ForceOrigin` will be able to change it + /// after to `Some(account)`. + /// + /// - `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::call_index(12)] + #[pallet::weight(T::WeightInfo::set_team())] + pub fn set_team( + origin: OriginFor, + collection: T::CollectionId, + issuer: Option>, + admin: Option>, + freezer: Option>, + ) -> DispatchResult { + 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 = issuer.map(T::Lookup::lookup).transpose()?; + let admin = admin.map(T::Lookup::lookup).transpose()?; + let freezer = freezer.map(T::Lookup::lookup).transpose()?; + Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) + } + + /// 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::call_index(13)] + #[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) + } + + /// 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::call_index(14)] + #[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 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. + /// - `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 `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub fn approve_transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + maybe_deadline: Option>, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_transfer( + maybe_check_origin, + collection, + item, + delegate, + maybe_deadline, + ) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `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 collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to loose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(16)] + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub fn cancel_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) + } + + /// Cancel all the approvals of a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `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::call_index(17)] + #[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_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) + } + + /// Disallows changing the metadata or attributes of the item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin + /// of the `collection`. + /// + /// - `collection`: The collection if the `item`. + /// - `item`: An item 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. + /// + /// Emits `ItemPropertiesLocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(18)] + #[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_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_lock_item_properties( + maybe_check_origin, + collection, + item, + lock_metadata, + lock_attributes, + ) + } + + /// Set an attribute for a collection or item. + /// + /// Origin must be Signed and must conform to the namespace ruleset: + /// - `CollectionOwner` namespace could be modified by the `collection` Admin 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; + /// + /// 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. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::set_attribute())] + pub fn set_attribute( + origin: OriginFor, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let depositor = match namespace { + AttributeNamespace::CollectionOwner => + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?, + _ => origin.clone(), + }; + Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor) + } + + /// 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::call_index(20)] + #[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. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// attribute. + /// + /// 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. + /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. + /// + /// Emits `AttributeCleared`. + /// + /// Weight: `O(1)` + #[pallet::call_index(21)] + #[pallet::weight(T::WeightInfo::clear_attribute())] + pub fn clear_attribute( + 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, 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::call_index(22)] + #[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::call_index(23)] + #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval( + witness.account_attributes + ))] + 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. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin 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`. + /// + /// Emits `ItemMetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(24)] + #[pallet::weight(T::WeightInfo::set_metadata())] + pub fn set_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None) + } + + /// Clear the metadata for an item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the + /// `collection`. + /// + /// 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 `ItemMetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::call_index(25)] + #[pallet::weight(T::WeightInfo::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_item_metadata(maybe_check_origin, collection, item) + } + + /// Set the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin 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`. + /// + /// Emits `CollectionMetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(26)] + #[pallet::weight(T::WeightInfo::set_collection_metadata())] + pub fn set_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_collection_metadata(maybe_check_origin, collection, data) + } + + /// Clear the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin 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::call_index(27)] + #[pallet::weight(T::WeightInfo::clear_collection_metadata())] + pub fn clear_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_collection_metadata(maybe_check_origin, collection) + } + + /// 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::call_index(28)] + #[pallet::weight(T::WeightInfo::set_accept_ownership())] + pub fn set_accept_ownership( + origin: OriginFor, + maybe_collection: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_accept_ownership(who, maybe_collection) + } + + /// 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`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `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, + 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).map_err(DispatchError::from))?; + Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) + } + + /// Update mint settings. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Issuer + /// of the `collection`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `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, + collection: T::CollectionId, + mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, + ) -> DispatchResult { + let maybe_check_origin = 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_origin, collection, mint_settings) + } + + /// Set (or reset) the price for an item. + /// + /// Origin must be Signed and must be the owner of the `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::call_index(31)] + #[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::call_index(32)] + #[pallet::weight(T::WeightInfo::buy_item())] + 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) + } + + /// Allows to pay the tips. + /// + /// Origin must be Signed. + /// + /// - `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, + tips: BoundedVec, T::MaxTips>, + ) -> DispatchResult { + 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`. + /// - `duration`: A deadline for the swap. Specified by providing the number of blocks + /// 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, + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + maybe_desired_item: Option, + maybe_price: Option>>, + duration: BlockNumberFor, + ) -> 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::call_index(35)] + #[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::call_index(36)] + #[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, + ) + } + + /// Mint an item by providing the pre-signed approval. + /// + /// Origin must be Signed. + /// + /// - `mint_data`: The pre-signed approval that consists of the information about the item, + /// its metadata, attributes, who can mint it (`None` for anyone) and until what block + /// number. + /// - `signature`: The signature of the `data` object. + /// - `signer`: The `data` object's signer. Should be an Issuer of the collection. + /// + /// Emits `Issued` on success. + /// Emits `AttributeSet` if the attributes were provided. + /// Emits `ItemMetadataSet` if the metadata was not empty. + #[pallet::call_index(37)] + #[pallet::weight(T::WeightInfo::mint_pre_signed(mint_data.attributes.len() as u32))] + pub fn mint_pre_signed( + origin: OriginFor, + mint_data: Box>, + signature: T::OffchainSignature, + signer: T::AccountId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::validate_signature(&Encode::encode(&mint_data), &signature, &signer)?; + Self::do_mint_pre_signed(origin, *mint_data, signer) + } + + /// Set attributes for an item by providing the pre-signed approval. + /// + /// Origin must be Signed and must be an owner of the `data.item`. + /// + /// - `data`: The pre-signed approval that consists of the information about the item, + /// attributes to update and until what block number. + /// - `signature`: The signature of the `data` object. + /// - `signer`: The `data` object's signer. Should be an Admin of the collection for the + /// `CollectionOwner` namespace. + /// + /// Emits `AttributeSet` for each provided attribute. + /// Emits `ItemAttributesApprovalAdded` if the approval wasn't set before. + /// Emits `PreSignedAttributesSet` on success. + #[pallet::call_index(38)] + #[pallet::weight(T::WeightInfo::set_attributes_pre_signed(data.attributes.len() as u32))] + pub fn set_attributes_pre_signed( + origin: OriginFor, + data: PreSignedAttributesOf, + signature: T::OffchainSignature, + signer: T::AccountId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; + Self::do_set_attributes_pre_signed(origin, data, signer) + } + } +} + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/pallets/nfts/src/macros.rs b/pallets/nfts/src/macros.rs new file mode 100644 index 00000000..d313c878 --- /dev/null +++ b/pallets/nfts/src/macros.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Implements encoding and decoding traits for a wrapper type that represents +/// bitflags. The wrapper type should contain a field of type `$size`, where +/// `$size` is an integer type (e.g., u8, u16, u32) that can represent the bitflags. +/// The `$bitflag_enum` type is the enumeration type that defines the individual bitflags. +/// +/// This macro provides implementations for the following traits: +/// - `MaxEncodedLen`: Calculates the maximum encoded length for the wrapper type. +/// - `Encode`: Encodes the wrapper type using the provided encoding function. +/// - `EncodeLike`: Trait indicating the type can be encoded as is. +/// - `Decode`: Decodes the wrapper type from the input. +/// - `TypeInfo`: Provides type information for the wrapper type. +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl EncodeLike for $wrapper {} + impl Decode for $wrapper { + fn decode( + input: &mut I, + ) -> ::core::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/pallets/nfts/src/migration.rs b/pallets/nfts/src/migration.rs new file mode 100644 index 00000000..8f82e092 --- /dev/null +++ b/pallets/nfts/src/migration.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::traits::OnRuntimeUpgrade; +use log; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +pub mod v1 { + use frame_support::{pallet_prelude::*, weights::Weight}; + + use super::*; + + #[derive(Decode)] + pub struct OldCollectionDetails { + pub owner: AccountId, + pub owner_deposit: DepositBalance, + pub items: u32, + pub item_metadatas: u32, + pub attributes: u32, + } + + impl OldCollectionDetails { + /// Migrates the old collection details to the new v1 format. + fn migrate_to_v1(self, item_configs: u32) -> CollectionDetails { + CollectionDetails { + owner: self.owner, + owner_deposit: self.owner_deposit, + items: self.items, + item_metadatas: self.item_metadatas, + item_configs, + attributes: self.attributes, + } + } + } + + /// A migration utility to update the storage version from v0 to v1 for the pallet. + pub struct MigrateToV1(core::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let in_code_version = Pallet::::in_code_storage_version(); + let on_chain_version = Pallet::::on_chain_storage_version(); + + log::info!( + target: LOG_TARGET, + "Running migration with in-code storage version {:?} / onchain {:?}", + in_code_version, + on_chain_version + ); + + if on_chain_version == 0 && in_code_version == 1 { + let mut translated = 0u64; + let mut configs_iterated = 0u64; + Collection::::translate::< + OldCollectionDetails>, + _, + >(|key, old_value| { + let item_configs = ItemConfigOf::::iter_prefix(&key).count() as u32; + configs_iterated += item_configs as u64; + translated.saturating_inc(); + Some(old_value.migrate_to_v1(item_configs)) + }); + + in_code_version.put::>(); + + log::info!( + target: LOG_TARGET, + "Upgraded {} records, storage to version {:?}", + translated, + in_code_version + ); + T::DbWeight::get().reads_writes(translated + configs_iterated + 1, translated + 1) + } else { + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let prev_count = Collection::::iter().count(); + Ok((prev_count as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), TryRuntimeError> { + let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( + "the state parameter should be something that was generated by pre_upgrade", + ); + let post_count = Collection::::iter().count() as u32; + ensure!( + prev_count == post_count, + "the records count before and after the migration should be the same" + ); + + ensure!(Pallet::::on_chain_storage_version() >= 1, "wrong storage version"); + + Ok(()) + } + } +} diff --git a/pallets/nfts/src/mock.rs b/pallets/nfts/src/mock.rs new file mode 100644 index 00000000..5b589f59 --- /dev/null +++ b/pallets/nfts/src/mock.rs @@ -0,0 +1,104 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for Nfts pallet. + +use super::*; +use crate as pallet_nfts; + +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, +}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, +}; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Nfts: pallet_nfts, + } +); + +pub type Signature = MultiSignature; +pub type AccountPublic = ::Signer; +pub type AccountId = ::AccountId; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + 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 ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxAttributesPerCall = ConstU32<2>; + type Features = Features; + /// Off-chain = signature On-chain - therefore no conversion needed. + /// It needs to be From for benchmarking. + type OffchainSignature = Signature; + /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. + type OffchainPublic = AccountPublic; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +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.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs new file mode 100644 index 00000000..e1b598ca --- /dev/null +++ b/pallets/nfts/src/tests.rs @@ -0,0 +1,3877 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for Nfts pallet. + +use crate::{mock::*, Event, SystemConfig, *}; +use enumflags2::BitFlags; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + tokens::nonfungibles_v2::{Create, Destroy, Inspect, Mutate}, + Currency, Get, + }, +}; +use pallet_balances::Error as BalancesError; +use sp_core::{bounded::BoundedVec, Pair}; +use sp_runtime::{ + traits::{Dispatchable, IdentifyAccount}, + MultiSignature, MultiSigner, +}; + +type AccountIdOf = ::AccountId; + +fn account(id: u8) -> AccountIdOf { + [id; 32].into() +} + +fn items() -> Vec<(AccountIdOf, 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<(AccountIdOf, 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, AttributeNamespace>, Vec, Vec)> { + let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) + .map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into())) + .collect(); + s.sort_by_key(|k: &(Option, AttributeNamespace>, Vec, Vec)| k.0); + s.sort_by_key(|k: &(Option, AttributeNamespace>, Vec, Vec)| { + k.2.clone() + }); + s +} + +fn approvals(collection_id: u32, item_id: u32) -> Vec<(AccountIdOf, Option)> { + let item = Item::::get(collection_id, item_id).unwrap(); + let s: Vec<_> = item.approvals.into_iter().collect(); + 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() + .map(|r| r.event) + .filter_map(|e| if let mock::RuntimeEvent::Nfts(inner) = e { Some(inner) } else { None }) + .collect::>(); + + System::reset_events(); + + result +} + +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 { settings: ItemSettings::all_enabled() } +} + +fn item_config_from_disabled_settings(settings: BitFlags) -> ItemConfig { + ItemConfig { settings: ItemSettings::from_disabled(settings) } +} + +#[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!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 42)]); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(2), + default_collection_config() + )); + assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); + }); +} + +#[test] +fn lifecycle_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_eq!(Balances::reserved_balance(&account(1)), 2); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0, 0] + )); + assert_eq!(Balances::reserved_balance(&account(1)), 5); + assert!(CollectionMetadataOf::::contains_key(0)); + + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(10), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(&account(1)), 6); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(20), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(&account(1)), 7); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 3); + assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + assert_eq!(Collection::::get(0).unwrap().item_configs, 3); + + assert_eq!(Balances::reserved_balance(&account(1)), 8); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); + assert_eq!(Balances::reserved_balance(&account(1)), 8); + assert_eq!(Balances::reserved_balance(&account(2)), 0); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![42, 42])); + assert_eq!(Balances::reserved_balance(&account(1)), 11); + assert!(ItemMetadataOf::::contains_key(0, 42)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![69, 69])); + assert_eq!(Balances::reserved_balance(&account(1)), 14); + assert!(ItemMetadataOf::::contains_key(0, 69)); + assert!(ItemConfigOf::::contains_key(0, 69)); + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_eq!(w.item_metadatas, 2); + assert_eq!(w.item_configs, 3); + assert_noop!( + Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), + Error::::CollectionNotEmpty + ); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(69), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); + assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); + + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_eq!(w.attributes, 1); + assert_eq!(w.item_metadatas, 0); + assert_eq!(w.item_configs, 0); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(Balances::reserved_balance(&account(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)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert!(!ItemConfigOf::::contains_key(0, 69)); + assert_eq!(attributes(0), vec![]); + 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(&account(1), 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_noop!( + Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + DestroyWitness { item_configs: 1, ..w } + ), + Error::::BadWitness + ); + }); +} + +#[test] +fn destroy_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_noop!( + Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + Nfts::get_destroy_witness(&0).unwrap() + ), + Error::::CollectionNotEmpty + ); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42)); + assert_eq!(Collection::::get(0).unwrap().item_configs, 1); + assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 1); + assert!(ItemConfigOf::::contains_key(0, 42)); + assert_ok!(Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + Nfts::get_destroy_witness(&0).unwrap() + )); + assert!(!ItemConfigOf::::contains_key(0, 42)); + assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); + }); +} + +#[test] +fn mint_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_eq!(items(), vec![(account(1), 0, 42)]); + + // validate minting start and end settings + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(account(1)), + 0, + 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(account(2)), 0, 43, account(1), None), + Error::::MintNotStarted + ); + System::set_block_number(4); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(1), None), + Error::::MintEnded + ); + + // validate price + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(account(1)), + 0, + MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } + )); + Balances::make_free_balance_be(&account(2), 100); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(2), None,), + Error::::WitnessRequired + ); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { ..Default::default() }) + ), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { mint_price: Some(0), ..Default::default() }) + ), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { mint_price: Some(1), ..Default::default() }) + )); + assert_eq!(Balances::total_balance(&account(2)), 99); + + // validate types + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(account(1)), + 1, + MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } + )); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(3)), 1, 42, account(3), None), + Error::::WitnessRequired + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 42, account(2), None), + Error::::WitnessRequired + ); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 1, + 42, + account(2), + Some(MintWitness { owned_item: Some(42), ..Default::default() }) + ), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(2)), + 1, + 42, + account(2), + Some(MintWitness { owned_item: Some(43), ..Default::default() }) + )); + assert!(events().contains(&Event::::PalletAttributeSet { + collection: 0, + item: Some(43), + attribute: PalletAttributes::<::CollectionId>::UsedToClaim(1), + value: Nfts::construct_attribute_value(vec![]).unwrap(), + })); + + // can't mint twice + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 1, + 46, + account(2), + Some(MintWitness { owned_item: Some(43), ..Default::default() }) + ), + Error::::AlreadyClaimed + ); + }); +} + +#[test] +fn transfer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(items(), vec![(account(3), 0, 42)]); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Error::::NoPermission + ); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(3)), + 0, + 42, + account(2), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); + + // validate we can't transfer non-transferable items + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 1, + 1, + account(42), + default_item_config() + )); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(1)), collection_id, 42, account(3)), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn locking_transfer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)), + Error::::ItemLocked + ); + + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(account(1)), + 0, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)), + Error::::ItemsNonTransferable + ); + + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + 0, + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2))); + }); +} + +#[test] +fn origin_guards_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + + Balances::make_free_balance_be(&account(2), 100); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0))); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(2)), + Error::::NoPermission + ); + assert_noop!( + Nfts::set_team( + RuntimeOrigin::signed(account(2)), + 0, + Some(account(2)), + Some(account(2)), + Some(account(2)), + ), + Error::::NoPermission + ); + assert_noop!( + Nfts::lock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 69, account(2), None), + Error::::NoPermission + ); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 43, account(2), None)); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 43), + Error::::NoPermission + ); + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_noop!( + Nfts::destroy(RuntimeOrigin::signed(account(2)), 0, w), + Error::::NoPermission + ); + }); +} + +#[test] +fn transfer_owner_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(3), 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2)), + Error::::Unaccepted + ); + assert_eq!(System::consumers(&account(2)), 0); + + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0))); + assert_eq!(System::consumers(&account(2)), 1); + + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2))); + assert_eq!(System::consumers(&account(2)), 1); // one consumer is added due to deposit repatriation + + assert_eq!(collections(), vec![(account(2), 0)]); + assert_eq!(Balances::total_balance(&account(1)), 98); + assert_eq!(Balances::total_balance(&account(2)), 102); + assert_eq!(Balances::reserved_balance(&account(1)), 0); + assert_eq!(Balances::reserved_balance(&account(2)), 2); + + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(1)), Some(0))); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(1)), + Error::::NoPermission + ); + + // Mint and set metadata now and make sure that deposit gets transferred back. + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 20], + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(Balances::reserved_balance(&account(1)), 1); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20])); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(3)), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(3))); + assert_eq!(collections(), vec![(account(3), 0)]); + assert_eq!(Balances::total_balance(&account(2)), 58); + assert_eq!(Balances::total_balance(&account(3)), 144); + assert_eq!(Balances::reserved_balance(&account(2)), 0); + assert_eq!(Balances::reserved_balance(&account(3)), 44); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2))); + // reserved_balance of accounts 1 & 2 should be unchanged: + assert_eq!(Balances::reserved_balance(&account(1)), 1); + assert_eq!(Balances::reserved_balance(&account(2)), 0); + + // 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(account(3)), 0, account(2)), + Error::::Unaccepted + ); + }); +} + +#[test] +fn set_team_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config(), + )); + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 42, account(2), None)); + + // admin can't transfer/burn items he doesn't own + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Error::::NoPermission + ); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42), + Error::::NoPermission + ); + + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42)); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42)); + + // validate we can set any role to None + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + None, + )); + assert_noop!( + Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42), + Error::::NoPermission + ); + + // set all the roles to None + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(account(1)), 0, None, None, None,)); + + // validate we can't set the roles back + assert_noop!( + Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + None, + ), + Error::::NoPermission + ); + + // only the root account can change the roles from None to Some() + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(3)), + None, + )); + }); +} + +#[test] +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(account(1)), 0, bvec![0u8; 20]), + Error::::NoPermission, + ); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + // Cannot add metadata to unowned item + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(2)), 0, bvec![0u8; 20]), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + Balances::make_free_balance_be(&account(1), 30); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 20] + )); + assert_eq!(Balances::free_balance(&account(1)), 9); + assert!(CollectionMetadataOf::::contains_key(0)); + + // Force origin works, too. + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18])); + + // Update deposit + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 15] + )); + assert_eq!(Balances::free_balance(&account(1)), 14); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 25] + )); + assert_eq!(Balances::free_balance(&account(1)), 4); + + // Cannot over-reserve + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 40]), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 15] + )); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(account(1)), + 0, + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()) + )); + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 15]), + Error::::LockedCollectionMetadata, + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0), + Error::::LockedCollectionMetadata + ); + + // Clear Metadata + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15])); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(2)), 0), + Error::::NoPermission + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 1), + Error::::NoPermission + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0), + Error::::LockedCollectionMetadata + ); + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); + assert!(!CollectionMetadataOf::::contains_key(0)); + }); +} + +#[test] +fn set_item_metadata_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 30); + + // Cannot add metadata to unknown item + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + // Cannot add metadata to unowned item + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(account(2)), 0, 42, bvec![0u8; 20]), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20])); + assert_eq!(Balances::free_balance(&account(1)), 8); + assert!(ItemMetadataOf::::contains_key(0, 42)); + + // Force origin works, too. + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18])); + + // Update deposit + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15])); + assert_eq!(Balances::free_balance(&account(1)), 13); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 25])); + assert_eq!(Balances::free_balance(&account(1)), 3); + + // Cannot over-reserve + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 40]), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15])); + assert_ok!(Nfts::lock_item_properties( + RuntimeOrigin::signed(account(1)), + 0, + 42, + true, + false + )); + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15]), + Error::::LockedItemMetadata, + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 0, 42), + Error::::LockedItemMetadata, + ); + + // Clear Metadata + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(account(2)), 0, 42), + Error::::NoPermission, + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 1, 42), + Error::::NoPermission, + ); + assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + }); +} + +#[test] +fn set_collection_owner_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (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(account(1)), 10); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 9); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0; 10], + )); + assert_eq!( + attributes(0), + vec![ + (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(account(1)), 19); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 18); + + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + )); + assert_eq!( + attributes(0), + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(account(1)), 16); + + assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0)); + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(account(1)), 0); + }); +} + +#[test] +fn set_collection_system_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + + let collection_id = 0; + let attribute_key = [0u8]; + let attribute_value = [0u8]; + + assert_ok!(, ItemConfig>>::set_collection_attribute( + &collection_id, + &attribute_key, + &attribute_value + )); + + assert_eq!(attributes(0), vec![(None, AttributeNamespace::Pallet, bvec![0], bvec![0])]); + + assert_eq!( + >>::system_attribute( + &collection_id, + None, + &attribute_key + ), + Some(attribute_value.to_vec()) + ); + + // test typed system attribute + let typed_attribute_key = [0u8; 32]; + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] + struct TypedAttributeValue(u32); + let typed_attribute_value = TypedAttributeValue(42); + + assert_ok!( + , ItemConfig>>::set_typed_collection_attribute( + &collection_id, + &typed_attribute_key, + &typed_attribute_value + ) + ); + + assert_eq!( + >>::typed_system_attribute( + &collection_id, + None, + &typed_attribute_key + ), + Some(typed_attribute_value) + ); + + // check storage + assert_eq!( + attributes(collection_id), + [ + (None, AttributeNamespace::Pallet, bvec![0], bvec![0]), + ( + None, + AttributeNamespace::Pallet, + bvec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + bvec![42, 0, 0, 0] + ) + ] + ); + + assert_ok!(Nfts::burn(RuntimeOrigin::root(), collection_id, 0)); + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), collection_id, w)); + assert_eq!(attributes(collection_id), vec![]); + }) +} + +#[test] +fn set_item_owner_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(3), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + )); + + // can't set for the collection + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(account(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(account(1)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(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(account(2)), 9); + + // validate an attribute can be updated + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(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(account(2)), 18); + + // validate only item's owner (or the root) can remove an attribute + assert_noop!( + Nfts::clear_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(account(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(account(2)), 15); + + // transfer item + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 0, account(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(account(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(account(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(account(3))); + assert_eq!(deposit.amount, 13); + assert_eq!(Balances::reserved_balance(account(2)), 3); + assert_eq!(Balances::reserved_balance(account(3)), 13); + + // validate attributes on item deletion + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0)); + 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(account(3)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + )); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + )); + assert_eq!(Balances::reserved_balance(account(2)), 0); + assert_eq!(Balances::reserved_balance(account(3)), 0); + }); +} + +#[test] +fn set_external_account_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(1), + default_item_config() + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2) + )); + + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::Account(account(1)), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::Account(account(2)), + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::Account(account(2)), + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::Account(account(2)), bvec![0], bvec![0]), + (Some(0), AttributeNamespace::Account(account(2)), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(account(2)), 6); + + // remove permission to set attributes + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + CancelAttributesApprovalWitness { account_attributes: 2 }, + )); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(account(2)), 0); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::Account(account(2)), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + }); +} + +#[test] +fn validate_deposit_required_setting() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(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(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(account(2)), + 0, + 0, + account(3) + )); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(2)), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(3)), + 0, + Some(0), + AttributeNamespace::Account(account(3)), + 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(account(3)), bvec![2], bvec![0]), + (Some(0), AttributeNamespace::Pallet, bvec![3], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(account(1)), 0); + assert_eq!(Balances::reserved_balance(account(2)), 3); + assert_eq!(Balances::reserved_balance(account(3)), 3); + + assert_ok!( + ::AccountId, ItemConfig>>::clear_attribute( + &0, + &0, + &[3], + ) + ); + 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(account(3)), bvec![2], bvec![0]), + ] + ); + }); +} + +#[test] +fn set_attribute_should_respect_lock() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (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(account(1)), 11); + + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![])); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(account(1)), + 0, + CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into()) + )); + + let e = Error::::LockedCollectionAttributes; + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + + assert_ok!(Nfts::lock_item_properties( + RuntimeOrigin::signed(account(1)), + 0, + 0, + false, + true + )); + let e = Error::::LockedItemAttributes; + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + }); +} + +#[test] +fn preserve_config_for_frozen_items() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None)); + + // if the item is not locked/frozen then the config gets deleted on item burn + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1)); + assert!(!ItemConfigOf::::contains_key(0, 1)); + + // lock the item and ensure the config stays unchanged + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(account(1)), 0, 0, true, true)); + + let expect_config = item_config_from_disabled_settings( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata, + ); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0)); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + // can't mint with the different config + assert_noop!( + Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + ), + Error::::InconsistentItemConfig + ); + + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(account(1)), + 0, + MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata + ), + ..Default::default() + } + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + }); +} + +#[test] +fn force_update_collection_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(2), + default_item_config(), + )); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0; 20] + )); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(1)), 65); + + // force item status to be free holding + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + 0, + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 142, account(1), None)); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 169, + account(2), + default_item_config(), + )); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 142, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 169, bvec![0; 20])); + + Balances::make_free_balance_be(&account(5), 100); + assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, account(5))); + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(5)), + Some(account(4)), + )); + assert_eq!(collections(), vec![(account(5), 0)]); + assert_eq!(Balances::reserved_balance(account(1)), 2); + assert_eq!(Balances::reserved_balance(account(5)), 63); + + assert_ok!(Nfts::redeposit( + RuntimeOrigin::signed(account(5)), + 0, + bvec![0, 42, 50, 69, 100] + )); + assert_eq!(Balances::reserved_balance(account(1)), 0); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 42, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(5)), 42); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(5)), 21); + + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(5)), + 0, + bvec![0; 20] + )); + assert_eq!(Balances::reserved_balance(account(5)), 0); + + // validate new roles + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); + assert_eq!( + CollectionRoleOf::::get(0, account(2)).unwrap(), + CollectionRoles(CollectionRole::Issuer.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, account(3)).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, account(4)).unwrap(), + CollectionRoles(CollectionRole::Freezer.into()) + ); + + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(3)), + Some(account(2)), + Some(account(3)), + )); + + assert_eq!( + CollectionRoleOf::::get(0, account(2)).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, account(3)).unwrap(), + CollectionRoles(CollectionRole::Issuer | CollectionRole::Freezer) + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); + + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42), + Error::::UnknownItem + ); + + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(5), + default_item_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(2)), + 0, + 69, + account(5), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(account(1)), 2); + + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), + Error::::NoPermission + ); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); + assert_eq!(Balances::reserved_balance(account(1)), 0); + }); +} + +#[test] +fn approval_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Error::::NoPermission + ); + assert!(Item::::get(0, 42).unwrap().approvals.is_empty()); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(4)), + 0, + 42, + account(2), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(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(), + account(1), + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(1)), + 1, + collection_id, + account(1), + None, + )); + + assert_noop!( + Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + collection_id, + 1, + account(2), + None + ), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Error::::NoPermission + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)), + Error::::NotDelegate + ); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(2), + default_item_config() + )); + // approval expires after 2 blocks. + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + Some(2) + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(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(RuntimeOrigin::signed(account(5)), 0, 42, account(3))); + assert_eq!(approvals(0, 69), vec![]); + }); +} + +#[test] +fn approving_multiple_accounts_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(4), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(5), + Some(2) + )); + assert_eq!( + approvals(0, 42), + vec![(account(3), None), (account(4), None), (account(5), Some(current_block + 2))] + ); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(6))); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(7)), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(5)), 0, 42, account(8)), + Error::::NoPermission + ); + }); +} + +#[test] +fn approvals_limit_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + for i in 3..13 { + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(i), + None + )); + } + // the limit is 10 + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(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( + RuntimeOrigin::root(), + account(1), + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + // the approval expires after the 2nd block. + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + Some(2) + )); + + System::set_block_number(3); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), + Error::::ApprovalExpired + ); + System::set_block_number(1); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(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( + RuntimeOrigin::signed(account(4)), + 0, + 42, + account(6), + Some(4) + )); + // this should still work. + System::set_block_number(5); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(6)), 0, 42, account(5))); + }); +} + +#[test] +fn cancel_approval_works_with_admin() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), + Error::::NotDelegate + ); + }); +} + +#[test] +fn cancel_approval_works_with_force() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3))); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)), + Error::::NotDelegate + ); + }); +} + +#[test] +fn clear_all_transfer_approvals_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(4), + None + )); + + assert_noop!( + Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(3)), 0, 42), + Error::::NoPermission + ); + + assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(2)), 0, 42)); + + assert!(events().contains(&Event::::AllApprovalsCancelled { + collection: 0, + item: 42, + owner: account(2), + })); + assert_eq!(approvals(0, 42), vec![]); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(5)), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(5)), + Error::::NoPermission + ); + }); +} + +#[test] +fn max_supply_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let max_supply = 1; + + // validate set_collection_max_supply + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); + assert!(!events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + + assert_ok!(Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 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_ok!(Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + max_supply + 1 + )); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into()) + )); + assert_noop!( + Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + max_supply + 2 + ), + Error::::MaxSupplyLocked + ); + + // validate we can't mint more to max supply + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 0, + user_id.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 1, + user_id.clone(), + None + )); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 2, + user_id.clone(), + None + ), + Error::::MaxSupplyReached + ); + + // validate the event gets emitted when we set the max supply on collection create + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + CollectionConfig { max_supply: Some(max_supply), ..default_collection_config() } + )); + assert_eq!( + CollectionConfigOf::::get(collection_id).unwrap().max_supply, + Some(max_supply) + ); + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + }); +} + +#[test] +fn mint_settings_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let item_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + user_id.clone(), + None, + )); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::all_enabled().get_disabled() + ); + + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + CollectionConfig { + mint_settings: MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::Transferable | ItemSetting::UnlockedMetadata + ), + ..Default::default() + }, + ..default_collection_config() + } + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + user_id.clone(), + None, + )); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::from_disabled(ItemSetting::Transferable | ItemSetting::UnlockedMetadata) + .get_disabled() + ); + }); +} + +#[test] +fn set_price_should_work() { + new_test_ext().execute_with(|| { + let user_id = account(1); + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + user_id.clone(), + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_2, + user_id.clone(), + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + Some(1), + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_2, + Some(2), + Some(account(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(account(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!(Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + 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)); + + // 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.clone(), + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + user_id.clone(), + None, + )); + + assert_noop!( + Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + Some(2), + None + ), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn buy_item_should_work() { + new_test_ext().execute_with(|| { + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(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!(Nfts::force_create( + RuntimeOrigin::root(), + user_1.clone(), + default_collection_config() + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_1, + user_1.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_2, + user_1.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_3, + user_1.clone(), + None + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_1, + Some(price_1), + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_2, + Some(price_2), + Some(user_3.clone()), + )); + + // can't buy for less + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_1, 1), + Error::::BidTooLow + ); + + // pass the higher price to validate it will still deduct correctly + assert_ok!(Nfts::buy_item( + RuntimeOrigin::signed(user_2.clone()), + 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.clone()); + assert_eq!(Balances::total_balance(&user_1.clone()), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2.clone()), initial_balance - price_1); + + // can't buy from yourself + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_1.clone()), 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(RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can buy when I'm a whitelisted buyer + assert_ok!(Nfts::buy_item( + RuntimeOrigin::signed(user_3.clone()), + collection_id, + item_2, + price_2 + )); + + assert!(events().contains(&Event::::ItemBought { + collection: collection_id, + item: item_2, + price: price_2, + seller: user_1.clone(), + buyer: user_3.clone(), + })); + + // 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!( + Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_3, price_2), + Error::::NotForSale + ); + + // ensure we can't buy an item when the collection or an item are frozen + { + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_3, + Some(price_1), + None, + )); + + // lock the collection + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + + 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(RuntimeOrigin::signed(user_2.clone())), + Error::::ItemsNonTransferable + ); + + // unlock the collection + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + collection_id, + collection_config_with_all_settings_enabled(), + )); + + // lock the transfer + assert_ok!(Nfts::lock_item_transfer( + RuntimeOrigin::signed(user_1.clone()), + 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(RuntimeOrigin::signed(user_2)), + Error::::ItemLocked + ); + } + }); +} + +#[test] +fn pay_tips_should_work() { + new_test_ext().execute_with(|| { + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(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.clone()), + bvec![ + ItemTip { + collection: collection_id, + item: item_id, + receiver: user_2.clone(), + amount: tip + }, + ItemTip { + collection: collection_id, + item: item_id, + receiver: user_3.clone(), + 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.clone(), + receiver: user_2.clone(), + amount: tip, + })); + assert!(events.contains(&Event::::TipSent { + collection: collection_id, + item: item_id, + sender: user_1.clone(), + receiver: user_3.clone(), + amount: tip, + })); + }); +} + +#[test] +fn create_cancel_swap_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user_id = account(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.clone(), + default_collection_config() + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + user_id.clone(), + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_2, + user_id.clone(), + None, + )); + + // validate desired item and the collection exists + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + assert_noop!( + Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), collection_id, item_1), + Error::::NoPermission + ); + System::set_block_number(expect_deadline + 1); + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), 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 = account(1); + let user_2 = account(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.clone(), + default_collection_config() + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_1, + user_1.clone(), + None, + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_2, + user_2.clone(), + default_item_config(), + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_3, + user_2.clone(), + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_4, + user_1.clone(), + None, + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_5, + user_2.clone(), + default_item_config(), + )); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_1.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()), + 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.clone()); + let item = Item::::get(collection_id, item_2).unwrap(); + assert_eq!(item.owner, user_1.clone()); + + // 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.clone(), + received_collection: collection_id, + received_item: item_1, + received_item_owner: user_1.clone(), + 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.clone()), + collection_id, + item_4, + collection_id, + None, + Some(price_with_direction.clone()), + duration, + )); + assert_ok!(Nfts::claim_swap( + RuntimeOrigin::signed(user_2.clone()), + 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); + }); +} + +#[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 = + collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into()); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(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 = collection_config_from_disabled_settings( + CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, + ); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(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(), + account(1), + default_collection_config() + )); + }); +} + +#[test] +fn collection_locking_should_work() { + new_test_ext().execute_with(|| { + let user_id = account(1); + let collection_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + 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.clone()), + collection_id, + lock_config.settings, + ), + Error::::WrongSetting + ); + + // validate partial lock + let lock_config = collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + lock_config.settings, + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, lock_config); + + // validate full lock + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + 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); + }); +} + +#[test] +fn pallet_level_feature_flags_should_work() { + new_test_ext().execute_with(|| { + Features::set(&PalletFeatures::from_disabled( + PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, + )); + + let user_id = account(1); + let collection_id = 0; + let item_id = 1; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + user_id.clone(), + None, + )); + + // PalletFeature::Trading + assert_noop!( + Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + Some(1), + None + ), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, 1), + Error::::MethodDisabled + ); + + // PalletFeature::Approvals + assert_noop!( + Nfts::approve_transfer( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + account(2), + None + ), + Error::::MethodDisabled + ); + + // PalletFeature::Attributes + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(user_id), + collection_id, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + ), + Error::::MethodDisabled + ); + }) +} + +#[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![ + (account(3), CollectionRole::Freezer), + (account(1), CollectionRole::Issuer), + (account(2), CollectionRole::Admin), + ]); + let expect = vec![ + (account(1), CollectionRoles(CollectionRole::Issuer.into())), + (account(2), CollectionRoles(CollectionRole::Admin.into())), + (account(3), CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + + let account_to_role = Nfts::group_roles_by_account(vec![ + (account(3), CollectionRole::Freezer), + (account(2), CollectionRole::Issuer), + (account(2), CollectionRole::Admin), + ]); + let expect = vec![ + (account(2), CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)), + (account(3), CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + }) +} + +#[test] +fn add_remove_item_attributes_approval_should_work() { + new_test_ext().execute_with(|| { + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(3); + let user_4 = account(4); + let collection_id = 0; + let item_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_1.clone(), + default_collection_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_1.clone(), + None + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_2.clone(), + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2.clone()]); + + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_3.clone(), + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_2.clone(), + )); + assert_eq!( + item_attributes_approvals(collection_id, item_id), + vec![user_2.clone(), user_3.clone()] + ); + + assert_noop!( + Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1.clone()), + 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]); + }) +} + +#[test] +fn validate_signature() { + new_test_ext().execute_with(|| { + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let mint_data: PreSignedMint = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: None, + deadline: 100000, + mint_price: None, + }; + let encoded_data = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&encoded_data)); + assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1)); + + let mut wrapped_data: Vec = Vec::new(); + wrapped_data.extend(b""); + wrapped_data.extend(&encoded_data); + wrapped_data.extend(b""); + + let signature = MultiSignature::Sr25519(user_1_pair.sign(&wrapped_data)); + assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1)); + }) +} + +#[test] +fn pre_signed_mints_should_work() { + new_test_ext().execute_with(|| { + let user_0 = account(0); + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + metadata: vec![0, 1], + only_account: None, + deadline: 10000000, + mint_price: Some(10), + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + let user_2 = account(2); + let user_3 = account(3); + + Balances::make_free_balance_be(&user_0, 100); + Balances::make_free_balance_be(&user_2, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(user_0.clone()), + user_1.clone(), + collection_config_with_all_settings_enabled(), + )); + + assert_ok!(Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + )); + assert_eq!(items(), vec![(user_2.clone(), 0, 0)]); + let metadata = ItemMetadataOf::::get(0, 0).unwrap(); + assert_eq!( + metadata.deposit, + ItemMetadataDeposit { account: Some(user_2.clone()), amount: 3 } + ); + assert_eq!(metadata.data, vec![0, 1]); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_0), 100 - 2 + 10); // 2 - collection deposit, 10 - mint price + assert_eq!(Balances::free_balance(&user_2), 100 - 1 - 3 - 6 - 10); // 1 - item deposit, 3 - metadata, 6 - attributes, 10 - mint price + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature.clone(), + user_1.clone(), + ), + Error::::AlreadyExists + ); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0)); + assert_eq!(Balances::free_balance(&user_2), 100 - 6 - 10); + + // validate the `only_account` field + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: Some(account(2)), + deadline: 10000000, + mint_price: None, + }; + + // can't mint with the wrong signature + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + ), + Error::::WrongSignature + ); + + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_3), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + ), + Error::::WrongOrigin + ); + + // validate signature's expiration + System::set_block_number(10000001); + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate the collection + let mint_data = PreSignedMint { + collection: 1, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: Some(account(2)), + deadline: 10000000, + mint_price: None, + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::NoPermission + ); + + // validate max attributes limit + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])], + metadata: vec![0, 1], + only_account: None, + deadline: 10000000, + mint_price: None, + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::MaxAttributesLimitReached + ); + }) +} + +#[test] +fn pre_signed_attributes_should_work() { + new_test_ext().execute_with(|| { + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let user_2 = account(2); + let user_3_pair = sp_core::sr25519::Pair::from_string("//Bob", None).unwrap(); + let user_3_signer = MultiSigner::Sr25519(user_3_pair.public()); + let user_3 = user_3_signer.clone().into_account(); + let collection_id = 0; + let item_id = 0; + + Balances::make_free_balance_be(&user_1, 100); + Balances::make_free_balance_be(&user_2, 100); + Balances::make_free_balance_be(&user_3, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(user_1.clone()), + user_1.clone(), + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_2.clone(), + None, + )); + + // validate the CollectionOwner namespace + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_ok!(Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + )); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_1), 100 - 2 - 1); // 2 - collection deposit, 1 - item deposit + assert_eq!(Balances::free_balance(&user_2), 100 - 6); // 6 - attributes + + // validate the deposit gets returned on attribute update from collection's owner + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + Some(item_id), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, None); + assert_eq!(deposit.amount, 3); + + // validate we don't partially modify the state + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]); + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2; 51], vec![3])], + namespace: AttributeNamespace::Account(user_3.clone()), + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::IncorrectData + ); + + // no new approval was set + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]); + + // no new attributes were added + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + + // validate the Account namespace + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::Account(user_3.clone()), + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_ok!(Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + )); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::Account(user_3.clone()), bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + (Some(0), AttributeNamespace::Account(user_3.clone()), bvec![2], bvec![3]), + ] + ); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3.clone()]); + + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::Account(user_3.clone()), + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_2), 100 - 9); + assert_eq!(Balances::free_balance(&user_3), 100); + + // validate the deposit gets returned on attribute update from user_3 + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(user_3.clone()), + collection_id, + Some(item_id), + AttributeNamespace::Account(user_3.clone()), + bvec![0], + bvec![1], + )); + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::Account(user_3.clone()), + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_3.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_2), 100 - 6); + assert_eq!(Balances::free_balance(&user_3), 100 - 3); + + // can't update with the wrong signature + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::WrongSignature + ); + + // can't update if I don't own that item + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_3.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::NoPermission + ); + + // can't update the CollectionOwner namespace if the signer is not an owner of that + // collection + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::NoPermission + ); + + // validate signature's expiration + System::set_block_number(10000001); + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate item & collection + let pre_signed_data = PreSignedAttributes { + collection: 1, + item: 1, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::UnknownItem + ); + + // validate max attributes limit + let pre_signed_data = PreSignedAttributes { + collection: 1, + item: 1, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::MaxAttributesLimitReached + ); + + // validate the attribute's value length + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3; 51])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::IncorrectData + ); + }) +} + +#[test] +fn basic_create_collection_with_id_should_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Nfts::create_collection_with_id( + 0u32, + &account(1), + &account(1), + &default_collection_config(), + ), + Error::::WrongSetting + ); + + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + + assert_ok!(Nfts::create_collection_with_id( + 0u32, + &account(1), + &account(1), + &collection_config_with_all_settings_enabled(), + )); + + assert_eq!(collections(), vec![(account(1), 0)]); + + // CollectionId already taken. + assert_noop!( + Nfts::create_collection_with_id( + 0u32, + &account(2), + &account(2), + &collection_config_with_all_settings_enabled(), + ), + Error::::CollectionIdInUse + ); + }); +} + +#[test] +fn clear_collection_metadata_works() { + new_test_ext().execute_with(|| { + // Start with an account with 100 tokens, 10 of which are reserved + Balances::make_free_balance_be(&account(1), 100); + Balances::reserve(&account(1), 10).unwrap(); + + // Creating a collection increases owner deposit by 2 + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 2); + assert_eq!(Balances::reserved_balance(&account(1)), 12); + + // Setting collection metadata increases owner deposit by 10 + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0, 0, 0, 0, 0, 0, 0, 0, 0], + )); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 12); + assert_eq!(Balances::reserved_balance(&account(1)), 22); + + // Clearing collection metadata decreases owner deposit by 10 + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0)); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 2); + assert_eq!(Balances::reserved_balance(&account(1)), 12); + + // Destroying the collection removes it from storage + assert_ok!(Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + DestroyWitness { item_configs: 0, item_metadatas: 0, attributes: 0 } + )); + assert_eq!(Collection::::get(0), None); + assert_eq!(Balances::reserved_balance(&account(1)), 10); + }); +} diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs new file mode 100644 index 00000000..1687a035 --- /dev/null +++ b/pallets/nfts/src/types.rs @@ -0,0 +1,548 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains various basic types and data structures used in the NFTs pallet. + +use super::*; +use crate::macros::*; +use alloc::{vec, vec::Vec}; +use codec::EncodeLike; +use enumflags2::{bitflags, BitFlags}; +use frame_support::{ + pallet_prelude::{BoundedVec, MaxEncodedLen}, + traits::Get, + BoundedBTreeMap, BoundedBTreeSet, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; + +/// A type alias for handling balance deposits. +pub(super) type DepositBalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +/// A type alias representing the details of a collection. +pub(super) type CollectionDetailsFor = + CollectionDetails<::AccountId, DepositBalanceOf>; +/// A type alias for keeping track of approvals used by a single item. +pub(super) type ApprovalsOf = BoundedBTreeMap< + ::AccountId, + Option>, + >::ApprovalsLimit, +>; +/// A type alias for keeping track of approvals for an item's attributes. +pub(super) type ItemAttributesApprovals = + BoundedBTreeSet<::AccountId, >::ItemAttributesApprovalsLimit>; +/// A type that holds the deposit for a single item. +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; +/// A type that holds the deposit amount for an item's attribute. +pub(super) type AttributeDepositOf = + AttributeDeposit, ::AccountId>; +/// A type that holds the deposit amount for an item's metadata. +pub(super) type ItemMetadataDepositOf = + ItemMetadataDeposit, ::AccountId>; +/// A type that holds the details of a single item. +pub(super) type ItemDetailsFor = + ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; +/// A type alias for an accounts balance. +pub(super) type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +/// A type alias to represent the price of an item. +pub(super) type ItemPrice = BalanceOf; +/// A type alias for the tips held by a single item. +pub(super) type ItemTipOf = ItemTip< + >::CollectionId, + >::ItemId, + ::AccountId, + BalanceOf, +>; +/// A type alias for the settings configuration of a collection. +pub(super) type CollectionConfigFor = + CollectionConfig, BlockNumberFor, >::CollectionId>; +/// A type alias for the pre-signed minting configuration for a specified collection. +pub(super) type PreSignedMintOf = PreSignedMint< + >::CollectionId, + >::ItemId, + ::AccountId, + BlockNumberFor, + BalanceOf, +>; +/// A type alias for the pre-signed minting configuration on the attribute level of an item. +pub(super) type PreSignedAttributesOf = PreSignedAttributes< + >::CollectionId, + >::ItemId, + ::AccountId, + BlockNumberFor, +>; + +/// Information about a collection. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { + /// Collection's owner. + pub(super) owner: AccountId, + /// 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. + pub(super) items: u32, + /// The total number of outstanding item metadata of this collection. + pub(super) item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub(super) item_configs: u32, + /// The total number of attributes for this collection. + pub(super) attributes: u32, +} + +/// Witness data for the destroy transactions. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct DestroyWitness { + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + #[codec(compact)] + pub item_configs: u32, + /// The total number of attributes for this collection. + #[codec(compact)] + pub attributes: u32, +} + +impl CollectionDetails { + pub fn destroy_witness(&self) -> DestroyWitness { + DestroyWitness { + item_metadatas: self.item_metadatas, + item_configs: self.item_configs, + attributes: self.attributes, + } + } +} + +/// Witness data for items mint transactions. +#[derive(Clone, Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owned_item: Option, + /// The price specified in mint settings. + pub mint_price: Option, +} + +/// 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) 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: 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, +} + +/// Information about the collection's metadata. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(StringLimit))] +#[codec(mel_bound(Deposit: MaxEncodedLen))] +pub struct CollectionMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: Deposit, + /// General information concerning this collection. Limited in length by `StringLimit`. This + /// will generally be either a JSON dump or the hash of some JSON which can be found on a + /// hash-addressable global publication system such as IPFS. + pub(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))] +pub struct ItemMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: Deposit, + /// General information concerning this item. Limited in length by `StringLimit`. This will + /// generally be either a JSON dump or the hash of some JSON which can be found on a + /// hash-addressable global publication system such as IPFS. + pub(super) data: BoundedVec, +} + +/// Information about the tip. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemTip { + /// The collection of the item. + pub(super) collection: CollectionId, + /// An item of which the tip is sent 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, +} + +/// Information about the pending swap. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct PendingSwap { + /// The collection that contains the item that the user wants to receive. + pub(super) desired_collection: CollectionId, + /// 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, + /// A deadline for the swap. + 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, +} + +/// Information about the reserved item's metadata deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemMetadataDeposit { + /// A depositor account, None means the deposit is collection's owner. + pub(super) account: Option, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} + +/// Specifies whether the tokens will be sent or received. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum PriceDirection { + /// Tokens will be sent. + Send, + /// Tokens will be received. + Receive, +} + +/// Holds the details about the price. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct PriceWithDirection { + /// An amount. + pub(super) amount: Amount, + /// 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, + /// The supply of this collection can be modified. + UnlockedMaxSupply, + /// When this isn't set then the deposit is required to hold the items of this collection. + DepositRequired, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct CollectionSettings(pub BitFlags); + +impl CollectionSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + 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), +} + +/// 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. + pub mint_type: MintType, + /// An optional price per mint. + pub price: Option, + /// When the mint starts. + pub start_block: Option, + /// When the mint ends. + pub end_block: Option, + /// Default settings each item will get during the mint. + pub 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(), + } + } +} + +/// 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, + /// An attribute was set by collection's owner. + CollectionOwner, + /// An attribute was set by item's owner. + ItemOwner, + /// An attribute was set by pre-approved account. + Account(AccountId), +} + +/// A witness data to cancel attributes approval operation. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, +} + +/// A list of possible pallet-level attributes. +#[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), + /// Marks an item as being restricted from transferring. + TransferDisabled, +} + +/// Collection's configuration. +#[derive( + Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, +)] +pub struct CollectionConfig { + /// Collection's settings. + pub settings: CollectionSettings, + /// Collection's max supply. + pub max_supply: Option, + /// Default settings each item will get during the mint. + pub mint_settings: MintSettings, +} + +impl CollectionConfig { + pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { + !self.settings.is_disabled(setting) + } + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { + self.settings.is_disabled(setting) + } + pub fn enable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 user-enabled features on an item. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum ItemSetting { + /// This item is transferable. + Transferable, + /// The metadata of this item can be modified. + UnlockedMetadata, + /// Attributes of this item can be modified. + UnlockedAttributes, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct ItemSettings(pub BitFlags); + +impl ItemSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + 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); + +/// Item's configuration. +#[derive( + Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo, +)] +pub struct ItemConfig { + /// Item's settings. + pub settings: ItemSettings, +} + +impl ItemConfig { + pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { + !self.settings.is_disabled(setting) + } + pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { + self.settings.is_disabled(setting) + } + pub fn has_disabled_settings(&self) -> bool { + !self.settings.get_disabled().is_empty() + } + pub fn enable_setting(&mut self, setting: ItemSetting) { + self.settings.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: ItemSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 system-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum PalletFeature { + /// Enable/disable trading operations. + Trading, + /// Allow/disallow setting attributes. + Attributes, + /// Allow/disallow transfer approvals. + Approvals, + /// Allow/disallow atomic items swap. + Swaps, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Default, RuntimeDebug)] +pub struct PalletFeatures(pub BitFlags); + +impl PalletFeatures { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn from_disabled(features: BitFlags) -> Self { + Self(features) + } + pub fn is_enabled(&self, feature: PalletFeature) -> bool { + !self.0.contains(feature) + } +} +impl_codec_bitflags!(PalletFeatures, u64, PalletFeature); + +/// Support for up to 8 different roles for collections. +#[bitflags] +#[repr(u8)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum CollectionRole { + /// Can mint items. + Issuer, + /// Can freeze items. + Freezer, + /// Can thaw items, force transfers and burn items from any account. + Admin, +} + +/// A wrapper type that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct CollectionRoles(pub BitFlags); + +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); + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PreSignedMint { + /// A collection of the item to be minted. + pub(super) collection: CollectionId, + /// Item's ID. + pub(super) item: ItemId, + /// Additional item's key-value attributes. + pub(super) attributes: Vec<(Vec, Vec)>, + /// Additional item's metadata. + pub(super) metadata: Vec, + /// Restrict the claim to a particular account. + pub(super) only_account: Option, + /// A deadline for the signature. + pub(super) deadline: Deadline, + /// An optional price the claimer would need to pay for the mint. + pub(super) mint_price: Option, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PreSignedAttributes { + /// Collection's ID. + pub(super) collection: CollectionId, + /// Item's ID. + pub(super) item: ItemId, + /// Key-value attributes. + pub(super) attributes: Vec<(Vec, Vec)>, + /// Attributes' namespace. + pub(super) namespace: AttributeNamespace, + /// A deadline for the signature. + pub(super) deadline: Deadline, +} diff --git a/pallets/nfts/src/weights.rs b/pallets/nfts/src/weights.rs new file mode 100644 index 00000000..c5fb60a2 --- /dev/null +++ b/pallets/nfts/src/weights.rs @@ -0,0 +1,1468 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_nfts` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/production/substrate-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nfts +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./substrate/frame/nfts/src/weights.rs +// --header=./substrate/HEADER-APACHE2 +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_nfts`. +pub trait WeightInfo { + fn create() -> Weight; + fn force_create() -> Weight; + fn destroy(m: u32, c: u32, a: u32, ) -> Weight; + fn mint() -> Weight; + fn force_mint() -> Weight; + fn burn() -> Weight; + fn transfer() -> Weight; + fn redeposit(i: u32, ) -> 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_collection_owner() -> Weight; + 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(n: u32, ) -> 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 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; + fn create_swap() -> Weight; + fn cancel_swap() -> Weight; + fn claim_swap() -> Weight; + fn mint_pre_signed(n: u32, ) -> Weight; + fn set_attributes_pre_signed(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) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `216` + // Estimated: `3549` + // Minimum execution time: 34_863_000 picoseconds. + Weight::from_parts(36_679_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::NextCollectionId` (r:1 w:1) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3549` + // Minimum execution time: 19_631_000 picoseconds. + Weight::from_parts(20_384_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1001 w:1000) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// The range of component `m` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32204 + a * (366 ±0)` + // Estimated: `2523990 + a * (2954 ±0)` + // Minimum execution time: 1_282_083_000 picoseconds. + Weight::from_parts(1_249_191_963, 2523990) + // Standard Error: 4_719 + .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1004_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1005_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(a.into())) + } + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 49_055_000 picoseconds. + Weight::from_parts(50_592_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn force_mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 47_102_000 picoseconds. + Weight::from_parts(48_772_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `4326` + // Minimum execution time: 52_968_000 picoseconds. + Weight::from_parts(55_136_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `593` + // Estimated: `4326` + // Minimum execution time: 41_140_000 picoseconds. + Weight::from_parts(43_288_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:5000 w:5000) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `763 + i * (108 ±0)` + // Estimated: `3549 + i * (3336 ±0)` + // Minimum execution time: 14_433_000 picoseconds. + Weight::from_parts(14_664_000, 3549) + // Standard Error: 23_078 + .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn lock_item_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 18_307_000 picoseconds. + Weight::from_parts(18_966_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn unlock_item_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 18_078_000 picoseconds. + Weight::from_parts(18_593_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn lock_collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 15_175_000 picoseconds. + Weight::from_parts(15_762_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) + /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:2) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `562` + // Estimated: `3593` + // Minimum execution time: 26_164_000 picoseconds. + Weight::from_parts(27_117_000, 3593) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:2 w:4) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `369` + // Estimated: `6078` + // Minimum execution time: 38_523_000 picoseconds. + Weight::from_parts(39_486_000, 6078) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:2) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn force_collection_owner() -> Weight { + // Proof Size summary in bytes: + // Measured: `311` + // Estimated: `3549` + // Minimum execution time: 15_733_000 picoseconds. + Weight::from_parts(16_227_000, 3549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn force_collection_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3549` + // Minimum execution time: 12_042_000 picoseconds. + Weight::from_parts(12_690_000, 3549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn lock_item_properties() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_769_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn set_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3944` + // Minimum execution time: 48_862_000 picoseconds. + Weight::from_parts(50_584_000, 3944) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn force_set_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `344` + // Estimated: `3944` + // Minimum execution time: 24_665_000 picoseconds. + Weight::from_parts(25_465_000, 3944) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn clear_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `983` + // Estimated: `3944` + // Minimum execution time: 44_617_000 picoseconds. + Weight::from_parts(46_458_000, 3944) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + fn approve_item_attributes() -> Weight { + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `4326` + // Minimum execution time: 15_710_000 picoseconds. + Weight::from_parts(16_191_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1001 w:1000) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `831 + n * (398 ±0)` + // Estimated: `4326 + n * (2954 ±0)` + // Minimum execution time: 24_447_000 picoseconds. + Weight::from_parts(25_144_000, 4326) + // Standard Error: 4_872 + .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + fn set_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3812` + // Minimum execution time: 39_990_000 picoseconds. + Weight::from_parts(41_098_000, 3812) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `849` + // Estimated: `3812` + // Minimum execution time: 38_030_000 picoseconds. + Weight::from_parts(39_842_000, 3812) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + fn set_collection_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3759` + // Minimum execution time: 36_778_000 picoseconds. + Weight::from_parts(38_088_000, 3759) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + fn clear_collection_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `716` + // Estimated: `3759` + // Minimum execution time: 36_887_000 picoseconds. + Weight::from_parts(38_406_000, 3759) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `410` + // Estimated: `4326` + // Minimum execution time: 18_734_000 picoseconds. + Weight::from_parts(19_267_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 16_080_000 picoseconds. + Weight::from_parts(16_603_000, 4326) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn clear_all_transfer_approvals() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 15_013_000 picoseconds. + Weight::from_parts(15_607_000, 4326) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) + /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn set_accept_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3517` + // Minimum execution time: 13_077_000 picoseconds. + Weight::from_parts(13_635_000, 3517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn set_collection_max_supply() -> Weight { + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 17_146_000 picoseconds. + Weight::from_parts(17_453_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn update_mint_settings() -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `3538` + // Minimum execution time: 16_102_000 picoseconds. + Weight::from_parts(16_629_000, 3538) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn set_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `518` + // Estimated: `4326` + // Minimum execution time: 22_118_000 picoseconds. + Weight::from_parts(22_849_000, 4326) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:1 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn buy_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `705` + // Estimated: `4326` + // Minimum execution time: 50_369_000 picoseconds. + Weight::from_parts(51_816_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_203_000 picoseconds. + Weight::from_parts(3_710_869, 0) + // Standard Error: 8_094 + .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + } + /// Storage: `Nfts::Item` (r:2 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn create_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `494` + // Estimated: `7662` + // Minimum execution time: 18_893_000 picoseconds. + Weight::from_parts(19_506_000, 7662) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::PendingSwapOf` (r:1 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn cancel_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `513` + // Estimated: `4326` + // Minimum execution time: 19_086_000 picoseconds. + Weight::from_parts(19_609_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:2 w:2) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:1 w:2) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:2 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:4) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn claim_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `7662` + // Minimum execution time: 84_103_000 picoseconds. + Weight::from_parts(85_325_000, 7662) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:10 w:10) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 10]`. + fn mint_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `629` + // Estimated: `6078 + n * (2954 ±0)` + // Minimum execution time: 128_363_000 picoseconds. + Weight::from_parts(139_474_918, 6078) + // Standard Error: 79_252 + .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:10 w:10) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 10]`. + fn set_attributes_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4326 + n * (2954 ±0)` + // Minimum execution time: 66_688_000 picoseconds. + Weight::from_parts(79_208_379, 4326) + // Standard Error: 74_020 + .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Nfts::NextCollectionId` (r:1 w:1) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `216` + // Estimated: `3549` + // Minimum execution time: 34_863_000 picoseconds. + Weight::from_parts(36_679_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::NextCollectionId` (r:1 w:1) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3549` + // Minimum execution time: 19_631_000 picoseconds. + Weight::from_parts(20_384_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1001 w:1000) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:1) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// The range of component `m` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32204 + a * (366 ±0)` + // Estimated: `2523990 + a * (2954 ±0)` + // Minimum execution time: 1_282_083_000 picoseconds. + Weight::from_parts(1_249_191_963, 2523990) + // Standard Error: 4_719 + .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1004_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(1005_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(a.into())) + } + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 49_055_000 picoseconds. + Weight::from_parts(50_592_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn force_mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 47_102_000 picoseconds. + Weight::from_parts(48_772_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `4326` + // Minimum execution time: 52_968_000 picoseconds. + Weight::from_parts(55_136_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `593` + // Estimated: `4326` + // Minimum execution time: 41_140_000 picoseconds. + Weight::from_parts(43_288_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:5000 w:5000) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `763 + i * (108 ±0)` + // Estimated: `3549 + i * (3336 ±0)` + // Minimum execution time: 14_433_000 picoseconds. + Weight::from_parts(14_664_000, 3549) + // Standard Error: 23_078 + .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn lock_item_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 18_307_000 picoseconds. + Weight::from_parts(18_966_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn unlock_item_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 18_078_000 picoseconds. + Weight::from_parts(18_593_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn lock_collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 15_175_000 picoseconds. + Weight::from_parts(15_762_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) + /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:2) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `562` + // Estimated: `3593` + // Minimum execution time: 26_164_000 picoseconds. + Weight::from_parts(27_117_000, 3593) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:2 w:4) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `369` + // Estimated: `6078` + // Minimum execution time: 38_523_000 picoseconds. + Weight::from_parts(39_486_000, 6078) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionAccount` (r:0 w:2) + /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn force_collection_owner() -> Weight { + // Proof Size summary in bytes: + // Measured: `311` + // Estimated: `3549` + // Minimum execution time: 15_733_000 picoseconds. + Weight::from_parts(16_227_000, 3549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn force_collection_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3549` + // Minimum execution time: 12_042_000 picoseconds. + Weight::from_parts(12_690_000, 3549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn lock_item_properties() -> Weight { + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_769_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn set_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3944` + // Minimum execution time: 48_862_000 picoseconds. + Weight::from_parts(50_584_000, 3944) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn force_set_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `344` + // Estimated: `3944` + // Minimum execution time: 24_665_000 picoseconds. + Weight::from_parts(25_465_000, 3944) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:1) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn clear_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `983` + // Estimated: `3944` + // Minimum execution time: 44_617_000 picoseconds. + Weight::from_parts(46_458_000, 3944) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + fn approve_item_attributes() -> Weight { + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `4326` + // Minimum execution time: 15_710_000 picoseconds. + Weight::from_parts(16_191_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1001 w:1000) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `831 + n * (398 ±0)` + // Estimated: `4326 + n * (2954 ±0)` + // Minimum execution time: 24_447_000 picoseconds. + Weight::from_parts(25_144_000, 4326) + // Standard Error: 4_872 + .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + fn set_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3812` + // Minimum execution time: 39_990_000 picoseconds. + Weight::from_parts(41_098_000, 3812) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `849` + // Estimated: `3812` + // Minimum execution time: 38_030_000 picoseconds. + Weight::from_parts(39_842_000, 3812) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + fn set_collection_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3759` + // Minimum execution time: 36_778_000 picoseconds. + Weight::from_parts(38_088_000, 3759) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) + /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) + fn clear_collection_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `716` + // Estimated: `3759` + // Minimum execution time: 36_887_000 picoseconds. + Weight::from_parts(38_406_000, 3759) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `410` + // Estimated: `4326` + // Minimum execution time: 18_734_000 picoseconds. + Weight::from_parts(19_267_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 16_080_000 picoseconds. + Weight::from_parts(16_603_000, 4326) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn clear_all_transfer_approvals() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 15_013_000 picoseconds. + Weight::from_parts(15_607_000, 4326) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) + /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn set_accept_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3517` + // Minimum execution time: 13_077_000 picoseconds. + Weight::from_parts(13_635_000, 3517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn set_collection_max_supply() -> Weight { + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 17_146_000 picoseconds. + Weight::from_parts(17_453_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn update_mint_settings() -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `3538` + // Minimum execution time: 16_102_000 picoseconds. + Weight::from_parts(16_629_000, 3538) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn set_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `518` + // Estimated: `4326` + // Minimum execution time: 22_118_000 picoseconds. + Weight::from_parts(22_849_000, 4326) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:1 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn buy_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `705` + // Estimated: `4326` + // Minimum execution time: 50_369_000 picoseconds. + Weight::from_parts(51_816_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_203_000 picoseconds. + Weight::from_parts(3_710_869, 0) + // Standard Error: 8_094 + .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + } + /// Storage: `Nfts::Item` (r:2 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + fn create_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `494` + // Estimated: `7662` + // Minimum execution time: 18_893_000 picoseconds. + Weight::from_parts(19_506_000, 7662) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::PendingSwapOf` (r:1 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn cancel_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `513` + // Estimated: `4326` + // Minimum execution time: 19_086_000 picoseconds. + Weight::from_parts(19_609_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::Item` (r:2 w:2) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:1 w:2) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:2 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:4) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn claim_swap() -> Weight { + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `7662` + // Minimum execution time: 84_103_000 picoseconds. + Weight::from_parts(85_325_000, 7662) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) + } + /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) + /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:10 w:10) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:1) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 10]`. + fn mint_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `629` + // Estimated: `6078 + n * (2954 ±0)` + // Minimum execution time: 128_363_000 picoseconds. + Weight::from_parts(139_474_918, 6078) + // Standard Error: 79_252 + .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:1) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:10 w:10) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 10]`. + fn set_attributes_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4326 + n * (2954 ±0)` + // Minimum execution time: 66_688_000 picoseconds. + Weight::from_parts(79_208_379, 4326) + // Standard Error: 74_020 + .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) + } +} From 59bd0eca647dea33417f964d051e70586b8f9487 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:24:10 +0700 Subject: [PATCH 02/79] fix: formatting --- pallets/nfts/src/benchmarking.rs | 2 +- pallets/nfts/src/common_functions.rs | 4 +- pallets/nfts/src/features/approvals.rs | 3 +- pallets/nfts/src/features/atomic_swap.rs | 4 +- pallets/nfts/src/features/attributes.rs | 3 +- pallets/nfts/src/features/buy_sell.rs | 3 +- .../src/features/create_delete_collection.rs | 3 +- .../nfts/src/features/create_delete_item.rs | 3 +- pallets/nfts/src/features/lock.rs | 4 +- pallets/nfts/src/features/metadata.rs | 4 +- pallets/nfts/src/features/roles.rs | 4 +- pallets/nfts/src/features/settings.rs | 3 +- pallets/nfts/src/features/transfer.rs | 4 +- pallets/nfts/src/impl_nonfungibles.rs | 9 ++-- pallets/nfts/src/lib.rs | 10 ++-- pallets/nfts/src/migration.rs | 4 +- pallets/nfts/src/mock.rs | 48 +++++++++---------- pallets/nfts/src/tests.rs | 3 +- pallets/nfts/src/types.rs | 24 +++++++++- 19 files changed, 93 insertions(+), 49 deletions(-) diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index bc81096b..8fa87557 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -19,7 +19,6 @@ #![cfg(feature = "runtime-benchmarks")] -use super::*; use enumflags2::{BitFlag, BitFlags}; use frame_benchmarking::v1::{ account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError, @@ -32,6 +31,7 @@ use frame_support::{ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; use sp_runtime::traits::{Bounded, One}; +use super::*; use crate::Pallet as Nfts; const SEED: u32 = 0; diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index 2c4778c1..f51de192 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -17,10 +17,12 @@ //! Various pieces of common functionality. -use crate::*; use alloc::vec::Vec; + use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Get the owner of the item, if the item exists. pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 053fa671..ad5d93c2 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -19,9 +19,10 @@ //! The bitflag [`PalletFeature::Approvals`] needs to be set in [`Config::Features`] for NFTs //! to have the functionality defined in this module. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Approves the transfer of an item to a delegate. /// diff --git a/pallets/nfts/src/features/atomic_swap.rs b/pallets/nfts/src/features/atomic_swap.rs index 830283b7..31c93fba 100644 --- a/pallets/nfts/src/features/atomic_swap.rs +++ b/pallets/nfts/src/features/atomic_swap.rs @@ -20,12 +20,13 @@ //! The bitflag [`PalletFeature::Swaps`] needs to be set in [`Config::Features`] for NFTs //! to have the functionality defined in this module. -use crate::*; use frame_support::{ pallet_prelude::*, traits::{Currency, ExistenceRequirement::KeepAlive}, }; +use crate::*; + impl, I: 'static> Pallet { /// Creates a new swap offer for the specified item. /// @@ -101,6 +102,7 @@ impl, I: 'static> Pallet { Ok(()) } + /// Cancels the specified swap offer. /// /// This function is used to cancel the specified swap offer created by the `caller` account. If diff --git a/pallets/nfts/src/features/attributes.rs b/pallets/nfts/src/features/attributes.rs index 28f7bd2c..ab0cdc68 100644 --- a/pallets/nfts/src/features/attributes.rs +++ b/pallets/nfts/src/features/attributes.rs @@ -20,9 +20,10 @@ //! The bitflag [`PalletFeature::Attributes`] needs to be set in [`Config::Features`] for NFTs //! to have the functionality defined in this module. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Sets the attribute of an item or a collection. /// diff --git a/pallets/nfts/src/features/buy_sell.rs b/pallets/nfts/src/features/buy_sell.rs index d6ec6f50..8cf86f79 100644 --- a/pallets/nfts/src/features/buy_sell.rs +++ b/pallets/nfts/src/features/buy_sell.rs @@ -20,12 +20,13 @@ //! The bitflag [`PalletFeature::Trading`] needs to be set in the [`Config::Features`] for NFTs //! to have the functionality defined in this module. -use crate::*; use frame_support::{ pallet_prelude::*, traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, }; +use crate::*; + impl, I: 'static> Pallet { /// Pays the specified tips to the corresponding receivers. /// diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index f03df7fd..348ec6b9 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -18,9 +18,10 @@ //! This module contains helper methods to perform functionality associated with creating and //! destroying collections for the NFTs pallet. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Create a new collection with the given `collection`, `owner`, `admin`, `config`, `deposit`, /// and `event`. diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index 37f64ae1..e9843b2e 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -18,9 +18,10 @@ //! This module contains helper methods to perform functionality associated with minting and burning //! items for the NFTs pallet. -use crate::*; use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; +use crate::*; + impl, I: 'static> Pallet { /// Mint a new unique item with the given `collection`, `item`, and other minting configuration /// details. diff --git a/pallets/nfts/src/features/lock.rs b/pallets/nfts/src/features/lock.rs index 1c3c9c86..4649f4a0 100644 --- a/pallets/nfts/src/features/lock.rs +++ b/pallets/nfts/src/features/lock.rs @@ -18,9 +18,10 @@ //! This module contains helper methods to configure locks on collections and items for the NFTs //! pallet. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Locks a collection with specified settings. /// @@ -29,7 +30,6 @@ impl, I: 'static> Pallet { /// /// Note: it's possible only to lock the setting, but not to unlock it after. - /// /// - `origin`: The origin of the transaction, representing the account attempting to lock the /// collection. /// - `collection`: The identifier of the collection to be locked. diff --git a/pallets/nfts/src/features/metadata.rs b/pallets/nfts/src/features/metadata.rs index 26006160..b3d16b12 100644 --- a/pallets/nfts/src/features/metadata.rs +++ b/pallets/nfts/src/features/metadata.rs @@ -17,10 +17,12 @@ //! This module contains helper methods to configure the metadata of collections and items. -use crate::*; use alloc::vec::Vec; + use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Sets the metadata for a specific item within a collection. /// diff --git a/pallets/nfts/src/features/roles.rs b/pallets/nfts/src/features/roles.rs index aa6394f7..053eaf0b 100644 --- a/pallets/nfts/src/features/roles.rs +++ b/pallets/nfts/src/features/roles.rs @@ -17,10 +17,12 @@ //! This module contains helper methods to configure account roles for existing collections. -use crate::*; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; + use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Set the team roles for a specific collection. /// diff --git a/pallets/nfts/src/features/settings.rs b/pallets/nfts/src/features/settings.rs index d4f7533f..9c7ac7ca 100644 --- a/pallets/nfts/src/features/settings.rs +++ b/pallets/nfts/src/features/settings.rs @@ -17,9 +17,10 @@ //! This module provides helper methods to configure collection settings for the NFTs pallet. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Forcefully change the configuration of a collection. /// diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index bba83448..b7223a7c 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -18,9 +18,10 @@ //! This module contains helper methods to perform the transfer functionalities //! of the NFTs pallet. -use crate::*; use frame_support::pallet_prelude::*; +use crate::*; + impl, I: 'static> Pallet { /// Transfer an NFT to the specified destination account. /// @@ -160,6 +161,7 @@ impl, I: 'static> Pallet { Ok(()) }) } + /// Set or unset the ownership acceptance for an account regarding a specific collection. /// /// - `who`: The account for which to set or unset the ownership acceptance. diff --git a/pallets/nfts/src/impl_nonfungibles.rs b/pallets/nfts/src/impl_nonfungibles.rs index c90655aa..362cccd9 100644 --- a/pallets/nfts/src/impl_nonfungibles.rs +++ b/pallets/nfts/src/impl_nonfungibles.rs @@ -17,7 +17,6 @@ //! Implementations for `nonfungibles` traits. -use super::*; use frame_support::{ ensure, storage::KeyPrefixIterator, @@ -26,9 +25,11 @@ use frame_support::{ }; use sp_runtime::{DispatchError, DispatchResult}; +use super::*; + impl, I: 'static> Inspect<::AccountId> for Pallet { - type ItemId = T::ItemId; type CollectionId = T::CollectionId; + type ItemId = T::ItemId; fn owner( collection: &Self::CollectionId, @@ -140,9 +141,11 @@ impl, I: 'static> InspectRole<::AccountId> for P fn is_issuer(collection: &Self::CollectionId, who: &::AccountId) -> bool { Self::has_role(collection, who, CollectionRole::Issuer) } + fn is_admin(collection: &Self::CollectionId, who: &::AccountId) -> bool { Self::has_role(collection, who, CollectionRole::Admin) } + fn is_freezer(collection: &Self::CollectionId, who: &::AccountId) -> bool { Self::has_role(collection, who, CollectionRole::Freezer) } @@ -469,9 +472,9 @@ impl, I: 'static> Trading> for Pallet impl, I: 'static> InspectEnumerable for Pallet { type CollectionsIterator = KeyPrefixIterator<>::CollectionId>; type ItemsIterator = KeyPrefixIterator<>::ItemId>; + type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; type OwnedIterator = KeyPrefixIterator<(>::CollectionId, >::ItemId)>; - type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; /// Returns an iterator of the collections in existence. /// diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 4e5493a3..89bfb963 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -51,18 +51,18 @@ pub mod weights; extern crate alloc; use alloc::{boxed::Box, vec, vec::Vec}; + use codec::{Decode, Encode}; use frame_support::traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, Incrementable, ReservableCurrency, }; use frame_system::Config as SystemConfig; +pub use pallet::*; use sp_runtime::{ traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; - -pub use pallet::*; pub use types::*; pub use weights::WeightInfo; @@ -74,10 +74,11 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc #[frame_support::pallet] pub mod pallet { - use super::*; use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; + use super::*; + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -108,14 +109,17 @@ pub mod pallet { fn collection(i: u16) -> CollectionId { i.into() } + fn item(i: u16) -> ItemId { i.into() } + fn signer() -> (sp_runtime::MultiSigner, sp_runtime::AccountId32) { let public = sp_io::crypto::sr25519_generate(0.into(), None); let account = sp_runtime::MultiSigner::Sr25519(public).into_account(); (public.into(), account) } + fn sign(signer: &sp_runtime::MultiSigner, message: &[u8]) -> sp_runtime::MultiSignature { sp_runtime::MultiSignature::Sr25519( sp_io::crypto::sr25519_sign(0.into(), &signer.clone().try_into().unwrap(), message) diff --git a/pallets/nfts/src/migration.rs b/pallets/nfts/src/migration.rs index 8f82e092..af611bf1 100644 --- a/pallets/nfts/src/migration.rs +++ b/pallets/nfts/src/migration.rs @@ -15,13 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; use frame_support::traits::OnRuntimeUpgrade; use log; - #[cfg(feature = "try-runtime")] use sp_runtime::TryRuntimeError; +use super::*; + pub mod v1 { use frame_support::{pallet_prelude::*, weights::Weight}; diff --git a/pallets/nfts/src/mock.rs b/pallets/nfts/src/mock.rs index 5b589f59..5532be8f 100644 --- a/pallets/nfts/src/mock.rs +++ b/pallets/nfts/src/mock.rs @@ -17,9 +17,6 @@ //! Test environment for Nfts pallet. -use super::*; -use crate as pallet_nfts; - use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, @@ -30,6 +27,9 @@ use sp_runtime::{ BuildStorage, MultiSignature, }; +use super::*; +use crate as pallet_nfts; + type Block = frame_system::mocking::MockBlock; construct_runtime!( @@ -47,10 +47,10 @@ pub type AccountId = ::AccountId; #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { + type AccountData = pallet_balances::AccountData; type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type AccountData = pallet_balances::AccountData; + type Lookup = IdentityLookup; } #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] @@ -63,35 +63,35 @@ parameter_types! { } impl Config for Test { - type RuntimeEvent = RuntimeEvent; + type ApprovalsLimit = ConstU32<10>; + type AttributeDepositBase = ConstU64<1>; + type CollectionDeposit = ConstU64<2>; type CollectionId = u32; - type ItemId = u32; - type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type DepositPerByte = ConstU64<1>; + type Features = Features; type ForceOrigin = frame_system::EnsureRoot; - type Locker = (); - type CollectionDeposit = ConstU64<2>; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type ItemAttributesApprovalsLimit = ConstU32<2>; type ItemDeposit = ConstU64<1>; - type MetadataDepositBase = ConstU64<1>; - type AttributeDepositBase = ConstU64<1>; - type DepositPerByte = ConstU64<1>; - type StringLimit = ConstU32<50>; + type ItemId = u32; 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 Locker = (); type MaxAttributesPerCall = ConstU32<2>; - type Features = Features; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxTips = ConstU32<10>; + type MetadataDepositBase = ConstU64<1>; + /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. + type OffchainPublic = AccountPublic; /// Off-chain = signature On-chain - therefore no conversion needed. /// It needs to be From for benchmarking. type OffchainSignature = Signature; - /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. - type OffchainPublic = AccountPublic; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type Helper = (); } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index e1b598ca..44f2f32a 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -17,7 +17,6 @@ //! Tests for Nfts pallet. -use crate::{mock::*, Event, SystemConfig, *}; use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, @@ -33,6 +32,8 @@ use sp_runtime::{ MultiSignature, MultiSigner, }; +use crate::{mock::*, Event, SystemConfig, *}; + type AccountIdOf = ::AccountId; fn account(id: u8) -> AccountIdOf { diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 1687a035..f08f1d09 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -17,9 +17,8 @@ //! This module contains various basic types and data structures used in the NFTs pallet. -use super::*; -use crate::macros::*; use alloc::{vec, vec::Vec}; + use codec::EncodeLike; use enumflags2::{bitflags, BitFlags}; use frame_support::{ @@ -30,6 +29,9 @@ use frame_support::{ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; +use super::*; +use crate::macros::*; + /// A type alias for handling balance deposits. pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -276,12 +278,15 @@ impl CollectionSettings { pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } + 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) } @@ -377,12 +382,15 @@ impl CollectionConfig bool { !self.settings.is_disabled(setting) } + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { self.settings.is_disabled(setting) } + pub fn enable_setting(&mut self, setting: CollectionSetting) { self.settings.0.remove(setting); } + pub fn disable_setting(&mut self, setting: CollectionSetting) { self.settings.0.insert(setting); } @@ -409,12 +417,15 @@ impl ItemSettings { pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } + 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) } @@ -435,15 +446,19 @@ impl ItemConfig { pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { !self.settings.is_disabled(setting) } + pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { self.settings.is_disabled(setting) } + pub fn has_disabled_settings(&self) -> bool { !self.settings.get_disabled().is_empty() } + pub fn enable_setting(&mut self, setting: ItemSetting) { self.settings.0.remove(setting); } + pub fn disable_setting(&mut self, setting: ItemSetting) { self.settings.0.insert(setting); } @@ -472,9 +487,11 @@ impl PalletFeatures { pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } + pub fn from_disabled(features: BitFlags) -> Self { Self(features) } + pub fn is_enabled(&self, feature: PalletFeature) -> bool { !self.0.contains(feature) } @@ -502,12 +519,15 @@ 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 From 0460a44b47ca1a28287f16fb6c15a6f97ce9ef40 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:24:34 +0700 Subject: [PATCH 03/79] fix: formatting --- pallets/nfts/runtime-api/Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pallets/nfts/runtime-api/Cargo.toml b/pallets/nfts/runtime-api/Cargo.toml index 51aac0f9..17fd68f2 100644 --- a/pallets/nfts/runtime-api/Cargo.toml +++ b/pallets/nfts/runtime-api/Cargo.toml @@ -1,25 +1,25 @@ [package] -name = "pallet-nfts-runtime-api" -version = "23.0.0" authors.workspace = true +description = "Runtime API for the FRAME NFTs pallet. (polkadot v1.15.0)" edition.workspace = true -license = "Apache-2.0" homepage = "https://substrate.io" -repository.workspace = true -description = "Runtime API for the FRAME NFTs pallet. (polkadot v1.15.0)" +license = "Apache-2.0" +name = "pallet-nfts-runtime-api" readme = "README.md" +repository.workspace = true +version = "23.0.0" [lints] workspace = true [package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] +targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] -codec = { features = ["derive"], workspace = true } +codec = { features = [ "derive" ], workspace = true } pallet-nfts.workspace = true sp-api.workspace = true [features] -default = ["std"] -std = ["codec/std", "pallet-nfts/std", "sp-api/std"] +default = [ "std" ] +std = [ "codec/std", "pallet-nfts/std", "sp-api/std" ] From d18123f69f394e5160dd69c9ef094f9dfe75f518 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:47:40 +0700 Subject: [PATCH 04/79] feat: add nonfungibles implementation --- Cargo.lock | 1 + pallets/api/Cargo.toml | 3 + pallets/api/src/fungibles/tests.rs | 286 ++++++++++++++++---------- pallets/api/src/lib.rs | 1 + pallets/api/src/mock.rs | 74 ++++++- pallets/api/src/nonfungibles/mod.rs | 260 +++++++++++++++++++++++ pallets/api/src/nonfungibles/tests.rs | 183 ++++++++++++++++ pallets/api/src/nonfungibles/types.rs | 61 ++++++ 8 files changed, 750 insertions(+), 119 deletions(-) create mode 100644 pallets/api/src/nonfungibles/mod.rs create mode 100644 pallets/api/src/nonfungibles/tests.rs create mode 100644 pallets/api/src/nonfungibles/types.rs diff --git a/Cargo.lock b/Cargo.lock index 2c9ef33a..41c3df7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7366,6 +7366,7 @@ dependencies = [ "log", "pallet-assets", "pallet-balances", + "pallet-nfts", "parity-scale-codec", "pop-chain-extension", "scale-info", diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index 55a00789..5d398724 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -22,6 +22,7 @@ frame-benchmarking.workspace = true frame-support.workspace = true frame-system.workspace = true pallet-assets.workspace = true +pallet-nfts.workspace = true sp-runtime.workspace = true sp-std.workspace = true @@ -37,6 +38,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pop-chain-extension/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -47,6 +49,7 @@ std = [ "frame-system/std", "pallet-assets/std", "pallet-balances/std", + "pallet-nfts/std", "pop-chain-extension/std", "scale-info/std", "sp-core/std", diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index f5c560bb..6e181a6f 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -83,17 +83,21 @@ fn transfer_works() { let to = BOB; for origin in vec![root(), none()] { - assert_noop!(Fungibles::transfer(origin, token, to, value), BadOrigin); + assert_noop!(Fungibles::transfer(origin, token, account(to), value), BadOrigin); } // Check error works for `Assets::transfer_keep_alive()`. - assert_noop!(Fungibles::transfer(signed(from), token, to, value), AssetsError::Unknown); + assert_noop!( + Fungibles::transfer(signed(from), token, account(to), value), + AssetsError::Unknown + ); assets::create_and_mint_to(from, token, from, value * 2); - let balance_before_transfer = Assets::balance(token, &to); - assert_ok!(Fungibles::transfer(signed(from), token, to, value)); - let balance_after_transfer = Assets::balance(token, &to); + let balance_before_transfer = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::transfer(signed(from), token, account(to), value)); + let balance_after_transfer = Assets::balance(token, &account(to)); assert_eq!(balance_after_transfer, balance_before_transfer + value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), + Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } + .into(), ); }); } @@ -108,26 +112,36 @@ fn transfer_from_works() { let spender = CHARLIE; for origin in vec![root(), none()] { - assert_noop!(Fungibles::transfer_from(origin, token, from, to, value), BadOrigin); + assert_noop!( + Fungibles::transfer_from(origin, token, account(from), account(to), value), + BadOrigin + ); } // Check error works for `Assets::transfer_approved()`. assert_noop!( - Fungibles::transfer_from(signed(spender), token, from, to, value), + Fungibles::transfer_from(signed(spender), token, account(from), account(to), value), AssetsError::Unknown ); // Approve `spender` to transfer up to `value`. assets::create_mint_and_approve(spender, token, from, value * 2, spender, value); // Successfully call transfer from. - let from_balance_before_transfer = Assets::balance(token, &from); - let to_balance_before_transfer = Assets::balance(token, &to); - assert_ok!(Fungibles::transfer_from(signed(spender), token, from, to, value)); - let from_balance_after_transfer = Assets::balance(token, &from); - let to_balance_after_transfer = Assets::balance(token, &to); + let from_balance_before_transfer = Assets::balance(token, &account(from)); + let to_balance_before_transfer = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::transfer_from( + signed(spender), + token, + account(from), + account(to), + value + )); + let from_balance_after_transfer = Assets::balance(token, &account(from)); + let to_balance_after_transfer = Assets::balance(token, &account(to)); // Check that `to` has received the `value` tokens from `from`. assert_eq!(to_balance_after_transfer, to_balance_before_transfer + value); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), + Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } + .into(), ); }); } @@ -144,7 +158,7 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, spender, value), + Fungibles::approve(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } @@ -161,20 +175,20 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, spender, value), + Fungibles::approve(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()` in `Greater` match arm. assert_noop!( - Fungibles::approve(signed(owner), token, spender, value), + Fungibles::approve(signed(owner), token, account(spender), value), AssetsError::Unknown.with_weight(WeightInfo::approve(1, 0)) ); assets::create_mint_and_approve(owner, token, owner, value, spender, value); // Check error works for `Assets::cancel_approval()` in `Less` match arm. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); @@ -193,38 +207,61 @@ mod approve { // Approves a value to spend that is higher than the current allowance. assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(Assets::allowance(token, &owner, &spender), 0); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); assert_eq!( - Fungibles::approve(signed(owner), token, spender, value), + Fungibles::approve(signed(owner), token, account(spender), value), Ok(Some(WeightInfo::approve(1, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value); - System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + System::assert_last_event( + Event::Approval { token, owner: account(owner), spender: account(spender), value } + .into(), + ); // Approves a value to spend that is lower than the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); // Approves a value to spend that is equal to the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); // Sets allowance to zero. assert_eq!( - Fungibles::approve(signed(owner), token, spender, 0), + Fungibles::approve(signed(owner), token, account(spender), 0), Ok(Some(WeightInfo::approve(0, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), 0); - System::assert_last_event(Event::Approval { token, owner, spender, value: 0 }.into()); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); + System::assert_last_event( + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: 0, + } + .into(), + ); }); } } @@ -239,25 +276,34 @@ fn increase_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::increase_allowance(origin, token, spender, value), + Fungibles::increase_allowance(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()`. assert_noop!( - Fungibles::increase_allowance(signed(owner), token, spender, value), + Fungibles::increase_allowance(signed(owner), token, account(spender), value), AssetsError::Unknown.with_weight(AssetsWeightInfo::approve_transfer()) ); assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(0, Assets::allowance(token, &owner, &spender)); - assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); - assert_eq!(Assets::allowance(token, &owner, &spender), value); - System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); + assert_eq!(0, Assets::allowance(token, &account(owner), &account(spender))); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + System::assert_last_event( + Event::Approval { token, owner: account(owner), spender: account(spender), value } + .into(), + ); // Additive. - assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); - assert_eq!(Assets::allowance(token, &owner, &spender), value * 2); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value * 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value * 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value * 2, + } + .into(), ); }); } @@ -272,40 +318,46 @@ fn decrease_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::decrease_allowance(origin, token, spender, 0), + Fungibles::decrease_allowance(origin, token, account(spender), 0), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } assets::create_mint_and_approve(owner, token, owner, value, spender, value); - assert_eq!(Assets::allowance(token, &owner, &spender), value); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); // Check error works for `Assets::cancel_approval()`. No error test for `approve_transfer` // because it is not possible. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); // Owner balance is not changed if decreased by zero. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, spender, 0), + Fungibles::decrease_allowance(signed(owner), token, account(spender), 0), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); // "Unapproved" error is returned if the current allowance is less than amount to decrease // with. assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, spender, value * 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value * 2), AssetsError::Unapproved ); // Decrease allowance successfully. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); }); } @@ -318,14 +370,19 @@ fn create_works() { let admin = ALICE; for origin in vec![root(), none()] { - assert_noop!(Fungibles::create(origin, id, admin, 100), BadOrigin); + assert_noop!(Fungibles::create(origin, id, account(admin), 100), BadOrigin); } assert!(!Assets::asset_exists(id)); - assert_ok!(Fungibles::create(signed(creator), id, admin, 100)); + assert_ok!(Fungibles::create(signed(creator), id, account(admin), 100)); assert!(Assets::asset_exists(id)); - System::assert_last_event(Event::Created { id, creator, admin }.into()); + System::assert_last_event( + Event::Created { id, creator: account(creator), admin: account(admin) }.into(), + ); // Check error works for `Assets::create()`. - assert_noop!(Fungibles::create(signed(creator), id, admin, 100), AssetsError::InUse); + assert_noop!( + Fungibles::create(signed(creator), id, account(admin), 100), + AssetsError::InUse + ); }); } @@ -336,11 +393,11 @@ fn start_destroy_works() { // Check error works for `Assets::start_destroy()`. assert_noop!(Fungibles::start_destroy(signed(ALICE), token), AssetsError::Unknown); - assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); assert_ok!(Fungibles::start_destroy(signed(ALICE), token)); // Check that the token is not live after starting the destroy process. assert_noop!( - Assets::mint(signed(ALICE), token, ALICE, 10 * UNIT), + Assets::mint(signed(ALICE), token, account(ALICE), 10 * UNIT), AssetsError::AssetNotLive ); }); @@ -359,7 +416,7 @@ fn set_metadata_works() { Fungibles::set_metadata(signed(ALICE), token, name.clone(), symbol.clone(), decimals), AssetsError::Unknown ); - assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); assert_ok!(Fungibles::set_metadata( signed(ALICE), token, @@ -398,16 +455,16 @@ fn mint_works() { // Check error works for `Assets::mint()`. assert_noop!( - Fungibles::mint(signed(from), token, to, value), + Fungibles::mint(signed(from), token, account(to), value), sp_runtime::TokenError::UnknownAsset ); - assert_ok!(Assets::create(signed(from), token, from, 1)); - let balance_before_mint = Assets::balance(token, &to); - assert_ok!(Fungibles::mint(signed(from), token, to, value)); - let balance_after_mint = Assets::balance(token, &to); + assert_ok!(Assets::create(signed(from), token, account(from), 1)); + let balance_before_mint = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::mint(signed(from), token, account(to), value)); + let balance_after_mint = Assets::balance(token, &account(to)); assert_eq!(balance_after_mint, balance_before_mint + value); System::assert_last_event( - Event::Transfer { token, from: None, to: Some(to), value }.into(), + Event::Transfer { token, from: None, to: Some(account(to)), value }.into(), ); }); } @@ -423,27 +480,30 @@ fn burn_works() { // "BalanceLow" error is returned if token is not created. assert_noop!( - Fungibles::burn(signed(owner), token, from, value), + Fungibles::burn(signed(owner), token, account(from), value), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); assets::create_and_mint_to(owner, token, from, total_supply); assert_eq!(Assets::total_supply(TOKEN), total_supply); // Check error works for `Assets::burn()`. assert_ok!(Assets::freeze_asset(signed(owner), token)); - assert_noop!(Fungibles::burn(signed(owner), token, from, value), AssetsError::AssetNotLive); + assert_noop!( + Fungibles::burn(signed(owner), token, account(from), value), + AssetsError::AssetNotLive + ); assert_ok!(Assets::thaw_asset(signed(owner), token)); // "BalanceLow" error is returned if the balance is less than amount to burn. assert_noop!( - Fungibles::burn(signed(owner), token, from, total_supply * 2), + Fungibles::burn(signed(owner), token, account(from), total_supply * 2), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); - let balance_before_burn = Assets::balance(token, &from); - assert_ok!(Fungibles::burn(signed(owner), token, from, value)); + let balance_before_burn = Assets::balance(token, &account(from)); + assert_ok!(Fungibles::burn(signed(owner), token, account(from), value)); assert_eq!(Assets::total_supply(TOKEN), total_supply - value); - let balance_after_burn = Assets::balance(token, &from); + let balance_after_burn = Assets::balance(token, &account(from)); assert_eq!(balance_after_burn, balance_before_burn - value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: None, value }.into(), + Event::Transfer { token, from: Some(account(from)), to: None, value }.into(), ); }); } @@ -470,17 +530,17 @@ fn balance_of_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), ReadResult::BalanceOf(Default::default()) ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, value); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), ReadResult::BalanceOf(value) ); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }).encode(), - Assets::balance(TOKEN, ALICE).encode(), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + Assets::balance(TOKEN, account(ALICE)).encode(), ); }); } @@ -490,17 +550,30 @@ fn allowance_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }), ReadResult::Allowance(Default::default()) ); assets::create_mint_and_approve(ALICE, TOKEN, ALICE, value * 2, BOB, value); assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }), ReadResult::Allowance(value) ); assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }).encode(), - Assets::allowance(TOKEN, &ALICE, &BOB).encode(), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }) + .encode(), + Assets::allowance(TOKEN, &account(ALICE), &account(BOB)).encode(), ); }); } @@ -534,7 +607,7 @@ fn token_metadata_works() { fn token_exists_works() { new_test_ext().execute_with(|| { assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(false)); - assert_ok!(Assets::create(signed(ALICE), TOKEN, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), TOKEN, account(ALICE), 1)); assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(true)); assert_eq!( Fungibles::read(TokenExists(TOKEN)).encode(), @@ -543,8 +616,8 @@ fn token_exists_works() { }); } -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) +fn signed(account_id: u8) -> RuntimeOrigin { + RuntimeOrigin::signed(account(account_id)) } fn root() -> RuntimeOrigin { @@ -559,36 +632,31 @@ fn none() -> RuntimeOrigin { mod assets { use super::*; - pub(super) fn create_and_mint_to( - owner: AccountId, - token: TokenId, - to: AccountId, - value: Balance, - ) { - assert_ok!(Assets::create(signed(owner), token, owner, 1)); - assert_ok!(Assets::mint(signed(owner), token, to, value)); + pub(super) fn create_and_mint_to(owner: u8, token: TokenId, to: u8, value: Balance) { + assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); + assert_ok!(Assets::mint(signed(owner), token, account(to), value)); } pub(super) fn create_mint_and_approve( - owner: AccountId, + owner: u8, token: TokenId, - to: AccountId, + to: u8, mint: Balance, - spender: AccountId, + spender: u8, approve: Balance, ) { create_and_mint_to(owner, token, to, mint); - assert_ok!(Assets::approve_transfer(signed(to), token, spender, approve,)); + assert_ok!(Assets::approve_transfer(signed(to), token, account(spender), approve,)); } pub(super) fn create_and_set_metadata( - owner: AccountId, + owner: u8, token: TokenId, name: Vec, symbol: Vec, decimals: u8, ) { - assert_ok!(Assets::create(signed(owner), token, owner, 1)); + assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); assert_ok!(Assets::set_metadata(signed(owner), token, name, symbol, decimals)); } } @@ -613,11 +681,11 @@ mod read_weights { fn new() -> Self { Self { total_supply: Fungibles::weight(&TotalSupply(TOKEN)), - balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: ALICE }), + balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: account(ALICE) }), allowance: Fungibles::weight(&Allowance { token: TOKEN, - owner: ALICE, - spender: BOB, + owner: account(ALICE), + spender: account(BOB), }), token_name: Fungibles::weight(&TokenName(TOKEN)), token_symbol: Fungibles::weight(&TokenSymbol(TOKEN)), @@ -699,15 +767,15 @@ mod ensure_codec_indexes { [ (TotalSupply::(Default::default()), 0u8, "TotalSupply"), ( - BalanceOf:: { token: Default::default(), owner: Default::default() }, + BalanceOf:: { token: Default::default(), owner: account(Default::default()) }, 1, "BalanceOf", ), ( Allowance:: { token: Default::default(), - owner: Default::default(), - spender: Default::default(), + owner: account(Default::default()), + spender: account(Default::default()), }, 2, "Allowance", @@ -731,7 +799,7 @@ mod ensure_codec_indexes { ( transfer { token: Default::default(), - to: Default::default(), + to: account(Default::default()), value: Default::default(), }, 3u8, @@ -740,8 +808,8 @@ mod ensure_codec_indexes { ( transfer_from { token: Default::default(), - from: Default::default(), - to: Default::default(), + from: account(Default::default()), + to: account(Default::default()), value: Default::default(), }, 4, @@ -750,7 +818,7 @@ mod ensure_codec_indexes { ( approve { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 5, @@ -759,7 +827,7 @@ mod ensure_codec_indexes { ( increase_allowance { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 6, @@ -768,7 +836,7 @@ mod ensure_codec_indexes { ( decrease_allowance { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 7, @@ -777,7 +845,7 @@ mod ensure_codec_indexes { ( create { id: Default::default(), - admin: Default::default(), + admin: account(Default::default()), min_balance: Default::default(), }, 11, @@ -798,7 +866,7 @@ mod ensure_codec_indexes { ( mint { token: Default::default(), - account: Default::default(), + account: account(Default::default()), value: Default::default(), }, 19, @@ -807,7 +875,7 @@ mod ensure_codec_indexes { ( burn { token: Default::default(), - account: Default::default(), + account: account(Default::default()), value: Default::default(), }, 20, diff --git a/pallets/api/src/lib.rs b/pallets/api/src/lib.rs index d94d1978..e3d706e2 100644 --- a/pallets/api/src/lib.rs +++ b/pallets/api/src/lib.rs @@ -7,6 +7,7 @@ pub mod extension; pub mod fungibles; #[cfg(test)] mod mock; +pub mod nonfungibles; /// Trait for performing reads of runtime state. pub trait Read { diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index 42c8bf0e..920d590f 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -1,25 +1,28 @@ use frame_support::{ derive_impl, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, Everything}, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Everything}, }; use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_nfts::PalletFeatures; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, }; -pub(crate) const ALICE: AccountId = 1; -pub(crate) const BOB: AccountId = 2; -pub(crate) const CHARLIE: AccountId = 3; +pub(crate) const ALICE: u8 = 1; +pub(crate) const BOB: u8 = 2; +pub(crate) const CHARLIE: u8 = 3; pub(crate) const INIT_AMOUNT: Balance = 100_000_000 * UNIT; pub(crate) const UNIT: Balance = 10_000_000_000; type Block = frame_system::mocking::MockBlock; -pub(crate) type AccountId = u64; +pub(crate) type AccountId = ::AccountId; pub(crate) type Balance = u128; // For terminology in tests. pub(crate) type TokenId = u32; +type Signature = MultiSignature; +type AccountPublic = ::Signer; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -29,6 +32,8 @@ frame_support::construct_runtime!( Assets: pallet_assets::, Balances: pallet_balances, Fungibles: crate::fungibles, + Nfts: pallet_nfts, + NonFungibles: crate::nonfungibles } ); @@ -91,10 +96,10 @@ impl pallet_assets::Config for Test { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); type CallbackHandle = (); - type CreateOrigin = AsEnsureOriginWithArg>; + type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; type Extra = (); - type ForceOrigin = EnsureRoot; + type ForceOrigin = EnsureRoot; type Freezer = (); type MetadataDepositBase = ConstU128<1>; type MetadataDepositPerByte = ConstU128<1>; @@ -110,13 +115,62 @@ impl crate::fungibles::Config for Test { type WeightInfo = (); } +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Test { + type ApprovalsLimit = ConstU32<10>; + type AttributeDepositBase = ConstU128<1>; + type CollectionDeposit = ConstU128<2>; + type CollectionId = u32; + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type DepositPerByte = ConstU128<1>; + type Features = Features; + type ForceOrigin = frame_system::EnsureRoot; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type ItemAttributesApprovalsLimit = ConstU32<2>; + type ItemDeposit = ConstU128<1>; + type ItemId = u32; + type KeyLimit = ConstU32<50>; + type Locker = (); + type MaxAttributesPerCall = ConstU32<2>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxTips = ConstU32<10>; + type MetadataDepositBase = ConstU128<1>; + /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. + type OffchainPublic = AccountPublic; + /// Off-chain = signature On-chain - therefore no conversion needed. + /// It needs to be From for benchmarking. + type OffchainSignature = Signature; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type WeightInfo = (); +} + +impl crate::nonfungibles::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +/// Initialize a new account ID. +pub(crate) fn account(id: u8) -> AccountId { + [id; 32].into() +} + pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() .expect("Frame system builds valid default genesis config"); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INIT_AMOUNT), (BOB, INIT_AMOUNT), (CHARLIE, INIT_AMOUNT)], + balances: vec![ + (account(ALICE), INIT_AMOUNT), + (account(BOB), INIT_AMOUNT), + (account(CHARLIE), INIT_AMOUNT), + ], } .assimilate_storage(&mut t) .expect("Pallet balances storage can be assimilated"); diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs new file mode 100644 index 00000000..cac0cbba --- /dev/null +++ b/pallets/api/src/nonfungibles/mod.rs @@ -0,0 +1,260 @@ +//! The non-fungibles pallet offers a streamlined interface for interacting with non-fungible +//! assets. The goal is to provide a simplified, consistent API that adheres to standards in the +//! smart contract space. + +use frame_support::traits::nonfungibles_v2::InspectEnumerable; +pub use pallet::*; +use pallet_nfts::WeightInfo; +use sp_runtime::traits::StaticLookup; + +#[cfg(test)] +mod tests; +mod types; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + use sp_std::vec::Vec; + use types::{AccountIdOf, CollectionIdOf, ItemDetails, ItemIdOf, NftsOf, NftsWeightInfoOf}; + + use super::*; + + /// State reads for the fungibles API with required input. + #[derive(Encode, Decode, Debug, MaxEncodedLen)] + #[repr(u8)] + #[allow(clippy::unnecessary_cast)] + pub enum Read { + /// Returns the owner of an item. + #[codec(index = 0)] + OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, + /// Returns the owner of a collection. + #[codec(index = 1)] + CollectionOwner(CollectionIdOf), + /// Number of items existing in a concrete collection. + #[codec(index = 2)] + TotalSupply(CollectionIdOf), + /// Returns the total number of items in the collection owned by the account. + #[codec(index = 3)] + BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, + /// Returns the details of a collection. + #[codec(index = 4)] + Collection(CollectionIdOf), + /// Returns the details of an item. + #[codec(index = 5)] + Item { collection: CollectionIdOf, item: ItemIdOf }, + /// Whether a spender is allowed to transfer an item or items from owner. + #[codec(index = 6)] + Allowance { spender: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf }, + } + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_nfts::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// The events that can be emitted. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event emitted when allowance by `owner` to `spender` canceled. + CancelApproval { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The beneficiary of the allowance. + spender: AccountIdOf, + }, + /// Event emitted when allowance by `owner` to `spender` changes. + Approval { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The owner providing the allowance. + owner: AccountIdOf, + /// The beneficiary of the allowance. + spender: AccountIdOf, + }, + /// Event emitted when new item is minted to the account. + Mint { + /// The owner of the item. + to: AccountIdOf, + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + }, + /// Event emitted when item is burned. + Burn { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + }, + /// Event emitted when an item transfer occurs. + Transfer { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The source of the transfer. + from: AccountIdOf, + /// The recipient of the transfer. + to: AccountIdOf, + }, + } + + #[pallet::call] + impl Pallet { + /// Create a new non-fungible token to the collection. + /// + /// # Parameters + /// - `to` - The owner of the collection item. + /// - `collection` - The collection ID. + /// - `item` - The item ID. + #[pallet::call_index(0)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to.clone()), None)?; + Self::deposit_event(Event::Mint { to, collection, item }); + Ok(()) + } + + /// Destroy a new non-fungible token to the collection. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + #[pallet::call_index(1)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Burn { collection, item }); + Ok(()) + } + + /// Transfer a token from one account to the another account. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `to` - The recipient account. + #[pallet::call_index(2)] + #[pallet::weight(NftsWeightInfoOf::::transfer())] + pub fn transfer( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + to: AccountIdOf, + ) -> DispatchResult { + let from = ensure_signed(origin.clone())?; + NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; + Self::deposit_event(Event::Transfer { from, to, collection, item }); + Ok(()) + } + + /// Delegate a permission to perform actions on the collection item to an account. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `spender` - The account that is allowed to transfer the collection item. + #[pallet::call_index(3)] + #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] + pub fn approve( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + NftsOf::::approve_transfer( + origin, + collection, + item, + T::Lookup::unlookup(spender.clone()), + None, + )?; + Self::deposit_event(Event::Approval { collection, item, spender, owner }); + Ok(()) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `spender` - The account that is revoked permission to transfer the collection item. + #[pallet::call_index(4)] + #[pallet::weight(NftsWeightInfoOf::::cancel_approval())] + pub fn cancel_approval( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> DispatchResult { + NftsOf::::cancel_approval( + origin, + collection, + item, + T::Lookup::unlookup(spender.clone()), + )?; + Self::deposit_event(Event::CancelApproval { collection, item, spender }); + Ok(()) + } + } + + impl Pallet { + /// Reads fungible asset state based on the provided value. + /// + /// This function matches the value to determine the type of state query and returns the + /// encoded result. + /// + /// # Parameter + /// - `value` - An instance of `Read`, which specifies the type of state query and the + /// associated parameters. + pub fn read_state(value: Read) -> Vec { + use Read::*; + match value { + OwnerOf { collection, item } => NftsOf::::owner(collection, item).encode(), + CollectionOwner(collection) => NftsOf::::collection_owner(collection).encode(), + TotalSupply(collection) => (NftsOf::::items(&collection).count() as u8).encode(), + Collection(collection) => pallet_nfts::Collection::::get(&collection).encode(), + Item { collection, item } => pallet_nfts::Item::::get(collection, item).encode(), + Allowance { collection, item, spender } => + Self::allowance(collection, item, spender).encode(), + BalanceOf { collection, owner } => + (NftsOf::::owned_in_collection(&collection, &owner).count() as u8).encode(), + } + } + + /// Check if the `spender` is approved to transfer the collection item + pub(super) fn allowance( + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> bool { + let data = pallet_nfts::Item::::get(collection, item).encode(); + if let Ok(detail) = ItemDetails::::decode(&mut data.as_slice()) { + return detail.approvals.contains_key(&spender); + } + false + } + } +} diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs new file mode 100644 index 00000000..9e9da5c2 --- /dev/null +++ b/pallets/api/src/nonfungibles/tests.rs @@ -0,0 +1,183 @@ +use codec::Encode; +use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; + +use super::types::*; +use crate::{ + mock::*, + nonfungibles::{Event, Read::*}, +}; + +const ITEM: u32 = 1; + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let collection = create_collection(owner.clone()); + // Successfully mint a new collection item. + assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); + System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + System::assert_last_event(Event::Burn { collection, item }.into()); + }); +} + +#[test] +fn transfer() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let dest = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + System::assert_last_event( + Event::Transfer { collection, item, from: owner, to: dest }.into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully approve `spender` to transfer the collection item. + assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); + System::assert_last_event( + Event::Approval { collection, item, owner, spender: spender.clone() }.into(), + ); + // Successfully transfer the item by the delegated account `spender`. + assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + // Successfully cancel the transfer approval of `spender` by `owner`. + assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); + // Failed to transfer the item by `spender` without permission. + assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + }); +} + +#[test] +fn owner_of_works() {} + +#[test] +fn collection_owner_works() { + new_test_ext().execute_with(|| { + let collection = create_collection(account(ALICE)); + assert_eq!( + NonFungibles::read_state(CollectionOwner(collection)), + Nfts::collection_owner(collection).encode() + ); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read_state(TotalSupply(collection)), + (Nfts::items(&collection).count() as u8).encode() + ); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read_state(Collection(collection)), + pallet_nfts::Collection::::get(&collection).encode(), + ); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, _) = create_collection_mint(owner.clone(), ITEM); + assert_eq!( + NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), + (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() + ); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + assert_eq!( + NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), + super::Pallet::::allowance(collection, item, spender).encode() + ); + }); +} + +fn signed(account: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account) +} + +fn create_collection_mint_and_approve( + owner: AccountIdOf, + item: ItemIdOf, + spender: AccountIdOf, +) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner.clone(), item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); + (collection, item) +} + +fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner.clone()); + assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); + (collection, item) +} + +fn create_collection(owner: AccountIdOf) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner.clone()), + owner.clone(), + collection_config_with_all_settings_enabled() + )); + next_id +} + +fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs new file mode 100644 index 00000000..0174ef77 --- /dev/null +++ b/pallets/api/src/nonfungibles/types.rs @@ -0,0 +1,61 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_runtime::BoundedBTreeMap; + +use super::Config; + +pub(super) type AccountIdOf = ::AccountId; + +pub(super) type NftsOf = pallet_nfts::Pallet; + +/// Weight information for extrinsics in this pallet. +pub(super) type NftsWeightInfoOf = ::WeightInfo; + +/// A type alias for the collection ID. +pub(super) type CollectionIdOf = + as Inspect<::AccountId>>::CollectionId; + +/// A type alias for the collection item ID. +pub(super) type ItemIdOf = + as Inspect<::AccountId>>::ItemId; + +// TODO: Even though this serves the `allowance` method, it creates the maintenance cost. + +/// A type that holds the deposit for a single item. +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; + +/// A type alias for handling balance deposits. +pub(super) type DepositBalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +/// A type alias for keeping track of approvals used by a single item. +pub(super) type ApprovalsOf = BoundedBTreeMap< + AccountIdOf, + Option>, + ::ApprovalsLimit, +>; + +/// Information concerning the ownership of a single unique item. +#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub(super) struct ItemDetails { + /// The owner of this item. + pub(super) owner: AccountIdOf, + /// The approved transferrer of this item, if one is set. + pub(super) approvals: ApprovalsOf, + /// The amount held in the pallet's default account for this item. Free-hold items will have + /// this as zero. + pub(super) deposit: ItemDepositOf, +} + +/// Information about the reserved item deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub struct ItemDeposit { + /// A depositor account. + pub(super) account: AccountId, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} From 8832470675fb10d537baebe5295672c580bd2719 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:12:39 +0700 Subject: [PATCH 05/79] chore: add nfts pallet --- Cargo.lock | 2 +- pallets/api/src/nonfungibles/mod.rs | 171 +++++++++--- pallets/api/src/nonfungibles/tests.rs | 367 +++++++++++++------------- pallets/api/src/nonfungibles/types.rs | 57 ++-- pallets/nfts/Cargo.toml | 1 - 5 files changed, 357 insertions(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41c3df7b..bd6525c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7366,7 +7366,7 @@ dependencies = [ "log", "pallet-assets", "pallet-balances", - "pallet-nfts", + "pallet-nfts 31.0.0", "parity-scale-codec", "pop-chain-extension", "scale-info", diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index cac0cbba..c5e8cffb 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -2,10 +2,9 @@ //! assets. The goal is to provide a simplified, consistent API that adheres to standards in the //! smart contract space. -use frame_support::traits::nonfungibles_v2::InspectEnumerable; pub use pallet::*; use pallet_nfts::WeightInfo; -use sp_runtime::traits::StaticLookup; +use sp_runtime::{traits::StaticLookup, RuntimeDebug}; #[cfg(test)] mod tests; @@ -16,12 +15,16 @@ pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; use sp_std::vec::Vec; - use types::{AccountIdOf, CollectionIdOf, ItemDetails, ItemIdOf, NftsOf, NftsWeightInfoOf}; + use types::{ + AccountIdOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, + NftsOf, NftsWeightInfoOf, + }; use super::*; - /// State reads for the fungibles API with required input. + /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -45,7 +48,41 @@ pub mod pallet { Item { collection: CollectionIdOf, item: ItemIdOf }, /// Whether a spender is allowed to transfer an item or items from owner. #[codec(index = 6)] - Allowance { spender: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf }, + Allowance { + collection: CollectionIdOf, + owner: AccountIdOf, + operator: AccountIdOf, + item: Option>, + }, + } + + /// Results of state reads for the non-fungibles API. + #[derive(Debug)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + pub enum ReadResult { + OwnerOf(Option>), + CollectionOwner(Option>), + TotalSupply(u32), + BalanceOf(BalanceOf), + Collection(Option>), + Item(Option>), + Allowance(bool), + } + + impl ReadResult { + /// Encodes the result. + pub fn encode(&self) -> Vec { + use ReadResult::*; + match self { + OwnerOf(result) => result.encode(), + CollectionOwner(result) => result.encode(), + TotalSupply(result) => result.encode(), + BalanceOf(result) => result.encode(), + Collection(result) => result.encode(), + Item(result) => result.encode(), + Allowance(result) => result.encode(), + } + } } /// Configure the pallet by specifying the parameters and types on which it depends. @@ -55,6 +92,32 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + #[pallet::storage] + type AccountBalance = StorageNMap< + Key = ( + // Collection ID + NMapKey>, + // Collection Owner ID + NMapKey>, + ), + Value = BalanceOf, + QueryKind = ValueQuery, + >; + + #[pallet::storage] + type Allowances = StorageNMap< + Key = ( + // Collection ID + NMapKey>, + // Collection Owner ID + NMapKey>, + // Collection Operator ID + NMapKey>, + ), + Value = bool, + QueryKind = ValueQuery, + >; + #[pallet::pallet] pub struct Pallet(_); @@ -221,40 +284,86 @@ pub mod pallet { } impl Pallet { - /// Reads fungible asset state based on the provided value. - /// - /// This function matches the value to determine the type of state query and returns the - /// encoded result. - /// - /// # Parameter - /// - `value` - An instance of `Read`, which specifies the type of state query and the - /// associated parameters. - pub fn read_state(value: Read) -> Vec { - use Read::*; - match value { - OwnerOf { collection, item } => NftsOf::::owner(collection, item).encode(), - CollectionOwner(collection) => NftsOf::::collection_owner(collection).encode(), - TotalSupply(collection) => (NftsOf::::items(&collection).count() as u8).encode(), - Collection(collection) => pallet_nfts::Collection::::get(&collection).encode(), - Item { collection, item } => pallet_nfts::Item::::get(collection, item).encode(), - Allowance { collection, item, spender } => - Self::allowance(collection, item, spender).encode(), - BalanceOf { collection, owner } => - (NftsOf::::owned_in_collection(&collection, &owner).count() as u8).encode(), - } + /// Check if the `spender` is approved to transfer the collection item. + pub(super) fn allowance( + collection: CollectionIdOf, + owner: AccountIdOf, + operator: AccountIdOf, + maybe_item: Option>, + ) -> bool { + // Check if has a permission to transfer all collection items. + Allowances::::get((collection, owner, operator.clone())) || + maybe_item + .and_then(|item| Some(Self::allowance_item(collection, operator, item))) + .unwrap_or(false) } - /// Check if the `spender` is approved to transfer the collection item - pub(super) fn allowance( + // Check the permission for the single item. + pub(super) fn allowance_item( collection: CollectionIdOf, + operator: AccountIdOf, item: ItemIdOf, - spender: AccountIdOf, ) -> bool { let data = pallet_nfts::Item::::get(collection, item).encode(); - if let Ok(detail) = ItemDetails::::decode(&mut data.as_slice()) { - return detail.approvals.contains_key(&spender); + if let Ok(detail) = ItemDetailsFor::::decode(&mut data.as_slice()) { + return detail.approvals.contains_key(&operator); } false } } + + impl crate::Read for Pallet { + /// The type of read requested. + type Read = Read; + /// The type or result returned. + type Result = ReadResult; + + /// Determines the weight of the requested read, used to charge the appropriate weight + /// before the read is performed. + /// + /// # Parameters + /// - `request` - The read request. + fn weight(_request: &Self::Read) -> Weight { + Default::default() + } + + /// Performs the requested read and returns the result. + /// + /// # Parameters + /// - `request` - The read request. + fn read(value: Self::Read) -> Self::Result { + use Read::*; + match value { + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), + CollectionOwner(collection) => + ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), + TotalSupply(collection) => { + let data = pallet_nfts::Collection::::get(collection).encode(); + ReadResult::TotalSupply( + CollectionDetailsFor::::decode(&mut data.as_slice()) + .map(|detail| detail.items) + .unwrap_or_default(), + ) + }, + Collection(collection) => { + let data = pallet_nfts::Collection::::get(collection).encode(); + ReadResult::Collection( + Option::>::decode(&mut data.as_slice()) + .unwrap_or(None), + ) + }, + Item { collection, item } => { + let data = pallet_nfts::Item::::get(collection, item).encode(); + ReadResult::Item( + Option::>::decode(&mut data.as_slice()).unwrap_or(None), + ) + }, + Allowance { collection, owner, operator, item } => + ReadResult::Allowance(Self::allowance(collection, owner, operator, item)), + BalanceOf { collection, owner } => + ReadResult::BalanceOf(AccountBalance::::get((collection, owner))), + } + } + } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 9e9da5c2..54d85516 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,183 +1,184 @@ -use codec::Encode; -use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; -use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; - -use super::types::*; -use crate::{ - mock::*, - nonfungibles::{Event, Read::*}, -}; - -const ITEM: u32 = 1; - -#[test] -fn mint_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let collection = create_collection(owner.clone()); - // Successfully mint a new collection item. - assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); - System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); - }); -} - -#[test] -fn burn_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); - System::assert_last_event(Event::Burn { collection, item }.into()); - }); -} - -#[test] -fn transfer() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let dest = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); - System::assert_last_event( - Event::Transfer { collection, item, from: owner, to: dest }.into(), - ); - }); -} - -#[test] -fn approve_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully approve `spender` to transfer the collection item. - assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); - System::assert_last_event( - Event::Approval { collection, item, owner, spender: spender.clone() }.into(), - ); - // Successfully transfer the item by the delegated account `spender`. - assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); - }); -} - -#[test] -fn cancel_approval_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - // Successfully cancel the transfer approval of `spender` by `owner`. - assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); - // Failed to transfer the item by `spender` without permission. - assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); - }); -} - -#[test] -fn owner_of_works() {} - -#[test] -fn collection_owner_works() { - new_test_ext().execute_with(|| { - let collection = create_collection(account(ALICE)); - assert_eq!( - NonFungibles::read_state(CollectionOwner(collection)), - Nfts::collection_owner(collection).encode() - ); - }); -} - -#[test] -fn total_supply_works() { - new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); - assert_eq!( - NonFungibles::read_state(TotalSupply(collection)), - (Nfts::items(&collection).count() as u8).encode() - ); - }); -} - -#[test] -fn collection_works() { - new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); - assert_eq!( - NonFungibles::read_state(Collection(collection)), - pallet_nfts::Collection::::get(&collection).encode(), - ); - }); -} - -#[test] -fn balance_of_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, _) = create_collection_mint(owner.clone(), ITEM); - assert_eq!( - NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), - (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() - ); - }); -} - -#[test] -fn allowance_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - assert_eq!( - NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), - super::Pallet::::allowance(collection, item, spender).encode() - ); - }); -} - -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) -} - -fn create_collection_mint_and_approve( - owner: AccountIdOf, - item: ItemIdOf, - spender: AccountIdOf, -) -> (u32, u32) { - let (collection, item) = create_collection_mint(owner.clone(), item); - assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); - (collection, item) -} - -fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { - let collection = create_collection(owner.clone()); - assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); - (collection, item) -} - -fn create_collection(owner: AccountIdOf) -> u32 { - let next_id = next_collection_id(); - assert_ok!(Nfts::create( - signed(owner.clone()), - owner.clone(), - collection_config_with_all_settings_enabled() - )); - next_id -} - -fn next_collection_id() -> u32 { - pallet_nfts::NextCollectionId::::get().unwrap_or_default() -} - -fn collection_config_with_all_settings_enabled( -) -> CollectionConfig, CollectionIdOf> { - CollectionConfig { - settings: CollectionSettings::all_enabled(), - max_supply: None, - mint_settings: MintSettings::default(), - } -} +// TODO +// use codec::Encode; +// use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +// use frame_system::pallet_prelude::BlockNumberFor; +// use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; + +// use super::types::*; +// use crate::{ +// mock::*, +// nonfungibles::{Event, Read::*}, +// }; + +// const ITEM: u32 = 1; + +// #[test] +// fn mint_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let collection = create_collection(owner.clone()); +// // Successfully mint a new collection item. +// assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); +// System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); +// }); +// } + +// #[test] +// fn burn_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully burn an existing new collection item. +// assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); +// System::assert_last_event(Event::Burn { collection, item }.into()); +// }); +// } + +// #[test] +// fn transfer() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let dest = account(BOB); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully burn an existing new collection item. +// assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); +// System::assert_last_event( +// Event::Transfer { collection, item, from: owner, to: dest }.into(), +// ); +// }); +// } + +// #[test] +// fn approve_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully approve `spender` to transfer the collection item. +// assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); +// System::assert_last_event( +// Event::Approval { collection, item, owner, spender: spender.clone() }.into(), +// ); +// // Successfully transfer the item by the delegated account `spender`. +// assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); +// }); +// } + +// #[test] +// fn cancel_approval_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = +// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); +// // Successfully cancel the transfer approval of `spender` by `owner`. +// assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); +// // Failed to transfer the item by `spender` without permission. +// assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); +// }); +// } + +// #[test] +// fn owner_of_works() {} + +// #[test] +// fn collection_owner_works() { +// new_test_ext().execute_with(|| { +// let collection = create_collection(account(ALICE)); +// assert_eq!( +// NonFungibles::read_state(CollectionOwner(collection)), +// Nfts::collection_owner(collection).encode() +// ); +// }); +// } + +// #[test] +// fn total_supply_works() { +// new_test_ext().execute_with(|| { +// let (collection, _) = create_collection_mint(account(ALICE), ITEM); +// assert_eq!( +// NonFungibles::read_state(TotalSupply(collection)), +// (Nfts::items(&collection).count() as u8).encode() +// ); +// }); +// } + +// #[test] +// fn collection_works() { +// new_test_ext().execute_with(|| { +// let (collection, _) = create_collection_mint(account(ALICE), ITEM); +// assert_eq!( +// NonFungibles::read_state(Collection(collection)), +// pallet_nfts::Collection::::get(&collection).encode(), +// ); +// }); +// } + +// #[test] +// fn balance_of_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let (collection, _) = create_collection_mint(owner.clone(), ITEM); +// assert_eq!( +// NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), +// (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() +// ); +// }); +// } + +// #[test] +// fn allowance_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = +// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); +// assert_eq!( +// NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), +// super::Pallet::::allowance(collection, item, spender).encode() +// ); +// }); +// } + +// fn signed(account: AccountId) -> RuntimeOrigin { +// RuntimeOrigin::signed(account) +// } + +// fn create_collection_mint_and_approve( +// owner: AccountIdOf, +// item: ItemIdOf, +// spender: AccountIdOf, +// ) -> (u32, u32) { +// let (collection, item) = create_collection_mint(owner.clone(), item); +// assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); +// (collection, item) +// } + +// fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { +// let collection = create_collection(owner.clone()); +// assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); +// (collection, item) +// } + +// fn create_collection(owner: AccountIdOf) -> u32 { +// let next_id = next_collection_id(); +// assert_ok!(Nfts::create( +// signed(owner.clone()), +// owner.clone(), +// collection_config_with_all_settings_enabled() +// )); +// next_id +// } + +// fn next_collection_id() -> u32 { +// pallet_nfts::NextCollectionId::::get().unwrap_or_default() +// } + +// fn collection_config_with_all_settings_enabled( +// ) -> CollectionConfig, CollectionIdOf> { +// CollectionConfig { +// settings: CollectionSettings::all_enabled(), +// max_supply: None, +// mint_settings: MintSettings::default(), +// } +// } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index 0174ef77..a55a8ec2 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -4,34 +4,22 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_runtime::BoundedBTreeMap; -use super::Config; +use super::*; pub(super) type AccountIdOf = ::AccountId; - pub(super) type NftsOf = pallet_nfts::Pallet; - /// Weight information for extrinsics in this pallet. pub(super) type NftsWeightInfoOf = ::WeightInfo; - /// A type alias for the collection ID. pub(super) type CollectionIdOf = - as Inspect<::AccountId>>::CollectionId; - + as Inspect<::AccountId>>::CollectionId; /// A type alias for the collection item ID. pub(super) type ItemIdOf = - as Inspect<::AccountId>>::ItemId; - -// TODO: Even though this serves the `allowance` method, it creates the maintenance cost. - -/// A type that holds the deposit for a single item. -pub(super) type ItemDepositOf = - ItemDeposit, ::AccountId>; - + as Inspect<::AccountId>>::ItemId; /// A type alias for handling balance deposits. -pub(super) type DepositBalanceOf = <::Currency as Currency< +pub(super) type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; - /// A type alias for keeping track of approvals used by a single item. pub(super) type ApprovalsOf = BoundedBTreeMap< AccountIdOf, @@ -39,23 +27,42 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< ::ApprovalsLimit, >; +pub(super) type ItemDetailsFor = ItemDetails, BalanceOf, ApprovalsOf>; +pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; + /// Information concerning the ownership of a single unique item. -#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -pub(super) struct ItemDetails { +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemDetails { /// The owner of this item. - pub(super) owner: AccountIdOf, + pub owner: AccountId, /// The approved transferrer of this item, if one is set. - pub(super) approvals: ApprovalsOf, + pub 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: ItemDepositOf, + pub deposit: Deposit, } - /// Information about the reserved item deposit. -#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemDeposit { /// A depositor account. - pub(super) account: AccountId, + account: AccountId, /// An amount that gets reserved. - pub(super) amount: DepositBalance, + amount: DepositBalance, +} +/// Information about a collection. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: DepositBalance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, } diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index 19d35803..b5639547 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -9,7 +9,6 @@ readme = "README.md" repository.workspace = true version = "31.0.0" - [package.metadata.docs.rs] targets = [ "x86_64-unknown-linux-gnu" ] From facbb172579d9f53b4e1761a51106479d3ddbd6a Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:23:52 +0700 Subject: [PATCH 06/79] feat: add new storage items to pallet-nfts --- pallets/api/src/nonfungibles/mod.rs | 101 +++--------------- pallets/api/src/nonfungibles/types.rs | 68 +++--------- pallets/nfts/src/common_functions.rs | 7 +- pallets/nfts/src/features/approvals.rs | 85 ++++++++++++++- .../src/features/create_delete_collection.rs | 3 + .../nfts/src/features/create_delete_item.rs | 6 ++ pallets/nfts/src/features/transfer.rs | 8 ++ pallets/nfts/src/lib.rs | 88 ++++++++++++++- 8 files changed, 220 insertions(+), 146 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index c5e8cffb..cf2b14e7 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -4,7 +4,7 @@ pub use pallet::*; use pallet_nfts::WeightInfo; -use sp_runtime::{traits::StaticLookup, RuntimeDebug}; +use sp_runtime::traits::StaticLookup; #[cfg(test)] mod tests; @@ -16,8 +16,8 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_std::vec::Vec; use types::{ - AccountIdOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, - NftsOf, NftsWeightInfoOf, + AccountIdOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, NftsOf, + NftsWeightInfoOf, }; use super::*; @@ -63,7 +63,7 @@ pub mod pallet { OwnerOf(Option>), CollectionOwner(Option>), TotalSupply(u32), - BalanceOf(BalanceOf), + BalanceOf(u32), Collection(Option>), Item(Option>), Allowance(bool), @@ -92,32 +92,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::storage] - type AccountBalance = StorageNMap< - Key = ( - // Collection ID - NMapKey>, - // Collection Owner ID - NMapKey>, - ), - Value = BalanceOf, - QueryKind = ValueQuery, - >; - - #[pallet::storage] - type Allowances = StorageNMap< - Key = ( - // Collection ID - NMapKey>, - // Collection Owner ID - NMapKey>, - // Collection Operator ID - NMapKey>, - ), - Value = bool, - QueryKind = ValueQuery, - >; - #[pallet::pallet] pub struct Pallet(_); @@ -283,35 +257,6 @@ pub mod pallet { } } - impl Pallet { - /// Check if the `spender` is approved to transfer the collection item. - pub(super) fn allowance( - collection: CollectionIdOf, - owner: AccountIdOf, - operator: AccountIdOf, - maybe_item: Option>, - ) -> bool { - // Check if has a permission to transfer all collection items. - Allowances::::get((collection, owner, operator.clone())) || - maybe_item - .and_then(|item| Some(Self::allowance_item(collection, operator, item))) - .unwrap_or(false) - } - - // Check the permission for the single item. - pub(super) fn allowance_item( - collection: CollectionIdOf, - operator: AccountIdOf, - item: ItemIdOf, - ) -> bool { - let data = pallet_nfts::Item::::get(collection, item).encode(); - if let Ok(detail) = ItemDetailsFor::::decode(&mut data.as_slice()) { - return detail.approvals.contains_key(&operator); - } - false - } - } - impl crate::Read for Pallet { /// The type of read requested. type Read = Read; @@ -338,31 +283,19 @@ pub mod pallet { ReadResult::OwnerOf(NftsOf::::owner(collection, item)), CollectionOwner(collection) => ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), - TotalSupply(collection) => { - let data = pallet_nfts::Collection::::get(collection).encode(); - ReadResult::TotalSupply( - CollectionDetailsFor::::decode(&mut data.as_slice()) - .map(|detail| detail.items) - .unwrap_or_default(), - ) - }, - Collection(collection) => { - let data = pallet_nfts::Collection::::get(collection).encode(); - ReadResult::Collection( - Option::>::decode(&mut data.as_slice()) - .unwrap_or(None), - ) - }, - Item { collection, item } => { - let data = pallet_nfts::Item::::get(collection, item).encode(); - ReadResult::Item( - Option::>::decode(&mut data.as_slice()).unwrap_or(None), - ) - }, - Allowance { collection, owner, operator, item } => - ReadResult::Allowance(Self::allowance(collection, owner, operator, item)), - BalanceOf { collection, owner } => - ReadResult::BalanceOf(AccountBalance::::get((collection, owner))), + TotalSupply(collection) => ReadResult::TotalSupply( + NftsOf::::collection_items(collection).unwrap_or_default(), + ), + Collection(collection) => + ReadResult::Collection(pallet_nfts::Collection::::get(collection)), + Item { collection, item } => + ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + Allowance { collection, owner, operator, item } => ReadResult::Allowance( + NftsOf::::allowance(collection, item, owner, operator).unwrap_or(false), + ), + BalanceOf { collection, owner } => ReadResult::BalanceOf( + pallet_nfts::AccountBalance::::get((collection, owner)), + ), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index a55a8ec2..f81ea535 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,68 +1,28 @@ -use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use scale_info::TypeInfo; +use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; use sp_runtime::BoundedBTreeMap; -use super::*; - -pub(super) type AccountIdOf = ::AccountId; +// Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; -/// Weight information for extrinsics in this pallet. pub(super) type NftsWeightInfoOf = ::WeightInfo; -/// A type alias for the collection ID. +// Type aliases for pallet-nfts storage items. +pub(super) type AccountIdOf = ::AccountId; +pub(super) type BalanceOf = <>::Currency as Currency< + ::AccountId, +>>::Balance; pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; -/// A type alias for the collection item ID. pub(super) type ItemIdOf = as Inspect<::AccountId>>::ItemId; -/// A type alias for handling balance deposits. -pub(super) type BalanceOf = <::Currency as Currency< - ::AccountId, ->>::Balance; -/// A type alias for keeping track of approvals used by a single item. -pub(super) type ApprovalsOf = BoundedBTreeMap< +type ApprovalsOf = BoundedBTreeMap< AccountIdOf, Option>, ::ApprovalsLimit, >; - -pub(super) type ItemDetailsFor = ItemDetails, BalanceOf, ApprovalsOf>; -pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; - -/// Information concerning the ownership of a single unique item. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ItemDetails { - /// The owner of this item. - pub owner: AccountId, - /// The approved transferrer of this item, if one is set. - pub approvals: Approvals, - /// The amount held in the pallet's default account for this item. Free-hold items will have - /// this as zero. - pub deposit: Deposit, -} -/// Information about the reserved item deposit. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ItemDeposit { - /// A depositor account. - account: AccountId, - /// An amount that gets reserved. - amount: DepositBalance, -} -/// Information about a collection. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct CollectionDetails { - /// Collection's owner. - pub owner: AccountId, - /// The total balance deposited by the owner for all the storage data associated with this - /// collection. Used by `destroy`. - pub owner_deposit: DepositBalance, - /// The total number of outstanding items of this collection. - pub items: u32, - /// The total number of outstanding item metadata of this collection. - pub item_metadatas: u32, - /// The total number of outstanding item configs of this collection. - pub item_configs: u32, - /// The total number of attributes for this collection. - pub attributes: u32, -} +// TODO: Multi-instances. +pub(super) type ItemDepositOf = ItemDeposit, AccountIdOf>; +pub(super) type CollectionDetailsFor = + CollectionDetails, BalanceOf>; +pub(super) type ItemDetailsFor = + ItemDetails, ItemDepositOf, ApprovalsOf>; diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index f51de192..6fe483f1 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -34,6 +34,11 @@ impl, I: 'static> Pallet { Collection::::get(collection).map(|i| i.owner) } + /// Get the total number of items in the collection, if the collection exists. + pub fn collection_items(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.items) + } + /// Validates the signature of the given data with the provided signer's account ID. /// /// # Errors @@ -46,7 +51,7 @@ impl, I: 'static> Pallet { signer: &T::AccountId, ) -> DispatchResult { if signature.verify(&**data, &signer) { - return Ok(()) + return Ok(()); } // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index ad5d93c2..f626a9fe 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -65,7 +65,6 @@ impl, I: 'static> Pallet { if let Some(check_origin) = maybe_check_origin { ensure!(check_origin == details.owner, Error::::NoPermission); } - let now = frame_system::Pallet::::block_number(); let deadline = maybe_deadline.map(|d| d.saturating_add(now)); @@ -74,15 +73,13 @@ impl, I: 'static> Pallet { .try_insert(delegate.clone(), deadline) .map_err(|_| Error::::ReachedApprovalLimit)?; Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::TransferApproved { collection, - item, + item: Some(item), owner: details.owner, delegate, deadline, }); - Ok(()) } @@ -129,7 +126,7 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::ApprovalCancelled { collection, - item, + item: Some(item), owner: details.owner, delegate, }); @@ -173,4 +170,82 @@ impl, I: 'static> Pallet { Ok(()) } + + pub(crate) fn do_approve_transfer_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let collection_owner = + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + 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 { + ensure!(check_origin == collection_owner, Error::::NoPermission); + } + + Allowances::::mutate((&collection, &collection_owner, &delegate), |allowance| { + *allowance = true; + }); + + Self::deposit_event(Event::TransferApproved { + collection, + item: None, + owner: collection_owner, + delegate, + deadline: None, + }); + Ok(()) + } + + pub(crate) fn do_cancel_approval_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + let collection_owner = + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == collection_owner, Error::::NoPermission); + } + + Allowances::::remove((&collection, &collection_owner, &delegate)); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + owner: collection_owner, + item: None, + delegate, + }); + + Ok(()) + } + + pub fn allowance( + collection: T::CollectionId, + item: Option, + owner: T::AccountId, + delegate: T::AccountId, + ) -> Option { + // Check if a `delegate` has a permission to spend the collection. + if Allowances::::get((&collection, &owner, &delegate)) { + return Some(true); + } + // Check if a `delegate` has a permission to spend the collection item. + item.map(|item| { + Item::::get(&collection, &item) + .map(|detail| detail.approvals.contains_key(&delegate)) + }) + .unwrap_or_default() + } } diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 348ec6b9..2ea5cd73 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -137,6 +137,9 @@ impl, I: 'static> Pallet { } } + let _ = + AccountBalance::::clear_prefix((collection,), collection_details.items, None); + let _ = Allowances::::clear_prefix((collection,), collection_details.items, None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index e9843b2e..036a63b7 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -69,6 +69,9 @@ impl, I: 'static> Pallet { } collection_details.items.saturating_inc(); + AccountBalance::::mutate((collection, &mint_to), |balance| { + balance.saturating_inc(); + }); let collection_config = Self::get_collection_config(&collection)?; let deposit_amount = match collection_config @@ -263,6 +266,9 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); + AccountBalance::::mutate((collection, &owner), |balance| { + balance.saturating_dec(); + }); if remove_config { ItemConfigOf::::remove(&collection, &item); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index b7223a7c..0aa83fe8 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -87,6 +87,14 @@ impl, I: 'static> Pallet { // Perform the transfer with custom details using the provided closure. with_details(&collection_details, &mut details)?; + // Update account balances. + AccountBalance::::mutate((collection, &details.owner), |balance| { + balance.saturating_dec(); + }); + AccountBalance::::mutate((collection, &dest), |balance| { + balance.saturating_dec(); + }); + // Update account ownership information. Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 89bfb963..9f4d3aed 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -402,6 +402,36 @@ pub mod pallet { pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + /// Number of collection items that accounts own. + #[pallet::storage] + pub type AccountBalance, I: 'static = ()> = StorageNMap< + _, + ( + // Collection Id. + NMapKey, + // Collection Owner Id. + NMapKey, + ), + u32, + ValueQuery, + >; + + /// Permission for the delegate to transfer all owner's items within a collection. + #[pallet::storage] + pub type Allowances, I: 'static = ()> = StorageNMap< + _, + ( + // Collection ID. + NMapKey, + // Collection Owner Id. + NMapKey, + // Delegate Id. + NMapKey, + ), + bool, + ValueQuery, + >; + /// Config of an item. #[pallet::storage] pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< @@ -460,7 +490,7 @@ pub mod pallet { /// a `delegate`. TransferApproved { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, deadline: Option>, @@ -469,7 +499,7 @@ pub mod pallet { /// `collection` was cancelled by its `owner`. ApprovalCancelled { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, }, @@ -1931,6 +1961,60 @@ pub mod pallet { Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; Self::do_set_attributes_pre_signed(origin, data, signer) } + + /// Approve an item to be transferred by a delegated third-party account. + /// + /// 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. + /// - `delegate`: The account to delegate permission to transfer the item. + /// + /// Emits `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(39)] + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub fn approve_transfer_collection( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `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 collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to loose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(40)] + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub fn cancel_approval_collection( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate) + } } } From 9e5e808f45906a1126e8a17a4196edd12c0c3016 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:30:14 +0700 Subject: [PATCH 07/79] feat: check allowance --- pallets/api/src/nonfungibles/mod.rs | 102 +++++++------------------ pallets/nfts/src/features/approvals.rs | 28 ++++--- pallets/nfts/src/features/transfer.rs | 6 +- pallets/nfts/src/lib.rs | 94 ++++++----------------- pallets/nfts/src/tests.rs | 93 +++++++++++++--------- 5 files changed, 128 insertions(+), 195 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index cf2b14e7..bc728ac8 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -62,7 +62,7 @@ pub mod pallet { pub enum ReadResult { OwnerOf(Option>), CollectionOwner(Option>), - TotalSupply(u32), + TotalSupply(u128), BalanceOf(u32), Collection(Option>), Item(Option>), @@ -99,25 +99,18 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Event emitted when allowance by `owner` to `spender` canceled. - CancelApproval { - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - /// The beneficiary of the allowance. - spender: AccountIdOf, - }, - /// Event emitted when allowance by `owner` to `spender` changes. + /// Event emitted when allowance by `owner` to `operator` changes. Approval { /// The collection ID. collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, /// The owner providing the allowance. owner: AccountIdOf, /// The beneficiary of the allowance. - spender: AccountIdOf, + operator: AccountIdOf, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option>, + /// Whether allowance is set or removed. + approved: bool, }, /// Event emitted when new item is minted to the account. Mint { @@ -150,12 +143,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Create a new non-fungible token to the collection. - /// - /// # Parameters - /// - `to` - The owner of the collection item. - /// - `collection` - The collection ID. - /// - `item` - The item ID. #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -169,11 +156,6 @@ pub mod pallet { Ok(()) } - /// Destroy a new non-fungible token to the collection. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. #[pallet::call_index(1)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( @@ -186,12 +168,6 @@ pub mod pallet { Ok(()) } - /// Transfer a token from one account to the another account. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `to` - The recipient account. #[pallet::call_index(2)] #[pallet::weight(NftsWeightInfoOf::::transfer())] pub fn transfer( @@ -206,53 +182,33 @@ pub mod pallet { Ok(()) } - /// Delegate a permission to perform actions on the collection item to an account. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `spender` - The account that is allowed to transfer the collection item. #[pallet::call_index(3)] #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] pub fn approve( origin: OriginFor, collection: CollectionIdOf, - item: ItemIdOf, - spender: AccountIdOf, + item: Option>, + operator: AccountIdOf, + approved: bool, ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; - NftsOf::::approve_transfer( - origin, - collection, - item, - T::Lookup::unlookup(spender.clone()), - None, - )?; - Self::deposit_event(Event::Approval { collection, item, spender, owner }); - Ok(()) - } - - /// Cancel one of the transfer approvals for a specific item. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `spender` - The account that is revoked permission to transfer the collection item. - #[pallet::call_index(4)] - #[pallet::weight(NftsWeightInfoOf::::cancel_approval())] - pub fn cancel_approval( - origin: OriginFor, - collection: CollectionIdOf, - item: ItemIdOf, - spender: AccountIdOf, - ) -> DispatchResult { - NftsOf::::cancel_approval( - origin, - collection, - item, - T::Lookup::unlookup(spender.clone()), - )?; - Self::deposit_event(Event::CancelApproval { collection, item, spender }); + if approved { + NftsOf::::approve_transfer( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + None, + )?; + } else { + NftsOf::::cancel_approval( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + )?; + } + Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); Ok(()) } } @@ -284,14 +240,14 @@ pub mod pallet { CollectionOwner(collection) => ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default(), + NftsOf::::collection_items(collection).unwrap_or_default().into(), ), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( - NftsOf::::allowance(collection, item, owner, operator).unwrap_or(false), + NftsOf::::allowance(&collection, &item, &owner, &operator).is_ok(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index f626a9fe..979035fa 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -232,20 +232,26 @@ impl, I: 'static> Pallet { } pub fn allowance( - collection: T::CollectionId, - item: Option, - owner: T::AccountId, - delegate: T::AccountId, - ) -> Option { + collection: &T::CollectionId, + item: &Option, + owner: &T::AccountId, + delegate: &T::AccountId, + ) -> Result<(), DispatchError> { // Check if a `delegate` has a permission to spend the collection. if Allowances::::get((&collection, &owner, &delegate)) { - return Some(true); + return Ok(()); } // Check if a `delegate` has a permission to spend the collection item. - item.map(|item| { - Item::::get(&collection, &item) - .map(|detail| detail.approvals.contains_key(&delegate)) - }) - .unwrap_or_default() + if let Some(item) = item { + let details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let deadline = details.approvals.get(&delegate).ok_or(Error::::NoPermission)?; + if let Some(d) = deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *d, Error::::ApprovalExpired); + } + }; + Ok(()) } } diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index 0aa83fe8..04d9f4fe 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -92,7 +92,7 @@ impl, I: 'static> Pallet { balance.saturating_dec(); }); AccountBalance::::mutate((collection, &dest), |balance| { - balance.saturating_dec(); + balance.saturating_inc(); }); // Update account ownership information. @@ -145,7 +145,7 @@ impl, I: 'static> Pallet { // Check if the `origin` is the current owner of the collection. ensure!(origin == details.owner, Error::::NoPermission); if details.owner == new_owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. @@ -220,7 +220,7 @@ impl, I: 'static> Pallet { Collection::::try_mutate(collection, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; if details.owner == owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 9f4d3aed..14689293 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -1060,12 +1060,7 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { - 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); - } + Self::allowance(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) @@ -1120,10 +1115,10 @@ pub mod pallet { 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 + continue; } } else { - continue + continue; } details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); @@ -1322,7 +1317,7 @@ pub mod pallet { pub fn approve_transfer( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, maybe_deadline: Option>, ) -> DispatchResult { @@ -1330,13 +1325,17 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; - Self::do_approve_transfer( - maybe_check_origin, - collection, - item, - delegate, - maybe_deadline, - ) + match maybe_item { + Some(item) => Self::do_approve_transfer( + maybe_check_origin, + collection, + item, + delegate, + maybe_deadline, + ), + None => + Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate), + } } /// Cancel one of the transfer approvals for a specific item. @@ -1358,14 +1357,19 @@ pub mod pallet { pub fn cancel_approval( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) + match maybe_item { + Some(item) => + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate), + None => + Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate), + } } /// Cancel all the approvals of a specific item. @@ -1961,60 +1965,6 @@ pub mod pallet { Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; Self::do_set_attributes_pre_signed(origin, data, signer) } - - /// Approve an item to be transferred by a delegated third-party account. - /// - /// 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. - /// - `delegate`: The account to delegate permission to transfer the item. - /// - /// Emits `TransferApproved` on success. - /// - /// Weight: `O(1)` - #[pallet::call_index(39)] - #[pallet::weight(T::WeightInfo::approve_transfer())] - pub fn approve_transfer_collection( - origin: OriginFor, - collection: T::CollectionId, - delegate: AccountIdLookupOf, - ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate) - } - - /// Cancel one of the transfer approvals for a specific item. - /// - /// Origin must be either: - /// - the `Force` origin; - /// - `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 collection of whose approval will be cancelled. - /// - `delegate`: The account that is going to loose their approval. - /// - /// Emits `ApprovalCancelled` on success. - /// - /// Weight: `O(1)` - #[pallet::call_index(40)] - #[pallet::weight(T::WeightInfo::cancel_approval())] - pub fn cancel_approval_collection( - origin: OriginFor, - collection: T::CollectionId, - delegate: AccountIdLookupOf, - ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate) - } } } diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 44f2f32a..4002c48e 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -487,7 +487,7 @@ fn transfer_should_work() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(3)), 0, - 42, + Some(42), account(2), None )); @@ -1777,7 +1777,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); @@ -1791,7 +1791,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(2), None )); @@ -1819,7 +1819,7 @@ fn approval_lifecycle_works() { Nfts::approve_transfer( RuntimeOrigin::signed(account(1)), collection_id, - 1, + Some(1), account(2), None ), @@ -1847,30 +1847,35 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, Some(42), account(3)), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(3)), Error::::NotDelegate ); @@ -1887,18 +1892,23 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, Some(42), account(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(RuntimeOrigin::signed(account(5)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(5)), + 0, + Some(42), + account(3) + )); assert_eq!(approvals(0, 69), vec![]); }); } @@ -1924,21 +1934,21 @@ fn approving_multiple_accounts_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(5), Some(2) )); @@ -1979,14 +1989,20 @@ fn approvals_limit_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(i), None )); } // the limit is 10 assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), + Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(14), + None + ), Error::::ReachedApprovalLimit ); }); @@ -2015,7 +2031,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); @@ -2034,7 +2050,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(6), Some(4) )); @@ -2063,26 +2079,31 @@ fn cancel_approval_works_with_admin() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2107,26 +2128,26 @@ fn cancel_approval_works_with_force() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2151,14 +2172,14 @@ fn clear_all_transfer_approvals_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); @@ -3169,7 +3190,7 @@ fn pallet_level_feature_flags_should_work() { Nfts::approve_transfer( RuntimeOrigin::signed(user_id.clone()), collection_id, - item_id, + Some(item_id), account(2), None ), From fe1f4484ff45259e95bcf473c4fb4d0ca5cf8967 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:03:43 +0700 Subject: [PATCH 08/79] test(nfts): check account balance & test total supply --- pallets/nfts/src/tests.rs | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 4002c48e..f75c25d8 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -164,6 +164,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 42)]); assert_ok!(Nfts::force_create( @@ -173,6 +174,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); + assert_eq!(AccountBalance::::get((1, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } @@ -204,6 +206,7 @@ fn lifecycle_should_work() { account(10), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(10))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 6); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), @@ -212,8 +215,10 @@ fn lifecycle_should_work() { account(20), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(20))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 7); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -221,6 +226,8 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); + assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); @@ -238,6 +245,7 @@ fn lifecycle_should_work() { Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), Error::::CollectionNotEmpty ); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(account(1)), @@ -248,7 +256,9 @@ fn lifecycle_should_work() { bvec![0], )); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); + assert_eq!(AccountBalance::::get((0, account(10))), 0); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); + assert_eq!(AccountBalance::::get((0, account(10))), 0); assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); let w = Nfts::get_destroy_witness(&0).unwrap(); @@ -256,6 +266,7 @@ fn lifecycle_should_work() { assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); @@ -305,6 +316,7 @@ fn destroy_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_noop!( Nfts::destroy( RuntimeOrigin::signed(account(1)), @@ -323,6 +335,7 @@ fn destroy_should_work() { 0, Nfts::get_destroy_witness(&0).unwrap() )); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert!(!ItemConfigOf::::contains_key(0, 42)); assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); @@ -337,6 +350,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -402,6 +416,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -440,6 +455,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); + assert_eq!(AccountBalance::::get((1, account(2))), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -478,6 +494,8 @@ fn transfer_should_work() { )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(AccountBalance::::get((0, account(2))), 0); + assert_eq!(AccountBalance::::get((0, account(3))), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -492,7 +510,9 @@ fn transfer_should_work() { None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - + assert_eq!(AccountBalance::::get((0, account(2))), 0); + assert_eq!(AccountBalance::::get((0, account(3))), 0); + assert_eq!(AccountBalance::::get((0, account(4))), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1746,15 +1766,17 @@ fn burn_works() { account(5), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(5))), 2); assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), Error::::NoPermission ); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); + assert_eq!(AccountBalance::::get((0, account(5))), 1); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); + assert_eq!(AccountBalance::::get((0, account(5))), 0); assert_eq!(Balances::reserved_balance(account(1)), 0); }); } @@ -2209,6 +2231,36 @@ fn clear_all_transfer_approvals_works() { }); } +#[test] +fn total_supply_should_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let total_items = 10; + + // no collection. + assert_eq!(Nfts::collection_items(collection_id), None); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + // mint items and validate the total supply. + (0..total_items).into_iter().for_each(|i| { + assert_ok!(Nfts::force_mint( + RuntimeOrigin::root(), + collection_id, + i, + user_id.clone(), + ItemConfig::default() + )); + }); + assert_eq!(Nfts::collection_items(collection_id), Some(total_items)); + }); +} + #[test] fn max_supply_should_work() { new_test_ext().execute_with(|| { @@ -2545,6 +2597,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -2912,6 +2965,8 @@ fn claim_swap_should_work() { default_item_config(), )); + assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1.clone()), collection_id, @@ -3002,6 +3057,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); + assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); From 709398cd271d57e6d7698d19b4a7347234eb2313 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:39:53 +0700 Subject: [PATCH 09/79] test(nfts): allowance works --- pallets/api/src/nonfungibles/mod.rs | 2 +- pallets/nfts/src/features/approvals.rs | 8 +++- pallets/nfts/src/lib.rs | 2 +- pallets/nfts/src/tests.rs | 57 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index bc728ac8..4b6558e9 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -247,7 +247,7 @@ pub mod pallet { Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( - NftsOf::::allowance(&collection, &item, &owner, &operator).is_ok(), + NftsOf::::check_allowance(&collection, &item, &owner, &operator).is_ok(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 979035fa..89b8fc6a 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -231,7 +231,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn allowance( + pub fn check_allowance( collection: &T::CollectionId, item: &Option, owner: &T::AccountId, @@ -239,6 +239,9 @@ impl, I: 'static> Pallet { ) -> Result<(), DispatchError> { // Check if a `delegate` has a permission to spend the collection. if Allowances::::get((&collection, &owner, &delegate)) { + if let Some(item) = item { + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + }; return Ok(()); } // Check if a `delegate` has a permission to spend the collection item. @@ -251,7 +254,8 @@ impl, I: 'static> Pallet { let block_number = frame_system::Pallet::::block_number(); ensure!(block_number <= *d, Error::::ApprovalExpired); } + return Ok(()); }; - Ok(()) + Err(Error::::NoPermission.into()) } } diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 14689293..c7d826b5 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -1060,7 +1060,7 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { - Self::allowance(&collection, &Some(item), &details.owner, &origin)?; + Self::check_allowance(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index f75c25d8..5ad13e6e 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1850,6 +1850,63 @@ fn approval_lifecycle_works() { }); } +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(2), + None + )); + + // collection transfer approved. + assert_noop!( + Nfts::check_allowance(&0, &Some(43), &account(1), &account(2)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::check_allowance(&0, &Some(42), &account(1), &account(3)), + Error::::NoPermission + ); + assert_ok!(Nfts::check_allowance(&0, &None, &account(1), &account(2))); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(1), &account(2))); + + // collection item transfer approved. + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3), + None + )); + + assert_noop!( + Nfts::check_allowance(&0, &Some(43), &account(2), &account(3)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::check_allowance(&0, &Some(42), &account(2), &account(4)), + Error::::NoPermission + ); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(2), &account(3))); + }); +} + #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { From 35a2839d2a882aa5acd0c1a897b3a65a781360e3 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:40:34 +0700 Subject: [PATCH 10/79] refactor(test): check_allowance_works --- pallets/nfts/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 5ad13e6e..9c3cce0d 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1851,7 +1851,7 @@ fn approval_lifecycle_works() { } #[test] -fn allowance_works() { +fn check_allowance_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), From 501a3c7ab7edf6e280c7e652ecc9cd70ccf9368e Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:18:49 +0700 Subject: [PATCH 11/79] fix(nfts): cancel / approve collection transfer --- pallets/nfts/src/features/approvals.rs | 27 ++++------ pallets/nfts/src/tests.rs | 72 ++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 89b8fc6a..05696fee 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -180,27 +180,24 @@ impl, I: 'static> Pallet { Self::is_pallet_feature_enabled(PalletFeature::Approvals), Error::::MethodDisabled ); - let collection_owner = - Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; - + if !Collection::::contains_key(collection) { + return Err(Error::::UnknownCollection.into()); + } 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 { - ensure!(check_origin == collection_owner, Error::::NoPermission); - } - - Allowances::::mutate((&collection, &collection_owner, &delegate), |allowance| { + let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; + Allowances::::mutate((&collection, &origin, &delegate), |allowance| { *allowance = true; }); Self::deposit_event(Event::TransferApproved { collection, item: None, - owner: collection_owner, + owner: origin, delegate, deadline: None, }); @@ -212,18 +209,16 @@ impl, I: 'static> Pallet { collection: T::CollectionId, delegate: T::AccountId, ) -> DispatchResult { - let collection_owner = - Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_origin) = maybe_check_origin { - ensure!(check_origin == collection_owner, Error::::NoPermission); + if !Collection::::contains_key(collection) { + return Err(Error::::UnknownCollection.into()); } - Allowances::::remove((&collection, &collection_owner, &delegate)); + let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; + Allowances::::remove((&collection, &origin, &delegate)); Self::deposit_event(Event::ApprovalCancelled { collection, - owner: collection_owner, + owner: origin, item: None, delegate, }); diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 9c3cce0d..07f2a74d 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1876,12 +1876,12 @@ fn check_allowance_works() { // collection transfer approved. assert_noop!( - Nfts::check_allowance(&0, &Some(43), &account(1), &account(2)), - Error::::UnknownItem + Nfts::check_allowance(&1, &None, &account(1), &account(2)), + Error::::NoPermission ); assert_noop!( - Nfts::check_allowance(&0, &Some(42), &account(1), &account(3)), - Error::::NoPermission + Nfts::check_allowance(&1, &Some(43), &account(1), &account(2)), + Error::::UnknownItem ); assert_ok!(Nfts::check_allowance(&0, &None, &account(1), &account(2))); assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(1), &account(2))); @@ -1992,6 +1992,43 @@ fn cancel_approval_works() { }); } +#[test] +fn cancel_approval_collection_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + None, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, None, account(3)), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, None, account(3))); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), + Error::::NoPermission + ); + }); +} + #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { @@ -2087,6 +2124,33 @@ fn approvals_limit_works() { }); } +#[test] +fn approval_collection_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(3), + None + )); + assert_eq!(Allowances::::get((0, account(1), account(3))), true); + }); +} + #[test] fn approval_deadline_works() { new_test_ext().execute_with(|| { From cebd059108d2f0f6a36b1c1121a451e50d643298 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:17:38 +0700 Subject: [PATCH 12/79] test(nfts): cancel approval collection --- pallets/nfts/src/tests.rs | 49 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 07f2a74d..4fbec4de 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -2021,6 +2021,13 @@ fn cancel_approval_collection_works() { ); assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, None, account(3))); + assert!(events().contains(&Event::::ApprovalCancelled { + collection: 0, + item: None, + owner: account(2), + delegate: account(3) + })); + assert_eq!(Allowances::::get((0, account(2), account(3))), false); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), @@ -2127,6 +2134,11 @@ fn approvals_limit_works() { #[test] fn approval_collection_works() { new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), account(1), @@ -2140,14 +2152,47 @@ fn approval_collection_works() { default_item_config() )); - assert_ok!(Nfts::approve_transfer( + // Error::ItemsNonTransferable. + assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(account(1)), + 1, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), + Error::::ItemsNonTransferable + ); + + // Error::MethodDisabled. + Features::set(&PalletFeatures::from_disabled(PalletFeature::Approvals.into())); + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), + Error::::MethodDisabled + ); + Features::set(&PalletFeatures::all_enabled()); + + // Error::UnknownCollection. + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 2, None, account(3), None), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), 0, None, account(3), None )); - assert_eq!(Allowances::::get((0, account(1), account(3))), true); + assert!(events().contains(&Event::::TransferApproved { + collection: 0, + item: None, + owner: account(2), + delegate: account(3), + deadline: None + })); + assert_eq!(Allowances::::get((0, account(2), account(3))), true); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); }); } From d23592877ce50369febfa08170ba3bbf78b89dbd Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:23:23 +0700 Subject: [PATCH 13/79] test(nfts): update approval test cases --- pallets/nfts/src/tests.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 4fbec4de..10c95bd6 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1993,7 +1993,7 @@ fn cancel_approval_works() { } #[test] -fn cancel_approval_collection_works() { +fn cancel_approval_collection_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), @@ -2132,7 +2132,7 @@ fn approvals_limit_works() { } #[test] -fn approval_collection_works() { +fn approval_collection_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), @@ -2163,14 +2163,6 @@ fn approval_collection_works() { Error::::ItemsNonTransferable ); - // Error::MethodDisabled. - Features::set(&PalletFeatures::from_disabled(PalletFeature::Approvals.into())); - assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), - Error::::MethodDisabled - ); - Features::set(&PalletFeatures::all_enabled()); - // Error::UnknownCollection. assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 2, None, account(3), None), From 901514681f0875fa43d6551e6a5e1c07d4e4536f Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:18:20 +0700 Subject: [PATCH 14/79] feat(nfts): get attribute read method --- pallets/api/src/nonfungibles/mod.rs | 105 ++++++++++++++++++-------- pallets/api/src/nonfungibles/types.rs | 6 +- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 4b6558e9..83d23585 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -14,10 +14,11 @@ mod types; pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; + use pallet_nfts::MintWitness; use sp_std::vec::Vec; use types::{ - AccountIdOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, NftsOf, - NftsWeightInfoOf, + AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, + ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, }; use super::*; @@ -54,6 +55,14 @@ pub mod pallet { operator: AccountIdOf, item: Option>, }, + /// Returns the attribute of `item` for the given `key`. + #[codec(index = 6)] + GetAttribute { + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + }, } /// Results of state reads for the non-fungibles API. @@ -67,6 +76,7 @@ pub mod pallet { Collection(Option>), Item(Option>), Allowance(bool), + GetAttribute(Option>), } impl ReadResult { @@ -81,6 +91,7 @@ pub mod pallet { Collection(result) => result.encode(), Item(result) => result.encode(), Allowance(result) => result.encode(), + GetAttribute(result) => result.encode(), } } } @@ -103,46 +114,46 @@ pub mod pallet { Approval { /// The collection ID. collection: CollectionIdOf, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option>, /// The owner providing the allowance. owner: AccountIdOf, /// The beneficiary of the allowance. operator: AccountIdOf, - /// The item which is (dis)approved. `None` for all owner's items. - item: Option>, /// Whether allowance is set or removed. approved: bool, }, - /// Event emitted when new item is minted to the account. - Mint { - /// The owner of the item. - to: AccountIdOf, - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - }, - /// Event emitted when item is burned. - Burn { - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - }, - /// Event emitted when an item transfer occurs. + /// Event emitted when a token transfer occurs. + // Differing style: event name abides by the PSP22 standard. Transfer { /// The collection ID. collection: CollectionIdOf, - /// the item ID. + /// The collection item ID. item: ItemIdOf, - /// The source of the transfer. - from: AccountIdOf, - /// The recipient of the transfer. - to: AccountIdOf, + /// The source of the transfer. `None` when minting. + from: Option>, + /// The recipient of the transfer. `None` when burning. + to: Option>, + /// The amount minted. + value: Option>, }, } #[pallet::call] impl Pallet { + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + + // TODO: Fix weight + #[pallet::call_index(21)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn destroy(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -150,9 +161,24 @@ pub mod pallet { to: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf, + mint_price: Option>, ) -> DispatchResult { - NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to.clone()), None)?; - Self::deposit_event(Event::Mint { to, collection, item }); + let account = ensure_signed(origin.clone())?; + let witness_data = MintWitness { mint_price, owned_item: Some(item) }; + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness_data), + )?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: None, + to: Some(account), + value: mint_price, + }); Ok(()) } @@ -163,8 +189,15 @@ pub mod pallet { collection: CollectionIdOf, item: ItemIdOf, ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Burn { collection, item }); + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(account), + to: None, + value: None, + }); Ok(()) } @@ -178,12 +211,18 @@ pub mod pallet { ) -> DispatchResult { let from = ensure_signed(origin.clone())?; NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; - Self::deposit_event(Event::Transfer { from, to, collection, item }); + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(from), + to: Some(to), + value: None, + }); Ok(()) } #[pallet::call_index(3)] - #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] + #[pallet::weight(NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::cancel_approval())] pub fn approve( origin: OriginFor, collection: CollectionIdOf, @@ -252,6 +291,10 @@ pub mod pallet { BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), ), + GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( + pallet_nfts::Attribute::::get((collection, item, namespace, key)) + .map(|attribute| attribute.0), + ), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index f81ea535..da949171 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,6 +1,6 @@ use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; +use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails}; use sp_runtime::BoundedBTreeMap; // Type aliases for pallet-nfts. @@ -15,14 +15,16 @@ pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; pub(super) type ItemIdOf = as Inspect<::AccountId>>::ItemId; -type ApprovalsOf = BoundedBTreeMap< +pub(super) type ApprovalsOf = BoundedBTreeMap< AccountIdOf, Option>, ::ApprovalsLimit, >; +pub(super) type ItemPriceOf = BalanceOf; // TODO: Multi-instances. pub(super) type ItemDepositOf = ItemDeposit, AccountIdOf>; pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; pub(super) type ItemDetailsFor = ItemDetails, ItemDepositOf, ApprovalsOf>; +pub(super) type AttributeNamespaceOf = AttributeNamespace>; From 2ad1764912261fd1aa0d66f5940bb06bd5b45af4 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:45:16 +0700 Subject: [PATCH 15/79] feat(nonfungibles + nfts): add new methods to manage attributes --- pallets/api/src/nonfungibles/mod.rs | 198 ++++++++++++++---- pallets/api/src/nonfungibles/types.rs | 17 +- pallets/nfts/src/features/approvals.rs | 4 +- .../src/features/create_delete_collection.rs | 4 +- .../nfts/src/features/create_delete_item.rs | 4 +- pallets/nfts/src/features/transfer.rs | 4 +- pallets/nfts/src/lib.rs | 18 +- pallets/nfts/src/tests.rs | 66 +++--- 8 files changed, 224 insertions(+), 91 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 83d23585..efef2386 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -14,11 +14,14 @@ mod types; pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; - use pallet_nfts::MintWitness; + use pallet_nfts::{ + CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, + MintSettings, MintWitness, + }; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, }; use super::*; @@ -29,24 +32,12 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// Returns the owner of an item. - #[codec(index = 0)] - OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Returns the owner of a collection. - #[codec(index = 1)] - CollectionOwner(CollectionIdOf), /// Number of items existing in a concrete collection. #[codec(index = 2)] TotalSupply(CollectionIdOf), /// Returns the total number of items in the collection owned by the account. #[codec(index = 3)] BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, - /// Returns the details of a collection. - #[codec(index = 4)] - Collection(CollectionIdOf), - /// Returns the details of an item. - #[codec(index = 5)] - Item { collection: CollectionIdOf, item: ItemIdOf }, /// Whether a spender is allowed to transfer an item or items from owner. #[codec(index = 6)] Allowance { @@ -55,6 +46,9 @@ pub mod pallet { operator: AccountIdOf, item: Option>, }, + /// Returns the owner of an item. + #[codec(index = 0)] + OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, /// Returns the attribute of `item` for the given `key`. #[codec(index = 6)] GetAttribute { @@ -63,20 +57,29 @@ pub mod pallet { namespace: AttributeNamespaceOf, key: BoundedVec, }, + /// Returns the owner of a collection. + #[codec(index = 1)] + CollectionOwner(CollectionIdOf), + /// Returns the details of a collection. + #[codec(index = 4)] + Collection(CollectionIdOf), + /// Returns the details of an item. + #[codec(index = 5)] + Item { collection: CollectionIdOf, item: ItemIdOf }, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - OwnerOf(Option>), - CollectionOwner(Option>), TotalSupply(u128), BalanceOf(u32), - Collection(Option>), - Item(Option>), Allowance(bool), + OwnerOf(Option>), GetAttribute(Option>), + CollectionOwner(Option>), + Collection(Option>), + Item(Option>), } impl ReadResult { @@ -141,19 +144,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::call_index(20)] - #[pallet::weight(NftsWeightInfoOf::::create())] - pub fn create(_origin: OriginFor) -> DispatchResult { - Ok(()) - } - - // TODO: Fix weight - #[pallet::call_index(21)] - #[pallet::weight(NftsWeightInfoOf::::create())] - pub fn destroy(_origin: OriginFor) -> DispatchResult { - Ok(()) - } - #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -250,6 +240,131 @@ pub mod pallet { Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); Ok(()) } + + #[pallet::call_index(4)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create( + origin: OriginFor, + admin: AccountIdOf, + config: CreateCollectionConfigFor, + ) -> DispatchResult { + let collection_config = CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: config.max_supply, + mint_settings: MintSettings { + mint_type: config.mint_type, + start_block: config.start_block, + end_block: config.end_block, + ..MintSettings::default() + }, + }; + NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), collection_config)?; + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(NftsWeightInfoOf::::destroy( + witness.item_metadatas, + witness.item_configs, + witness.attributes, + ))] + pub fn destroy( + origin: OriginFor, + collection: CollectionIdOf, + witness: DestroyWitness, + ) -> DispatchResultWithPostInfo { + NftsOf::::destroy(origin, collection, witness) + } + + #[pallet::call_index(6)] + #[pallet::weight(NftsWeightInfoOf::::set_attribute())] + pub fn set_attribute( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + NftsOf::::set_attribute(origin, collection, item, namespace, key, value) + } + + #[pallet::call_index(7)] + #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] + pub fn clear_attribute( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + ) -> DispatchResult { + NftsOf::::clear_attribute(origin, collection, item, namespace, key) + } + + #[pallet::call_index(8)] + #[pallet::weight(NftsWeightInfoOf::::set_metadata())] + pub fn set_metadata( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + data: BoundedVec, + ) -> DispatchResult { + NftsOf::::set_metadata(origin, collection, item, data) + } + + #[pallet::call_index(9)] + #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::clear_metadata(origin, collection, item) + } + + #[pallet::call_index(10)] + #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] + pub fn approve_item_attributes( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + delegate: AccountIdOf, + ) -> DispatchResult { + NftsOf::::approve_item_attributes( + origin, + collection, + item, + T::Lookup::unlookup(delegate.clone()), + ) + } + + #[pallet::call_index(11)] + #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] + pub fn cancel_item_attributes_approval( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + delegate: AccountIdOf, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + NftsOf::::cancel_item_attributes_approval( + origin, + collection, + item, + T::Lookup::unlookup(delegate.clone()), + witness, + ) + } + + #[pallet::call_index(12)] + #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] + pub fn set_max_supply( + origin: OriginFor, + collection: CollectionIdOf, + max_supply: u32, + ) -> DispatchResult { + NftsOf::::set_collection_max_supply(origin, collection, max_supply) + } } impl crate::Read for Pallet { @@ -274,27 +389,26 @@ pub mod pallet { fn read(value: Self::Read) -> Self::Result { use Read::*; match value { - OwnerOf { collection, item } => - ReadResult::OwnerOf(NftsOf::::owner(collection, item)), - CollectionOwner(collection) => - ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), TotalSupply(collection) => ReadResult::TotalSupply( NftsOf::::collection_items(collection).unwrap_or_default().into(), ), - Collection(collection) => - ReadResult::Collection(pallet_nfts::Collection::::get(collection)), - Item { collection, item } => - ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + BalanceOf { collection, owner } => + ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( NftsOf::::check_allowance(&collection, &item, &owner, &operator).is_ok(), ), - BalanceOf { collection, owner } => ReadResult::BalanceOf( - pallet_nfts::AccountBalance::::get((collection, owner)), - ), + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( pallet_nfts::Attribute::::get((collection, item, namespace, key)) .map(|attribute| attribute.0), ), + CollectionOwner(collection) => + ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), + Collection(collection) => + ReadResult::Collection(pallet_nfts::Collection::::get(collection)), + Item { collection, item } => + ReadResult::Item(pallet_nfts::Item::::get(collection, item)), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index da949171..8c53ffd3 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,7 +1,9 @@ +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails}; -use sp_runtime::BoundedBTreeMap; +use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; +use scale_info::TypeInfo; +use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; @@ -28,3 +30,14 @@ pub(super) type CollectionDetailsFor = pub(super) type ItemDetailsFor = ItemDetails, ItemDepositOf, ApprovalsOf>; pub(super) type AttributeNamespaceOf = AttributeNamespace>; +pub(super) type CreateCollectionConfigFor = + CreateCollectionConfig, BlockNumberFor, CollectionIdOf>; + +#[derive(Clone, Copy, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CreateCollectionConfig { + pub max_supply: Option, + pub mint_type: MintType, + pub price: Option, + pub start_block: Option, + pub end_block: Option, +} diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 05696fee..6d71c1a2 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -171,7 +171,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub(crate) fn do_approve_transfer_collection( + pub(crate) fn do_approve_collection( maybe_check_origin: Option, collection: T::CollectionId, delegate: T::AccountId, @@ -204,7 +204,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub(crate) fn do_cancel_approval_collection( + pub(crate) fn do_cancel_collection( maybe_check_origin: Option, collection: T::CollectionId, delegate: T::AccountId, diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 2ea5cd73..b7efd03a 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -137,8 +137,10 @@ impl, I: 'static> Pallet { } } + // TODO: Do we need another storage item to keep track of number of holders of a + // collection let _ = - AccountBalance::::clear_prefix((collection,), collection_details.items, None); + AccountBalance::::clear_prefix(collection, collection_details.items, None); let _ = Allowances::::clear_prefix((collection,), collection_details.items, None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index 036a63b7..cc29f8da 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -69,7 +69,7 @@ impl, I: 'static> Pallet { } collection_details.items.saturating_inc(); - AccountBalance::::mutate((collection, &mint_to), |balance| { + AccountBalance::::mutate(collection, &mint_to, |balance| { balance.saturating_inc(); }); @@ -266,7 +266,7 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); - AccountBalance::::mutate((collection, &owner), |balance| { + AccountBalance::::mutate(collection, &owner, |balance| { balance.saturating_dec(); }); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index 04d9f4fe..3b25b014 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -88,10 +88,10 @@ impl, I: 'static> Pallet { with_details(&collection_details, &mut details)?; // Update account balances. - AccountBalance::::mutate((collection, &details.owner), |balance| { + AccountBalance::::mutate(collection, &details.owner, |balance| { balance.saturating_dec(); }); - AccountBalance::::mutate((collection, &dest), |balance| { + AccountBalance::::mutate(collection, &dest, |balance| { balance.saturating_inc(); }); diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index c7d826b5..bc8b67b6 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -404,14 +404,12 @@ pub mod pallet { /// Number of collection items that accounts own. #[pallet::storage] - pub type AccountBalance, I: 'static = ()> = StorageNMap< + pub type AccountBalance, I: 'static = ()> = StorageDoubleMap< _, - ( - // Collection Id. - NMapKey, - // Collection Owner Id. - NMapKey, - ), + Twox64Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, u32, ValueQuery, >; @@ -1333,8 +1331,7 @@ pub mod pallet { delegate, maybe_deadline, ), - None => - Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate), + None => Self::do_approve_collection(maybe_check_origin, collection, delegate), } } @@ -1367,8 +1364,7 @@ pub mod pallet { match maybe_item { Some(item) => Self::do_cancel_approval(maybe_check_origin, collection, item, delegate), - None => - Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate), + None => Self::do_cancel_collection(maybe_check_origin, collection, delegate), } } diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 10c95bd6..397a715c 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -164,7 +164,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42)]); assert_ok!(Nfts::force_create( @@ -174,7 +174,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); - assert_eq!(AccountBalance::::get((1, account(1))), 1); + assert_eq!(AccountBalance::::get(1, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } @@ -206,7 +206,7 @@ fn lifecycle_should_work() { account(10), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(10))), 1); + assert_eq!(AccountBalance::::get(0, account(10)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 6); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), @@ -215,10 +215,10 @@ fn lifecycle_should_work() { account(20), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(20))), 1); + assert_eq!(AccountBalance::::get(0, account(20)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 7); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -226,8 +226,8 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); - assert_eq!(AccountBalance::::get((0, account(1))), 0); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 0); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); @@ -245,7 +245,7 @@ fn lifecycle_should_work() { Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), Error::::CollectionNotEmpty ); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(account(1)), @@ -256,9 +256,9 @@ fn lifecycle_should_work() { bvec![0], )); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); - assert_eq!(AccountBalance::::get((0, account(10))), 0); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); - assert_eq!(AccountBalance::::get((0, account(10))), 0); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); let w = Nfts::get_destroy_witness(&0).unwrap(); @@ -266,7 +266,7 @@ fn lifecycle_should_work() { assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); @@ -316,7 +316,14 @@ fn destroy_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 1); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + None, + account(3), + None + )); assert_noop!( Nfts::destroy( RuntimeOrigin::signed(account(1)), @@ -335,7 +342,8 @@ fn destroy_should_work() { 0, Nfts::get_destroy_witness(&0).unwrap() )); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::iter_prefix(0).count(), 0); + assert_eq!(Allowances::::iter_prefix((0,)).count(), 0); assert!(!ItemConfigOf::::contains_key(0, 42)); assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); @@ -350,7 +358,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -416,7 +424,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -455,7 +463,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); - assert_eq!(AccountBalance::::get((1, account(2))), 1); + assert_eq!(AccountBalance::::get(1, account(2)), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -494,8 +502,8 @@ fn transfer_should_work() { )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); - assert_eq!(AccountBalance::::get((0, account(2))), 0); - assert_eq!(AccountBalance::::get((0, account(3))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -510,9 +518,9 @@ fn transfer_should_work() { None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - assert_eq!(AccountBalance::::get((0, account(2))), 0); - assert_eq!(AccountBalance::::get((0, account(3))), 0); - assert_eq!(AccountBalance::::get((0, account(4))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 0); + assert_eq!(AccountBalance::::get(0, account(4)), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1766,7 +1774,7 @@ fn burn_works() { account(5), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(5))), 2); + assert_eq!(AccountBalance::::get(0, account(5)), 2); assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( @@ -1774,9 +1782,9 @@ fn burn_works() { Error::::NoPermission ); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); - assert_eq!(AccountBalance::::get((0, account(5))), 1); + assert_eq!(AccountBalance::::get(0, account(5)), 1); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); - assert_eq!(AccountBalance::::get((0, account(5))), 0); + assert_eq!(AccountBalance::::get(0, account(5)), 0); assert_eq!(Balances::reserved_balance(account(1)), 0); }); } @@ -2755,7 +2763,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 1); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -3123,8 +3131,8 @@ fn claim_swap_should_work() { default_item_config(), )); - assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1.clone()), collection_id, @@ -3215,8 +3223,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); - assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); From 48abce6cbffe276f4586f81da869c97078411410 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:04:01 +0700 Subject: [PATCH 16/79] refactor(nfts): toml lock file --- Cargo.lock | 20 ++++++++++---------- pallets/nfts/Cargo.toml | 18 ++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd6525c4..68ed3967 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7366,7 +7366,7 @@ dependencies = [ "log", "pallet-assets", "pallet-balances", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "parity-scale-codec", "pop-chain-extension", "scale-info", @@ -8240,39 +8240,39 @@ dependencies = [ [[package]] name = "pallet-nfts" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" +version = "0.1.0" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", - "sp-std", ] [[package]] name = "pallet-nfts" -version = "31.0.0" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", - "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-keystore", "sp-runtime", + "sp-std", ] [[package]] @@ -10857,7 +10857,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", @@ -11001,7 +11001,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index b5639547..1fcfe9dd 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -1,32 +1,30 @@ [package] authors.workspace = true -description = "FRAME NFTs pallet (polkadot v1.15.0)" +description = "NFTs pallet (polkadot v1.15.0) for Pop Network Runtimes" edition.workspace = true -homepage = "https://substrate.io" -license = "Apache-2.0" +license.workspace = true name = "pallet-nfts" readme = "README.md" -repository.workspace = true -version = "31.0.0" +version = "0.1.0" [package.metadata.docs.rs] targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] -codec = { workspace = true } -enumflags2 = { workspace = true } +codec.workspace = true +enumflags2.workspace = true frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true frame-system.workspace = true -log = { workspace = true } +log.workspace = true scale-info = { features = [ "derive" ], workspace = true } sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true [dev-dependencies] -pallet-balances = { default-features = true, workspace = true } -sp-keystore = { default-features = true, workspace = true } +pallet-balances.workspace = true +sp-keystore.workspace = true [features] default = [ "std" ] From 390ca7812bcee4db307dd5aa2b296935d720aa65 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:06:00 +0700 Subject: [PATCH 17/79] fix(pallet-api): propagate try-runtime feature --- pallets/api/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index 5d398724..31ccc076 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -60,5 +60,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-nfts/try-runtime", "sp-runtime/try-runtime", ] From 2653a058d91afaa4372daed6df3235c6d915dc33 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:36:29 +0700 Subject: [PATCH 18/79] fix(api/nonfungibles): unit tests --- pallets/api/src/nonfungibles/mod.rs | 4 +- pallets/api/src/nonfungibles/tests.rs | 407 ++++++++++++++------------ 2 files changed, 225 insertions(+), 186 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index efef2386..9655bbea 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -72,7 +72,7 @@ pub mod pallet { #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - TotalSupply(u128), + TotalSupply(u32), BalanceOf(u32), Allowance(bool), OwnerOf(Option>), @@ -390,7 +390,7 @@ pub mod pallet { use Read::*; match value { TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default().into(), + NftsOf::::collection_items(collection).unwrap_or_default(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 54d85516..d757508a 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,184 +1,223 @@ -// TODO -// use codec::Encode; -// use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; -// use frame_system::pallet_prelude::BlockNumberFor; -// use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; - -// use super::types::*; -// use crate::{ -// mock::*, -// nonfungibles::{Event, Read::*}, -// }; - -// const ITEM: u32 = 1; - -// #[test] -// fn mint_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let collection = create_collection(owner.clone()); -// // Successfully mint a new collection item. -// assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); -// System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); -// }); -// } - -// #[test] -// fn burn_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully burn an existing new collection item. -// assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); -// System::assert_last_event(Event::Burn { collection, item }.into()); -// }); -// } - -// #[test] -// fn transfer() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let dest = account(BOB); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully burn an existing new collection item. -// assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); -// System::assert_last_event( -// Event::Transfer { collection, item, from: owner, to: dest }.into(), -// ); -// }); -// } - -// #[test] -// fn approve_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully approve `spender` to transfer the collection item. -// assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); -// System::assert_last_event( -// Event::Approval { collection, item, owner, spender: spender.clone() }.into(), -// ); -// // Successfully transfer the item by the delegated account `spender`. -// assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); -// }); -// } - -// #[test] -// fn cancel_approval_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = -// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); -// // Successfully cancel the transfer approval of `spender` by `owner`. -// assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); -// // Failed to transfer the item by `spender` without permission. -// assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); -// }); -// } - -// #[test] -// fn owner_of_works() {} - -// #[test] -// fn collection_owner_works() { -// new_test_ext().execute_with(|| { -// let collection = create_collection(account(ALICE)); -// assert_eq!( -// NonFungibles::read_state(CollectionOwner(collection)), -// Nfts::collection_owner(collection).encode() -// ); -// }); -// } - -// #[test] -// fn total_supply_works() { -// new_test_ext().execute_with(|| { -// let (collection, _) = create_collection_mint(account(ALICE), ITEM); -// assert_eq!( -// NonFungibles::read_state(TotalSupply(collection)), -// (Nfts::items(&collection).count() as u8).encode() -// ); -// }); -// } - -// #[test] -// fn collection_works() { -// new_test_ext().execute_with(|| { -// let (collection, _) = create_collection_mint(account(ALICE), ITEM); -// assert_eq!( -// NonFungibles::read_state(Collection(collection)), -// pallet_nfts::Collection::::get(&collection).encode(), -// ); -// }); -// } - -// #[test] -// fn balance_of_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let (collection, _) = create_collection_mint(owner.clone(), ITEM); -// assert_eq!( -// NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), -// (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() -// ); -// }); -// } - -// #[test] -// fn allowance_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = -// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); -// assert_eq!( -// NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), -// super::Pallet::::allowance(collection, item, spender).encode() -// ); -// }); -// } - -// fn signed(account: AccountId) -> RuntimeOrigin { -// RuntimeOrigin::signed(account) -// } - -// fn create_collection_mint_and_approve( -// owner: AccountIdOf, -// item: ItemIdOf, -// spender: AccountIdOf, -// ) -> (u32, u32) { -// let (collection, item) = create_collection_mint(owner.clone(), item); -// assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); -// (collection, item) -// } - -// fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { -// let collection = create_collection(owner.clone()); -// assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); -// (collection, item) -// } - -// fn create_collection(owner: AccountIdOf) -> u32 { -// let next_id = next_collection_id(); -// assert_ok!(Nfts::create( -// signed(owner.clone()), -// owner.clone(), -// collection_config_with_all_settings_enabled() -// )); -// next_id -// } - -// fn next_collection_id() -> u32 { -// pallet_nfts::NextCollectionId::::get().unwrap_or_default() -// } - -// fn collection_config_with_all_settings_enabled( -// ) -> CollectionConfig, CollectionIdOf> { -// CollectionConfig { -// settings: CollectionSettings::all_enabled(), -// max_supply: None, -// mint_settings: MintSettings::default(), -// } -// } +use codec::Encode; +use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; + +use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; +use crate::{ + mock::*, + nonfungibles::{Event, Read::*}, + Read, +}; + +const ITEM: u32 = 1; + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let collection = create_collection(owner.clone()); + // Successfully mint a new collection item. + assert_ok!(NonFungibles::mint( + signed(owner.clone()), + owner.clone(), + collection, + ITEM, + None + )); + System::assert_last_event( + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), value: None } + .into(), + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: None, value: None }.into(), + ); + }); +} + +#[test] +fn transfer() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let dest = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), value: None } + .into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let operator = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully approve `spender` to transfer the collection item. + assert_ok!(NonFungibles::approve( + signed(owner.clone()), + collection, + Some(item), + operator.clone(), + true + )); + System::assert_last_event( + Event::Approval { + collection, + item: Some(item), + owner, + operator: operator.clone(), + approved: true, + } + .into(), + ); + // Successfully transfer the item by the delegated account `spender`. + assert_ok!(Nfts::transfer(signed(operator.clone()), collection, item, operator)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + // Successfully cancel the transfer approval of `spender` by `owner`. + assert_ok!(NonFungibles::approve( + signed(owner), + collection, + Some(item), + spender.clone(), + false + )); + // Failed to transfer the item by `spender` without permission. + assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + }); +} + +#[test] +fn owner_of_works() {} + +#[test] +fn collection_owner_works() { + new_test_ext().execute_with(|| { + let collection = create_collection(account(ALICE)); + assert_eq!( + NonFungibles::read(CollectionOwner(collection)).encode(), + Nfts::collection_owner(collection).encode() + ); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + Nfts::collection_items(collection).unwrap_or_default().encode() + ); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read(Collection(collection)).encode(), + pallet_nfts::Collection::::get(&collection).encode(), + ); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, _) = create_collection_mint(owner.clone(), ITEM); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: owner.clone() }).encode(), + AccountBalance::::get(collection, owner).encode() + ); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let operator = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, operator.clone()); + assert_eq!( + NonFungibles::read(Allowance { + collection, + item: Some(item), + owner: owner.clone(), + operator: operator.clone(), + }) + .encode(), + Nfts::check_allowance(&collection, &Some(item), &owner, &operator) + .is_ok() + .encode() + ); + }); +} + +fn signed(account: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account) +} + +fn create_collection_mint_and_approve( + owner: AccountIdOf, + item: ItemIdOf, + spender: AccountIdOf, +) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner.clone(), item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), spender, None)); + (collection, item) +} + +fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner.clone()); + assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); + (collection, item) +} + +fn create_collection(owner: AccountIdOf) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner.clone()), + owner.clone(), + collection_config_with_all_settings_enabled() + )); + next_id +} + +fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} From c41134fe5a006136d0237e1f347a3761795f5818 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:15:11 +0700 Subject: [PATCH 19/79] test(api/nonfungibles): encoding_read_result --- pallets/api/src/nonfungibles/mod.rs | 10 +-- pallets/api/src/nonfungibles/tests.rs | 109 ++++++++++++++++++++++++-- pallets/nfts/src/types.rs | 22 +++--- 3 files changed, 120 insertions(+), 21 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 9655bbea..eb88bfa6 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -137,8 +137,8 @@ pub mod pallet { from: Option>, /// The recipient of the transfer. `None` when burning. to: Option>, - /// The amount minted. - value: Option>, + /// The price of the collection item. + price: Option>, }, } @@ -167,7 +167,7 @@ pub mod pallet { item, from: None, to: Some(account), - value: mint_price, + price: mint_price, }); Ok(()) } @@ -186,7 +186,7 @@ pub mod pallet { item, from: Some(account), to: None, - value: None, + price: None, }); Ok(()) } @@ -206,7 +206,7 @@ pub mod pallet { item, from: Some(from), to: Some(to), - value: None, + price: None, }); Ok(()) } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index d757508a..063ceee3 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,23 +1,119 @@ use codec::Encode; -use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_support::assert_ok; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; use crate::{ mock::*, - nonfungibles::{Event, Read::*}, + nonfungibles::{Event, Read::*, ReadResult}, Read, }; const ITEM: u32 = 1; +mod encoding_read_result { + use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; + use sp_runtime::{BoundedBTreeMap, BoundedVec}; + + use super::*; + + #[test] + fn total_supply() { + let total_supply: u32 = 1_000_000; + assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); + } + + #[test] + fn balance_of() { + let balance: u32 = 100; + assert_eq!(ReadResult::BalanceOf::(balance).encode(), balance.encode()); + } + + #[test] + fn allowance() { + let allowance = false; + assert_eq!(ReadResult::Allowance::(allowance).encode(), allowance.encode()); + } + + #[test] + fn owner_of() { + let mut owner = Some(account(ALICE)); + assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); + owner = None; + assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); + } + + #[test] + fn get_attribute() { + let mut attribute = Some(BoundedVec::truncate_from("some attribute".as_bytes().to_vec())); + assert_eq!( + ReadResult::GetAttribute::(attribute.clone()).encode(), + attribute.encode() + ); + attribute = None; + assert_eq!( + ReadResult::GetAttribute::(attribute.clone()).encode(), + attribute.encode() + ); + } + + #[test] + fn collection_owner() { + let mut collection_owner = Some(account(ALICE)); + assert_eq!( + ReadResult::CollectionOwner::(collection_owner.clone()).encode(), + collection_owner.encode() + ); + collection_owner = None; + assert_eq!( + ReadResult::CollectionOwner::(collection_owner.clone()).encode(), + collection_owner.encode() + ); + } + + #[test] + fn collection() { + let mut collection_details = Some(CollectionDetails { + owner: account(ALICE), + owner_deposit: 0, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }); + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + collection_details = None; + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + } + + #[test] + fn item() { + let mut item_details = Some(ItemDetails { + owner: account(ALICE), + approvals: BoundedBTreeMap::default(), + deposit: ItemDeposit { amount: 0, account: account(BOB) }, + }); + assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); + item_details = None; + assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); + } +} + #[test] fn mint_works() { new_test_ext().execute_with(|| { let owner = account(ALICE); let collection = create_collection(owner.clone()); // Successfully mint a new collection item. + let balance_before_mint = AccountBalance::::get(collection.clone(), owner.clone()); + // assert_ok!(NonFungibles::mint( signed(owner.clone()), owner.clone(), @@ -25,8 +121,11 @@ fn mint_works() { ITEM, None )); + let balance_after_mint = AccountBalance::::get(collection.clone(), owner.clone()); + assert_eq!(balance_after_mint, 1); + assert_eq!(balance_after_mint - balance_before_mint, 1); System::assert_last_event( - Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), value: None } + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } .into(), ); }); @@ -40,7 +139,7 @@ fn burn_works() { // Successfully burn an existing new collection item. assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None, value: None }.into(), + Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), ); }); } @@ -54,7 +153,7 @@ fn transfer() { // Successfully burn an existing new collection item. assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), value: None } + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } .into(), ); }); diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index f08f1d09..061352c0 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -94,18 +94,18 @@ pub(super) type PreSignedAttributesOf = PreSignedAttributes< #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Collection's owner. - pub(super) owner: AccountId, + pub owner: AccountId, /// The total balance deposited by the owner for all the storage data associated with this /// collection. Used by `destroy`. - pub(super) owner_deposit: DepositBalance, + pub owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. - pub(super) items: u32, + pub items: u32, /// The total number of outstanding item metadata of this collection. - pub(super) item_metadatas: u32, + pub item_metadatas: u32, /// The total number of outstanding item configs of this collection. - pub(super) item_configs: u32, + pub item_configs: u32, /// The total number of attributes for this collection. - pub(super) attributes: u32, + pub attributes: u32, } /// Witness data for the destroy transactions. @@ -145,21 +145,21 @@ pub struct MintWitness { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ItemDetails { /// The owner of this item. - pub(super) owner: AccountId, + pub owner: AccountId, /// The approved transferrer of this item, if one is set. - pub(super) approvals: Approvals, + pub 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: Deposit, + pub 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, + pub account: AccountId, /// An amount that gets reserved. - pub(super) amount: DepositBalance, + pub amount: DepositBalance, } /// Information about the collection's metadata. From a32e801cbc18f07e1f05acf6f52c8d8f2c0e015b Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:51:50 +0700 Subject: [PATCH 20/79] refactor(api/nonfungibles): pallet tests --- pallets/api/src/nonfungibles/tests.rs | 243 +++++++++++++++----------- 1 file changed, 143 insertions(+), 100 deletions(-) diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 063ceee3..05c82f60 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,7 +1,11 @@ use codec::Encode; -use frame_support::assert_ok; +use frame_support::{assert_noop, assert_ok}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; +use pallet_nfts::{ + AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, + ItemDetails, MintSettings, +}; +use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; use crate::{ @@ -12,10 +16,9 @@ use crate::{ const ITEM: u32 = 1; -mod encoding_read_result { - use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; - use sp_runtime::{BoundedBTreeMap, BoundedVec}; +type NftsError = pallet_nfts::Error; +mod encoding_read_result { use super::*; #[test] @@ -107,53 +110,72 @@ mod encoding_read_result { } #[test] -fn mint_works() { +fn transfer() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let collection = create_collection(owner.clone()); - // Successfully mint a new collection item. - let balance_before_mint = AccountBalance::::get(collection.clone(), owner.clone()); - // - assert_ok!(NonFungibles::mint( - signed(owner.clone()), - owner.clone(), - collection, - ITEM, - None - )); - let balance_after_mint = AccountBalance::::get(collection.clone(), owner.clone()); - assert_eq!(balance_after_mint, 1); - assert_eq!(balance_after_mint - balance_before_mint, 1); + let owner = ALICE; + let dest = BOB; + + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::transfer(origin, collection, item, account(dest)), + BadOrigin + ); + } + // Successfully burn an existing new collection item. + let balance_before_transfer = AccountBalance::::get(collection, &account(dest)); + assert_ok!(NonFungibles::transfer(signed(owner), collection, ITEM, account(dest))); + let balance_after_transfer = AccountBalance::::get(collection, &account(dest)); + assert_eq!(AccountBalance::::get(collection, &account(owner)), 0); + assert_eq!(balance_after_transfer - balance_before_transfer, 1); System::assert_last_event( - Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } - .into(), + Event::Transfer { + collection, + item, + from: Some(account(owner)), + to: Some(account(dest)), + price: None, + } + .into(), ); }); } #[test] -fn burn_works() { +fn mint_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + let owner = ALICE; + let collection = nfts::create_collection(owner); + + // Successfully mint a new collection item. + let balance_before_mint = AccountBalance::::get(collection, account(owner)); + assert_ok!(NonFungibles::mint(signed(owner), account(owner), collection, ITEM, None)); + let balance_after_mint = AccountBalance::::get(collection, account(owner)); + assert_eq!(balance_after_mint, 1); + assert_eq!(balance_after_mint - balance_before_mint, 1); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), + Event::Transfer { + collection, + item: ITEM, + from: None, + to: Some(account(owner)), + price: None, + } + .into(), ); }); } #[test] -fn transfer() { +fn burn_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let dest = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); + let owner = ALICE; + // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + assert_ok!(NonFungibles::burn(signed(owner), collection, ITEM)); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + Event::Transfer { collection, item, from: Some(account(owner)), to: None, price: None } .into(), ); }); @@ -162,59 +184,63 @@ fn transfer() { #[test] fn approve_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let operator = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully approve `spender` to transfer the collection item. + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + // Successfully approve `oeprator` to transfer the collection item. assert_ok!(NonFungibles::approve( - signed(owner.clone()), + signed(owner), collection, Some(item), - operator.clone(), + account(operator), true )); System::assert_last_event( Event::Approval { collection, item: Some(item), - owner, - operator: operator.clone(), + owner: account(owner), + operator: account(operator), approved: true, } .into(), ); - // Successfully transfer the item by the delegated account `spender`. - assert_ok!(Nfts::transfer(signed(operator.clone()), collection, item, operator)); + // Successfully transfer the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, account(operator))); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - // Successfully cancel the transfer approval of `spender` by `owner`. + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + // Successfully cancel the transfer approval of `operator` by `owner`. assert_ok!(NonFungibles::approve( signed(owner), collection, Some(item), - spender.clone(), + account(operator), false )); - // Failed to transfer the item by `spender` without permission. - assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + // Failed to transfer the item by `operator` without permission. + assert_noop!( + Nfts::transfer(signed(operator), collection, item, account(operator)), + NftsError::NoPermission + ); }); } #[test] -fn owner_of_works() {} +fn owner_of_works() { + unimplemented!() +} #[test] fn collection_owner_works() { new_test_ext().execute_with(|| { - let collection = create_collection(account(ALICE)); + let collection = nfts::create_collection(ALICE); assert_eq!( NonFungibles::read(CollectionOwner(collection)).encode(), Nfts::collection_owner(collection).encode() @@ -225,7 +251,7 @@ fn collection_owner_works() { #[test] fn total_supply_works() { new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); + let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( NonFungibles::read(TotalSupply(collection)).encode(), Nfts::collection_items(collection).unwrap_or_default().encode() @@ -236,7 +262,7 @@ fn total_supply_works() { #[test] fn collection_works() { new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); + let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( NonFungibles::read(Collection(collection)).encode(), pallet_nfts::Collection::::get(&collection).encode(), @@ -247,11 +273,11 @@ fn collection_works() { #[test] fn balance_of_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, _) = create_collection_mint(owner.clone(), ITEM); + let owner = ALICE; + let (collection, _) = nfts::create_collection_mint(owner, ITEM); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: owner.clone() }).encode(), - AccountBalance::::get(collection, owner).encode() + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + AccountBalance::::get(collection, account(owner)).encode() ); }); } @@ -259,64 +285,81 @@ fn balance_of_works() { #[test] fn allowance_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let operator = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, operator.clone()); + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); assert_eq!( NonFungibles::read(Allowance { collection, item: Some(item), - owner: owner.clone(), - operator: operator.clone(), + owner: account(owner), + operator: account(operator), }) .encode(), - Nfts::check_allowance(&collection, &Some(item), &owner, &operator) + Nfts::check_allowance(&collection, &Some(item), &account(owner), &account(operator)) .is_ok() .encode() ); }); } -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) +fn signed(account_id: u8) -> RuntimeOrigin { + RuntimeOrigin::signed(account(account_id)) } -fn create_collection_mint_and_approve( - owner: AccountIdOf, - item: ItemIdOf, - spender: AccountIdOf, -) -> (u32, u32) { - let (collection, item) = create_collection_mint(owner.clone(), item); - assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), spender, None)); - (collection, item) +fn root() -> RuntimeOrigin { + RuntimeOrigin::root() } -fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { - let collection = create_collection(owner.clone()); - assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); - (collection, item) +fn none() -> RuntimeOrigin { + RuntimeOrigin::none() } -fn create_collection(owner: AccountIdOf) -> u32 { - let next_id = next_collection_id(); - assert_ok!(Nfts::create( - signed(owner.clone()), - owner.clone(), - collection_config_with_all_settings_enabled() - )); - next_id -} +mod nfts { + use super::*; -fn next_collection_id() -> u32 { - pallet_nfts::NextCollectionId::::get().unwrap_or_default() -} + pub(super) fn create_collection_mint_and_approve( + owner: u8, + item: ItemIdOf, + operator: u8, + ) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner, item); + assert_ok!(Nfts::approve_transfer( + signed(owner), + collection, + Some(item), + account(operator), + None + )); + (collection, item) + } + + pub(super) fn create_collection_mint(owner: u8, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner); + assert_ok!(Nfts::mint(signed(owner), collection, item, account(owner), None)); + (collection, item) + } + + pub(super) fn create_collection(owner: u8) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner), + account(owner), + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } -fn collection_config_with_all_settings_enabled( -) -> CollectionConfig, CollectionIdOf> { - CollectionConfig { - settings: CollectionSettings::all_enabled(), - max_supply: None, - mint_settings: MintSettings::default(), + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } } } From 83e8122e0c65fd904fdc1194e0ea03f227eae44a Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:30:10 +0700 Subject: [PATCH 21/79] test(nonfungibles): add tests and remove collection owner read --- pallets/api/src/nonfungibles/mod.rs | 70 ++++--- pallets/api/src/nonfungibles/tests.rs | 267 +++++++++++++++++++++++--- pallets/api/src/nonfungibles/types.rs | 2 + 3 files changed, 286 insertions(+), 53 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index eb88bfa6..7377048f 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -12,7 +12,7 @@ mod types; #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::Incrementable}; use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, @@ -21,7 +21,8 @@ pub mod pallet { use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, + NftsOf, NftsWeightInfoOf, }; use super::*; @@ -32,54 +33,62 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// Number of items existing in a concrete collection. - #[codec(index = 2)] + /// Total item supply of a collection. + #[codec(index = 0)] TotalSupply(CollectionIdOf), - /// Returns the total number of items in the collection owned by the account. - #[codec(index = 3)] + /// Account balance for a specified collection. + #[codec(index = 1)] BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, - /// Whether a spender is allowed to transfer an item or items from owner. - #[codec(index = 6)] + /// Allowance for an operator approved by an owner, for a specified collection or item. + #[codec(index = 2)] Allowance { collection: CollectionIdOf, owner: AccountIdOf, operator: AccountIdOf, item: Option>, }, - /// Returns the owner of an item. - #[codec(index = 0)] + /// Owner of a specified collection item. + #[codec(index = 3)] OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Returns the attribute of `item` for the given `key`. - #[codec(index = 6)] + /// Attribute value of a collection item. + #[codec(index = 4)] GetAttribute { collection: CollectionIdOf, item: Option>, namespace: AttributeNamespaceOf, key: BoundedVec, }, - /// Returns the owner of a collection. - #[codec(index = 1)] - CollectionOwner(CollectionIdOf), - /// Returns the details of a collection. - #[codec(index = 4)] + /// Details of a collection. + #[codec(index = 6)] Collection(CollectionIdOf), - /// Returns the details of an item. - #[codec(index = 5)] + /// Details of a collection item. + #[codec(index = 7)] Item { collection: CollectionIdOf, item: ItemIdOf }, + /// Next collection ID. + #[codec(index = 8)] + NextCollectionId, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { + /// Total item supply of a collection. TotalSupply(u32), + /// Account balance for a specified collection. BalanceOf(u32), + /// Allowance for an operator approved by an owner, for a specified collection or item. Allowance(bool), + /// Owner of a specified collection owner. OwnerOf(Option>), + /// Attribute value of a collection item. GetAttribute(Option>), - CollectionOwner(Option>), + /// Details of a collection. Collection(Option>), + /// Details of a collection item. Item(Option>), + /// Next collection ID. + NextCollectionId(Option>), } impl ReadResult { @@ -88,13 +97,13 @@ pub mod pallet { use ReadResult::*; match self { OwnerOf(result) => result.encode(), - CollectionOwner(result) => result.encode(), TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), Collection(result) => result.encode(), Item(result) => result.encode(), Allowance(result) => result.encode(), GetAttribute(result) => result.encode(), + NextCollectionId(result) => result.encode(), } } } @@ -140,6 +149,15 @@ pub mod pallet { /// The price of the collection item. price: Option>, }, + /// Event emitted when a collection is created. + Created { + /// The collection identifier. + id: CollectionIdOf, + /// The creator of the collection. + creator: AccountIdOf, + /// The administrator of the collection. + admin: AccountIdOf, + }, } #[pallet::call] @@ -248,6 +266,10 @@ pub mod pallet { admin: AccountIdOf, config: CreateCollectionConfigFor, ) -> DispatchResult { + let id = NextCollectionIdOf::::get() + .or(T::CollectionId::initial_value()) + .ok_or(pallet_nfts::Error::::UnknownCollection)?; + let creator = ensure_signed(origin.clone())?; let collection_config = CollectionConfig { settings: CollectionSettings::all_enabled(), max_supply: config.max_supply, @@ -259,6 +281,7 @@ pub mod pallet { }, }; NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), collection_config)?; + Self::deposit_event(Event::Created { id, admin, creator }); Ok(()) } @@ -403,12 +426,13 @@ pub mod pallet { pallet_nfts::Attribute::::get((collection, item, namespace, key)) .map(|attribute| attribute.0), ), - CollectionOwner(collection) => - ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + NextCollectionId => ReadResult::NextCollectionId( + NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), + ), } } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 05c82f60..e89ba0bd 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,5 +1,5 @@ use codec::Encode; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, @@ -7,7 +7,7 @@ use pallet_nfts::{ }; use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; -use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; +use super::types::{CollectionIdOf, ItemIdOf}; use crate::{ mock::*, nonfungibles::{Event, Read::*, ReadResult}, @@ -61,20 +61,6 @@ mod encoding_read_result { ); } - #[test] - fn collection_owner() { - let mut collection_owner = Some(account(ALICE)); - assert_eq!( - ReadResult::CollectionOwner::(collection_owner.clone()).encode(), - collection_owner.encode() - ); - collection_owner = None; - assert_eq!( - ReadResult::CollectionOwner::(collection_owner.clone()).encode(), - collection_owner.encode() - ); - } - #[test] fn collection() { let mut collection_details = Some(CollectionDetails { @@ -107,6 +93,20 @@ mod encoding_read_result { item_details = None; assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); } + + #[test] + fn next_collection_id_works() { + let mut next_collection_id = Some(0); + assert_eq!( + ReadResult::NextCollectionId::(next_collection_id).encode(), + next_collection_id.encode() + ); + next_collection_id = None; + assert_eq!( + ReadResult::NextCollectionId::(next_collection_id).encode(), + next_collection_id.encode() + ); + } } #[test] @@ -232,33 +232,212 @@ fn cancel_approval_works() { }); } +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, 10)); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + }); + assert_noop!( + Nfts::mint(signed(owner), collection, 42, account(owner), None), + NftsError::MaxSupplyReached + ); + }); +} + #[test] fn owner_of_works() { - unimplemented!() + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }).encode(), + Nfts::owner(collection, item).encode() + ); + }); +} + +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + result.encode() + ); + // Successfully get an existing attribute. + result = Some(value.clone()); + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + value, + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() + ); + }); } #[test] -fn collection_owner_works() { +fn clear_attribute_works() { new_test_ext().execute_with(|| { - let collection = nfts::create_collection(ALICE); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + BoundedVec::truncate_from("some value".as_bytes().to_vec()) + )); + // Successfully clear an attribute. + assert_ok!(Nfts::clear_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + )); assert_eq!( - NonFungibles::read(CollectionOwner(collection)).encode(), - Nfts::collection_owner(collection).encode() + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() ); }); } #[test] -fn total_supply_works() { +fn approve_item_attribute_works() { new_test_ext().execute_with(|| { - let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + )); + result = Some(value); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::Account(account(BOB)), + key: attribute + }) + .encode(), + result.encode() + ); + }); +} + +#[test] +fn cancel_item_attribute_approval_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + )); + assert_ok!(Nfts::cancel_item_attributes_approval( + signed(ALICE), + collection, + item, + account(BOB), + pallet_nfts::CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert_noop!( + Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + ), + NftsError::NoPermission + ); + }); +} + +#[test] +fn next_collection_id_works() { + new_test_ext().execute_with(|| { + let _ = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); assert_eq!( - NonFungibles::read(TotalSupply(collection)).encode(), - Nfts::collection_items(collection).unwrap_or_default().encode() + NonFungibles::read(NextCollectionId).encode(), + pallet_nfts::NextCollectionId::::get() + .or(CollectionIdOf::::initial_value()) + .encode(), ); }); } +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_eq!(NonFungibles::read(TotalSupply(collection)).encode(), (i + 1).encode()); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + Nfts::collection_items(collection).unwrap_or_default().encode() + ); + }); + }); +} + #[test] fn collection_works() { new_test_ext().execute_with(|| { @@ -271,23 +450,51 @@ fn collection_works() { } #[test] -fn balance_of_works() { +fn item_works() { new_test_ext().execute_with(|| { - let owner = ALICE; - let (collection, _) = nfts::create_collection_mint(owner, ITEM); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), - AccountBalance::::get(collection, account(owner)).encode() + NonFungibles::read(Item { collection, item }).encode(), + pallet_nfts::Item::::get(&collection, &item).encode(), ); }); } +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + (i + 1).encode() + ); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + AccountBalance::::get(collection, account(owner)).encode() + ); + }); + }); +} + #[test] fn allowance_works() { new_test_ext().execute_with(|| { let owner = ALICE; let operator = BOB; let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + assert_eq!( + NonFungibles::read(Allowance { + collection, + item: Some(item), + owner: account(owner), + operator: account(operator), + }) + .encode(), + true.encode() + ); assert_eq!( NonFungibles::read(Allowance { collection, diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index 8c53ffd3..f57ee697 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -7,12 +7,14 @@ use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; +pub(super) type NftsErrorOf = pallet_nfts::Error; pub(super) type NftsWeightInfoOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. pub(super) type AccountIdOf = ::AccountId; pub(super) type BalanceOf = <>::Currency as Currency< ::AccountId, >>::Balance; +pub(super) type NextCollectionIdOf = pallet_nfts::NextCollectionId; pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; pub(super) type ItemIdOf = From c6351729a98e9431f97154065e6213cf39c100b7 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:17:07 +0700 Subject: [PATCH 22/79] fix(runtime): remove PartialEq, add Encode --- pallets/api/src/fungibles/mod.rs | 4 +- pallets/api/src/fungibles/tests.rs | 85 +++++--- pallets/api/src/nonfungibles/mod.rs | 135 ++++++------- pallets/api/src/nonfungibles/tests.rs | 15 +- pallets/api/src/nonfungibles/types.rs | 3 +- pallets/nfts/src/benchmarking.rs | 12 +- pop-api/Cargo.toml | 1 + pop-api/src/lib.rs | 2 + pop-api/src/primitives.rs | 2 + pop-api/src/v0/mod.rs | 4 + pop-api/src/v0/nonfungibles/errors.rs | 33 +++ pop-api/src/v0/nonfungibles/events.rs | 45 +++++ pop-api/src/v0/nonfungibles/mod.rs | 98 +++++++++ pop-api/src/v0/nonfungibles/traits.rs | 87 ++++++++ pop-api/src/v0/nonfungibles/types.rs | 41 ++++ runtime/devnet/src/config/api/mod.rs | 212 +++++++++++++++++--- runtime/devnet/src/config/api/versioning.rs | 31 +-- runtime/devnet/src/lib.rs | 5 +- 18 files changed, 659 insertions(+), 156 deletions(-) create mode 100644 pop-api/src/v0/nonfungibles/errors.rs create mode 100644 pop-api/src/v0/nonfungibles/events.rs create mode 100644 pop-api/src/v0/nonfungibles/mod.rs create mode 100644 pop-api/src/v0/nonfungibles/traits.rs create mode 100644 pop-api/src/v0/nonfungibles/types.rs diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index bc36936c..50e5a218 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -43,7 +43,7 @@ pub mod pallet { /// State reads for the fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -84,7 +84,7 @@ pub mod pallet { /// Results of state reads for the fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total token supply for a specified token. TotalSupply(BalanceOf), diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 6e181a6f..a71c8394 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -30,47 +30,47 @@ mod encoding_read_result { #[test] fn total_supply() { let total_supply = 1_000_000 * UNIT; - assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); + assert_eq!(ReadResult::::TotalSupply(total_supply).encode(), total_supply.encode()); } #[test] fn balance_of() { let balance = 100 * UNIT; - assert_eq!(ReadResult::BalanceOf::(balance).encode(), balance.encode()); + assert_eq!(ReadResult::::BalanceOf(balance).encode(), balance.encode()); } #[test] fn allowance() { let allowance = 100 * UNIT; - assert_eq!(ReadResult::Allowance::(allowance).encode(), allowance.encode()); + assert_eq!(ReadResult::::Allowance(allowance).encode(), allowance.encode()); } #[test] fn token_name() { let mut name = Some("some name".as_bytes().to_vec()); - assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); name = None; - assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); } #[test] fn token_symbol() { let mut symbol = Some("some symbol".as_bytes().to_vec()); - assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); symbol = None; - assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); } #[test] fn token_decimals() { let decimals = 42; - assert_eq!(ReadResult::TokenDecimals::(decimals).encode(), decimals.encode()); + assert_eq!(ReadResult::::TokenDecimals(decimals).encode(), decimals.encode()); } #[test] fn token_exists() { let exists = true; - assert_eq!(ReadResult::TokenExists::(exists).encode(), exists.encode()); + assert_eq!(ReadResult::::TokenExists(exists).encode(), exists.encode()); } } @@ -513,11 +513,14 @@ fn total_supply_works() { new_test_ext().execute_with(|| { let total_supply = INIT_AMOUNT; assert_eq!( - Fungibles::read(TotalSupply(TOKEN)), - ReadResult::TotalSupply(Default::default()) + Fungibles::read(TotalSupply(TOKEN)).encode(), + ReadResult::::TotalSupply(Default::default()).encode() ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, total_supply); - assert_eq!(Fungibles::read(TotalSupply(TOKEN)), ReadResult::TotalSupply(total_supply)); + assert_eq!( + Fungibles::read(TotalSupply(TOKEN)).encode(), + ReadResult::::TotalSupply(total_supply).encode() + ); assert_eq!( Fungibles::read(TotalSupply(TOKEN)).encode(), Assets::total_supply(TOKEN).encode(), @@ -530,13 +533,13 @@ fn balance_of_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), - ReadResult::BalanceOf(Default::default()) + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + ReadResult::::BalanceOf(Default::default()).encode() ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, value); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), - ReadResult::BalanceOf(value) + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + ReadResult::::BalanceOf(value).encode() ); assert_eq!( Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), @@ -554,8 +557,9 @@ fn allowance_works() { token: TOKEN, owner: account(ALICE), spender: account(BOB) - }), - ReadResult::Allowance(Default::default()) + }) + .encode(), + ReadResult::::Allowance(Default::default()).encode() ); assets::create_mint_and_approve(ALICE, TOKEN, ALICE, value * 2, BOB, value); assert_eq!( @@ -563,8 +567,9 @@ fn allowance_works() { token: TOKEN, owner: account(ALICE), spender: account(BOB) - }), - ReadResult::Allowance(value) + }) + .encode(), + ReadResult::::Allowance(value).encode() ); assert_eq!( Fungibles::read(Allowance { @@ -584,13 +589,31 @@ fn token_metadata_works() { let name: Vec = vec![11, 12, 13]; let symbol: Vec = vec![21, 22, 23]; let decimals: u8 = 69; - assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(None)); - assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(None)); - assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(0)); + assert_eq!( + Fungibles::read(TokenName(TOKEN)).encode(), + ReadResult::::TokenName(None).encode() + ); + assert_eq!( + Fungibles::read(TokenSymbol(TOKEN)).encode(), + ReadResult::::TokenSymbol(None).encode() + ); + assert_eq!( + Fungibles::read(TokenDecimals(TOKEN)).encode(), + ReadResult::::TokenDecimals(0).encode() + ); assets::create_and_set_metadata(ALICE, TOKEN, name.clone(), symbol.clone(), decimals); - assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(Some(name))); - assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(Some(symbol))); - assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(decimals)); + assert_eq!( + Fungibles::read(TokenName(TOKEN)).encode(), + ReadResult::::TokenName(Some(name)).encode() + ); + assert_eq!( + Fungibles::read(TokenSymbol(TOKEN)).encode(), + ReadResult::::TokenSymbol(Some(symbol)).encode() + ); + assert_eq!( + Fungibles::read(TokenDecimals(TOKEN)).encode(), + ReadResult::::TokenDecimals(decimals).encode() + ); assert_eq!(Fungibles::read(TokenName(TOKEN)).encode(), Some(Assets::name(TOKEN)).encode()); assert_eq!( Fungibles::read(TokenSymbol(TOKEN)).encode(), @@ -606,9 +629,15 @@ fn token_metadata_works() { #[test] fn token_exists_works() { new_test_ext().execute_with(|| { - assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(false)); + assert_eq!( + Fungibles::read(TokenExists(TOKEN)).encode(), + ReadResult::::TokenExists(false).encode() + ); assert_ok!(Assets::create(signed(ALICE), TOKEN, account(ALICE), 1)); - assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(true)); + assert_eq!( + Fungibles::read(TokenExists(TOKEN)).encode(), + ReadResult::::TokenExists(true).encode() + ); assert_eq!( Fungibles::read(TokenExists(TOKEN)).encode(), Assets::asset_exists(TOKEN).encode(), diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 7377048f..4939b7ee 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -5,10 +5,11 @@ pub use pallet::*; use pallet_nfts::WeightInfo; use sp_runtime::traits::StaticLookup; +pub use types::*; #[cfg(test)] mod tests; -mod types; +pub mod types; #[frame_support::pallet] pub mod pallet { @@ -29,7 +30,7 @@ pub mod pallet { /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -48,10 +49,10 @@ pub mod pallet { item: Option>, }, /// Owner of a specified collection item. - #[codec(index = 3)] + #[codec(index = 5)] OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Attribute value of a collection item. - #[codec(index = 4)] + /// Attribute value of a collection item. (Error: bounded collection is not partial) + #[codec(index = 6)] GetAttribute { collection: CollectionIdOf, item: Option>, @@ -59,19 +60,19 @@ pub mod pallet { key: BoundedVec, }, /// Details of a collection. - #[codec(index = 6)] + #[codec(index = 9)] Collection(CollectionIdOf), /// Details of a collection item. - #[codec(index = 7)] + #[codec(index = 10)] Item { collection: CollectionIdOf, item: ItemIdOf }, /// Next collection ID. - #[codec(index = 8)] + #[codec(index = 11)] NextCollectionId, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total item supply of a collection. TotalSupply(u32), @@ -162,54 +163,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(NftsWeightInfoOf::::mint())] - pub fn mint( - origin: OriginFor, - to: AccountIdOf, - collection: CollectionIdOf, - item: ItemIdOf, - mint_price: Option>, - ) -> DispatchResult { - let account = ensure_signed(origin.clone())?; - let witness_data = MintWitness { mint_price, owned_item: Some(item) }; - NftsOf::::mint( - origin, - collection, - item, - T::Lookup::unlookup(to.clone()), - Some(witness_data), - )?; - Self::deposit_event(Event::Transfer { - collection, - item, - from: None, - to: Some(account), - price: mint_price, - }); - Ok(()) - } - - #[pallet::call_index(1)] - #[pallet::weight(NftsWeightInfoOf::::burn())] - pub fn burn( - origin: OriginFor, - collection: CollectionIdOf, - item: ItemIdOf, - ) -> DispatchResult { - let account = ensure_signed(origin.clone())?; - NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Transfer { - collection, - item, - from: Some(account), - to: None, - price: None, - }); - Ok(()) - } - - #[pallet::call_index(2)] + #[pallet::call_index(3)] #[pallet::weight(NftsWeightInfoOf::::transfer())] pub fn transfer( origin: OriginFor, @@ -229,7 +183,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(3)] + #[pallet::call_index(4)] #[pallet::weight(NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::cancel_approval())] pub fn approve( origin: OriginFor, @@ -259,7 +213,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(4)] + #[pallet::call_index(7)] #[pallet::weight(NftsWeightInfoOf::::create())] pub fn create( origin: OriginFor, @@ -285,7 +239,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(5)] + #[pallet::call_index(8)] #[pallet::weight(NftsWeightInfoOf::::destroy( witness.item_metadatas, witness.item_configs, @@ -299,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(6)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -312,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(7)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -324,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(8)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -335,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(9)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -345,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(10)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -361,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(11)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -379,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(12)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -388,6 +342,53 @@ pub mod pallet { ) -> DispatchResult { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } + + #[pallet::call_index(19)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + mint_price: Option>, + ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + let witness_data = MintWitness { mint_price, owned_item: Some(item) }; + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness_data), + )?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: None, + to: Some(account), + price: mint_price, + }); + Ok(()) + } + + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(account), + to: None, + price: None, + }); + Ok(()) + } } impl crate::Read for Pallet { diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index e89ba0bd..ad575469 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -264,7 +264,7 @@ fn get_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; // No attribute set. @@ -301,13 +301,18 @@ fn get_attribute_works() { }); } +#[test] +fn set_metadata_works() { + unimplemented!() +} + #[test] fn clear_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); - let mut result: Option::ValueLimit>> = None; + let result: Option::ValueLimit>> = None; assert_ok!(Nfts::set_attribute( signed(ALICE), collection, @@ -342,7 +347,7 @@ fn approve_item_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. @@ -374,9 +379,9 @@ fn cancel_item_attribute_approval_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - let mut result: Option::ValueLimit>> = None; + let result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); assert_ok!(Nfts::set_attribute( diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index f57ee697..be6d47e6 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,13 +1,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; +pub use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; use scale_info::TypeInfo; use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; -pub(super) type NftsErrorOf = pallet_nfts::Error; pub(super) type NftsWeightInfoOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. pub(super) type AccountIdOf = ::AccountId; diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index 8fa87557..ab66da26 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -578,9 +578,9 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let deadline = BlockNumberFor::::max_value(); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + assert_last_event::(Event::TransferApproved { collection, item: Some(item), owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { @@ -590,10 +590,10 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection, item: Some(item), owner: caller, delegate }.into()); } clear_all_transfer_approvals { @@ -603,7 +603,7 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 4caf8eaa..1b8c9f50 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -22,4 +22,5 @@ path = "src/lib.rs" [features] default = [ "std" ] fungibles = [ ] +nonfungibles = [ ] std = [ "ink/std", "pop-primitives/std", "sp-io/std" ] diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 546106e5..19241a5d 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -71,6 +71,8 @@ mod constants { pub(crate) const ASSETS: u8 = 52; pub(crate) const BALANCES: u8 = 10; pub(crate) const FUNGIBLES: u8 = 150; + pub(crate) const NONFUNGIBLES: u8 = 151; + pub(crate) const NFTS: u8 = 50; } // Helper method to build a dispatch call or a call to read state. diff --git a/pop-api/src/primitives.rs b/pop-api/src/primitives.rs index 2fcb8a95..250c1361 100644 --- a/pop-api/src/primitives.rs +++ b/pop-api/src/primitives.rs @@ -3,4 +3,6 @@ pub use pop_primitives::*; // Public due to integration tests crate. pub type AccountId = ::AccountId; +// Public due to integration tests crate. +pub type BlockNumber = ::BlockNumber; pub(crate) type Balance = ::Balance; diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index c6153892..8d0961ad 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -9,6 +9,10 @@ use crate::{ #[cfg(feature = "fungibles")] pub mod fungibles; +/// APIs for nonfungible tokens. +#[cfg(feature = "nonfungibles")] +pub mod nonfungibles; + pub(crate) const V0: u8 = 0; impl From for Error { diff --git a/pop-api/src/v0/nonfungibles/errors.rs b/pop-api/src/v0/nonfungibles/errors.rs new file mode 100644 index 00000000..f7e2324c --- /dev/null +++ b/pop-api/src/v0/nonfungibles/errors.rs @@ -0,0 +1,33 @@ +//! A set of errors for use in smart contracts that interact with the nonfungibles api. This +//! includes errors compliant to standards. + +use super::*; + +/// The PSP34 error. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Psp34Error { + /// Custom error type for cases if writer of traits added own restrictions + Custom(String), + /// Returned if owner approves self + SelfApprove, + /// Returned if the caller doesn't have allowance for transferring. + NotApproved, + /// Returned if the owner already own the token. + TokenExists, + /// Returned if the token doesn't exist + TokenNotExists, + /// Returned if safe transfer check fails + SafeTransferCheckFailed(String), +} + +impl From for Psp34Error { + /// Converts a `StatusCode` to a `PSP22Error`. + fn from(value: StatusCode) -> Self { + let encoded = value.0.to_le_bytes(); + match encoded { + // TODO: Handle conversion. + _ => Psp34Error::Custom(value.0.to_string()), + } + } +} diff --git a/pop-api/src/v0/nonfungibles/events.rs b/pop-api/src/v0/nonfungibles/events.rs new file mode 100644 index 00000000..e11b2f31 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/events.rs @@ -0,0 +1,45 @@ +//! A set of events for use in smart contracts interacting with the nonfungibles API. +//! +//! The `Transfer` and `Approval` events conform to the PSP-34 standard. +//! +//! These events are not emitted by the API itself but can be used in your contracts to +//! track token operations. Be mindful of the costs associated with emitting events. +//! +//! For more details, refer to [ink! events](https://use.ink/basics/events). + +use super::*; + +/// Event emitted when a token transfer occurs. +#[ink::event] +pub struct Transfer { + /// The source of the transfer. `None` when minting. + from: Option, + /// The recipient of the transfer. `None` when burning. + to: Option, + /// The item transferred (or minted/burned). + item: ItemId, +} + +/// Event emitted when a token approve occurs. +#[ink::event] +pub struct Approval { + /// The owner providing the allowance. + owner: AccountId, + /// The beneficiary of the allowance. + operator: AccountId, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option, + /// Whether allowance is set or removed. + approved: bool, +} + +/// Event emitted when an attribute is set for a token. +#[ink::event] +pub struct AttributeSet { + /// The item which attribute is set. + item: ItemId, + /// The key for the attribute. + key: Vec, + /// The data for the attribute. + data: Vec, +} diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs new file mode 100644 index 00000000..601dd99e --- /dev/null +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -0,0 +1,98 @@ +//! The `nonfungibles` module provides an API for interacting and managing nonfungible tokens. +//! +//! The API includes the following interfaces: +//! 1. PSP-34 +//! 2. PSP-34 Metadata +//! 3. PSP-22 Mintable & Burnable + +use constants::*; +pub use errors::*; +pub use events::*; +use ink::prelude::vec::Vec; +pub use traits::*; +pub use types::*; + +use crate::{ + primitives::{AccountId, Balance, TokenId}, + ChainExtensionMethodApi, Result, StatusCode, +}; + +pub mod errors; +pub mod events; +pub mod traits; +pub mod types; + +/// Returns the total item supply for a specified collection. +/// +/// # Parameters +/// - `collection` - The collection. +#[inline] +pub fn total_supply(collection: CollectionId) -> Result { + build_read_state(TOTAL_SUPPLY) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(token)) +} + +/// Returns the account balance for a specified `token` and `owner`. Returns `0` if +/// the account is non-existent. +/// +/// # Parameters +/// - `token` - The token. +/// - `owner` - The account whose balance is being queried. +#[inline] +pub fn balance_of(token: TokenId, owner: AccountId) -> Result { + build_read_state(BALANCE_OF) + .input::<(TokenId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(token, owner)) +} + +mod constants { + /// 1. PSP-34 + pub(super) const TOTAL_SUPPLY: u8 = 0; + pub(super) const BALANCE_OF: u8 = 1; + pub(super) const ALLOWANCE: u8 = 2; + pub(super) const TRANSFER: u8 = 3; + pub(super) const APPROVE: u8 = 4; + pub(super) const OWNER_OF: u8 = 5; + + /// 2. PSP-34 Metadata + pub(super) const GET_ATTRIBUTE: u8 = 6; + + /// 3. Management + pub(super) const CREATE: u8 = 7; + pub(super) const DESTROY: u8 = 8; + pub(super) const COLLECTION: u8 = 9; + pub(super) const ITEM: u8 = 10; + pub(super) const NEXT_COLLECTION_ID: u8 = 11; + pub(super) const SET_ATTRIBUTE: u8 = 12; + pub(super) const CLEAR_ATTRIBUTE: u8 = 13; + pub(super) const SET_METADATA: u8 = 14; + pub(super) const CLEAR_METADATA: u8 = 15; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; + pub(super) const SET_MAX_SUPPLY: u8 = 18; + + /// 4. PSP-34 Mintable & Burnable + pub(super) const MINT: u8 = 19; + pub(super) const BURN: u8 = 20; +} + +// Helper method to build a dispatch call. +// +// Parameters: +// - 'dispatchable': The index of the dispatchable function within the module. +fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { + crate::v0::build_dispatch(FUNGIBLES, dispatchable) +} + +// Helper method to build a call to read state. +// +// Parameters: +// - 'state_query': The index of the runtime state query. +fn build_read_state(state_query: u8) -> ChainExtensionMethodApi { + crate::v0::build_read_state(FUNGIBLES, state_query) +} diff --git a/pop-api/src/v0/nonfungibles/traits.rs b/pop-api/src/v0/nonfungibles/traits.rs new file mode 100644 index 00000000..616767a3 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/traits.rs @@ -0,0 +1,87 @@ +//! Traits that can be used by contracts. Including standard compliant traits. + +use core::result::Result; + +use ink::prelude::string::String; + +use super::*; + +#[ink::trait_definition] +pub trait Psp34 { + /// Returns the collection `Id`. + #[ink(message, selector = 0xffa27a5f)] + fn collection_id(&self) -> ItemId; + + // Returns the current total supply of the NFT. + #[ink(message, selector = 0x628413fe)] + fn total_supply(&self) -> u128; + + /// Returns the amount of items the owner has within a collection. + /// + /// # Parameters + /// - `owner` - The account whose balance is being queried. + #[ink(message, selector = 0xcde7e55f)] + fn balance_of(&self, owner: AccountId) -> u32; + + /// Returns whether the operator is approved by the owner to withdraw `item`. If `item` is + /// `None`, it returns whether the operator is approved to withdraw all owner's items for the + /// given collection. + /// + /// # Parameters + /// * `owner` - The account that owns the item(s). + /// * `operator` - the account that is allowed to withdraw the item(s). + /// * `item` - The item. If `None`, it is regarding all owner's items in collection. + #[ink(message, selector = 0x4790f55a)] + fn allowance(&self, owner: AccountId, operator: AccountId, id: Option) -> bool; + + /// Transfers an owned or approved item to the specified recipient. + /// + /// # Parameters + /// * `to` - The recipient account. + /// * `item` - The item. + /// - `data` - Additional data in unspecified format. + #[ink(message, selector = 0x3128d61b)] + fn transfer(&mut self, to: AccountId, id: ItemId, data: Vec) -> Result<(), Psp34Error>; + + /// Approves operator to withdraw item(s) from the contract's account. + /// + /// # Parameters + /// * `operator` - The account that is allowed to withdraw the item. + /// * `item` - Optional item. `None` means all items owned in the specified collection. + /// * `approved` - Whether the operator is given or removed the right to withdraw the item(s). + #[ink(message, selector = 0x1932a8b0)] + fn approve( + &mut self, + operator: AccountId, + id: Option, + approved: bool, + ) -> Result<(), Psp34Error>; + + /// Returns the owner of an item within a specified collection, if any. + /// + /// # Parameters + /// * `item` - The item. + #[ink(message, selector = 0x1168624d)] + fn owner_of(&self, id: ItemId) -> Option; +} + +#[ink::trait_definition] +pub trait Psp34Metadata { + /// Returns the attribute of `item` for the given `key`. + /// + /// # Parameters + /// * `item` - The item. If `None` the attributes for the collection are queried. + /// * `namespace` - The attribute's namespace. + /// * `key` - The key of the attribute. + #[ink(message, selector = 0xf19d48d1)] + fn get_attribute(&self, id: ItemId, key: Vec) -> Option>; +} + +#[ink::trait_definition] +pub trait Psp34Enumerable { + #[ink(message, selector = 0x3bcfb511)] + fn owners_token_by_index(&self, owner: AccountId, index: u128) -> Result; + + #[ink(message, selector = 0xcd0340d0)] + fn token_by_index(&self, index: u128) -> Result; +} diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs new file mode 100644 index 00000000..0cae037f --- /dev/null +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -0,0 +1,41 @@ +use super::*; +use crate::primitives::AccountId; + +pub type ItemId = u32; +pub type Collection = u32; + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CreateCollectionConfig { + pub max_supply: Option, + pub mint_type: MintType, + pub price: Option, + pub start_block: Option, + pub end_block: Option, +} + +/// Attribute namespaces for non-fungible tokens. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum AttributeNamespace { + /// An attribute set by collection's owner. + #[codec(index = 1)] + CollectionOwner, + /// An attribute set by item's owner. + #[codec(index = 2)] + ItemOwner, + /// An attribute set by a pre-approved account. + #[codec(index = 3)] + Account(AccountId), +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +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), +} diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 035a6014..a77abfb2 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use codec::Decode; +use codec::{Decode, Encode}; use cumulus_primitives_core::Weight; use frame_support::traits::Contains; pub(crate) use pallet_api::Extension; @@ -11,7 +11,8 @@ use sp_std::vec::Vec; use versioning::*; use crate::{ - config::assets::TrustBackedAssetsInstance, fungibles, Runtime, RuntimeCall, RuntimeEvent, + config::assets::TrustBackedAssetsInstance, fungibles, nonfungibles, Runtime, RuntimeCall, + RuntimeEvent, }; mod versioning; @@ -26,12 +27,15 @@ type DecodesAs = pallet_api::extension::DecodesAs< /// A query of runtime state. #[derive(Decode, Debug)] -#[cfg_attr(test, derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] #[repr(u8)] pub enum RuntimeRead { /// Fungible token queries. #[codec(index = 150)] Fungibles(fungibles::Read), + // Non-fungible token queries. + #[codec(index = 151)] + NonFungibles(nonfungibles::Read), } impl Readable for RuntimeRead { @@ -43,6 +47,7 @@ impl Readable for RuntimeRead { fn weight(&self) -> Weight { match self { RuntimeRead::Fungibles(key) => fungibles::Pallet::weight(key), + RuntimeRead::NonFungibles(key) => nonfungibles::Pallet::weight(key), } } @@ -50,16 +55,20 @@ impl Readable for RuntimeRead { fn read(self) -> Self::Result { match self { RuntimeRead::Fungibles(key) => RuntimeResult::Fungibles(fungibles::Pallet::read(key)), + RuntimeRead::NonFungibles(key) => + RuntimeResult::NonFungibles(nonfungibles::Pallet::read(key)), } } } /// The result of a runtime state read. #[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] pub enum RuntimeResult { /// Fungible token read results. Fungibles(fungibles::ReadResult), + /// Non-fungible token read results. + NonFungibles(nonfungibles::ReadResult), } impl RuntimeResult { @@ -67,6 +76,7 @@ impl RuntimeResult { fn encode(&self) -> Vec { match self { RuntimeResult::Fungibles(result) => result.encode(), + RuntimeResult::NonFungibles(result) => result.encode(), } } } @@ -77,6 +87,10 @@ impl fungibles::Config for Runtime { type WeightInfo = fungibles::weights::SubstrateWeight; } +impl nonfungibles::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + #[derive(Default)] pub struct Config; impl pallet_api::extension::Config for Config { @@ -130,8 +144,8 @@ pub struct Filter(PhantomData); impl> Contains for Filter { fn contains(c: &RuntimeCall) -> bool { - use fungibles::Call::*; - T::BaseCallFilter::contains(c) && + let contains_fungibles = { + use fungibles::Call::*; matches!( c, RuntimeCall::Fungibles( @@ -145,32 +159,65 @@ impl> Contains f mint { .. } | burn { .. } ) ) + }; + let contains_nonfungibles = { + use nonfungibles::Call::*; + matches!( + c, + RuntimeCall::NonFungibles( + transfer { .. } | + approve { .. } | create { .. } | + destroy { .. } | set_metadata { .. } | + clear_metadata { .. } | + set_attribute { .. } | + clear_attribute { .. } | + approve_item_attributes { .. } | + cancel_item_attributes_approval { .. } | + mint { .. } | burn { .. } | + set_max_supply { .. }, + ) + ) + }; + T::BaseCallFilter::contains(c) && contains_fungibles && contains_nonfungibles } } impl Contains for Filter { fn contains(r: &RuntimeRead) -> bool { - use fungibles::Read::*; - matches!( - r, - RuntimeRead::Fungibles( - TotalSupply(..) | - BalanceOf { .. } | - Allowance { .. } | - TokenName(..) | TokenSymbol(..) | - TokenDecimals(..) | - TokenExists(..) + let contains_fungibles = { + use fungibles::Read::*; + matches!( + r, + RuntimeRead::Fungibles( + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + TokenName(..) | TokenSymbol(..) | + TokenDecimals(..) | TokenExists(..) + ) + ) + }; + let contains_nonfungibles = { + use nonfungibles::Read::*; + matches!( + r, + RuntimeRead::NonFungibles( + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + OwnerOf { .. } | GetAttribute { .. } | + Collection { .. } | Item { .. } | + NextCollectionId + ) ) - ) + }; + contains_fungibles && contains_nonfungibles } } #[cfg(test)] mod tests { use codec::Encode; - use pallet_api::fungibles::Call::*; - use sp_core::crypto::AccountId32; - use RuntimeCall::{Balances, Fungibles}; + use sp_core::{bounded_vec, crypto::AccountId32}; + use RuntimeCall::{Balances, Fungibles, NonFungibles}; use super::*; @@ -181,6 +228,10 @@ mod tests { let value = 1_000; let result = fungibles::ReadResult::::TotalSupply(value); assert_eq!(RuntimeResult::Fungibles(result).encode(), value.encode()); + + let value = 1_000; + let result = nonfungibles::ReadResult::::TotalSupply(value); + assert_eq!(RuntimeResult::NonFungibles(result).encode(), value.encode()); } #[test] @@ -188,7 +239,7 @@ mod tests { use pallet_balances::{AdjustmentDirection, Call::*}; use sp_runtime::MultiAddress; - const CALLS: [RuntimeCall; 4] = [ + for call in vec![ Balances(force_adjust_total_issuance { direction: AdjustmentDirection::Increase, delta: 0, @@ -200,16 +251,18 @@ mod tests { value: 0, }), Balances(force_unreserve { who: MultiAddress::Address32([0u8; 32]), amount: 0 }), - ]; - - for call in CALLS { - assert!(!Filter::::contains(&call)) + ] + .iter() + { + assert!(!Filter::::contains(call)) } } #[test] fn filter_allows_fungibles_calls() { - const CALLS: [RuntimeCall; 11] = [ + use pallet_api::fungibles::Call::*; + + for call in vec![ Fungibles(transfer { token: 0, to: ACCOUNT, value: 0 }), Fungibles(transfer_from { token: 0, from: ACCOUNT, to: ACCOUNT, value: 0 }), Fungibles(approve { token: 0, spender: ACCOUNT, value: 0 }), @@ -221,17 +274,80 @@ mod tests { Fungibles(clear_metadata { token: 0 }), Fungibles(mint { token: 0, account: ACCOUNT, value: 0 }), Fungibles(burn { token: 0, account: ACCOUNT, value: 0 }), - ]; + ] + .iter() + { + assert!(Filter::::contains(call)) + } + } + + #[test] + fn filter_allows_nonfungibles_calls() { + use pallet_api::nonfungibles::{ + types::{CreateCollectionConfig, MintType}, + Call::*, + }; + use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness}; - for call in CALLS { - assert!(Filter::::contains(&call)) + for call in vec![ + NonFungibles(transfer { collection: 0, item: 0, to: ACCOUNT }), + NonFungibles(approve { + collection: 0, + item: Some(0), + operator: ACCOUNT, + approved: false, + }), + NonFungibles(create { + admin: ACCOUNT, + config: CreateCollectionConfig { + max_supply: Some(0), + mint_type: MintType::Public, + price: None, + start_block: Some(0), + end_block: None, + }, + }), + NonFungibles(destroy { + collection: 0, + witness: DestroyWitness { attributes: 0, item_configs: 0, item_metadatas: 0 }, + }), + NonFungibles(set_attribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::Pallet, + key: bounded_vec![], + value: bounded_vec![], + }), + NonFungibles(clear_attribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::Pallet, + key: bounded_vec![], + }), + NonFungibles(set_metadata { collection: 0, item: 0, data: bounded_vec![] }), + NonFungibles(clear_metadata { collection: 0, item: 0 }), + NonFungibles(approve_item_attributes { collection: 0, item: 0, delegate: ACCOUNT }), + NonFungibles(cancel_item_attributes_approval { + collection: 0, + item: 0, + delegate: ACCOUNT, + witness: CancelAttributesApprovalWitness { account_attributes: 0 }, + }), + NonFungibles(set_max_supply { collection: 0, max_supply: 0 }), + NonFungibles(mint { to: ACCOUNT, collection: 0, item: 0, mint_price: None }), + NonFungibles(burn { collection: 0, item: 0 }), + ] + .iter() + { + assert!(Filter::::contains(call)) } } #[test] fn filter_allows_fungibles_reads() { use super::{fungibles::Read::*, RuntimeRead::*}; - const READS: [RuntimeRead; 7] = [ + + for read in vec![ Fungibles(TotalSupply(1)), Fungibles(BalanceOf { token: 1, owner: ACCOUNT }), Fungibles(Allowance { token: 1, owner: ACCOUNT, spender: ACCOUNT }), @@ -239,10 +355,40 @@ mod tests { Fungibles(TokenSymbol(1)), Fungibles(TokenDecimals(10)), Fungibles(TokenExists(1)), - ]; + ] + .iter() + { + assert!(Filter::::contains(read)) + } + } + + #[test] + fn filter_allows_nonfungibles_reads() { + use super::{nonfungibles::Read::*, RuntimeRead::*}; - for read in READS { - assert!(Filter::::contains(&read)) + for read in vec![ + NonFungibles(TotalSupply(1)), + NonFungibles(BalanceOf { collection: 1, owner: ACCOUNT }), + NonFungibles(Allowance { + collection: 1, + item: None, + owner: ACCOUNT, + operator: ACCOUNT, + }), + NonFungibles(OwnerOf { collection: 1, item: 1 }), + // NonFungibles(GetAttribute { + // collection: 0, + // item: Some(0), + // namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + // key: bounded_vec![], + // }), + NonFungibles(Collection(1)), + NonFungibles(Item { collection: 1, item: 1 }), + NonFungibles(NextCollectionId), + ] + .iter() + { + assert!(Filter::::contains(read)) } } } diff --git a/runtime/devnet/src/config/api/versioning.rs b/runtime/devnet/src/config/api/versioning.rs index daa2a0a9..dbfd841d 100644 --- a/runtime/devnet/src/config/api/versioning.rs +++ b/runtime/devnet/src/config/api/versioning.rs @@ -1,3 +1,4 @@ +use codec::Encode; use sp_runtime::ModuleError; use super::*; @@ -40,7 +41,7 @@ impl From for RuntimeRead { /// Versioned runtime state read results. #[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] pub enum VersionedRuntimeResult { /// Version zero of runtime read results. V0(RuntimeResult), @@ -175,13 +176,19 @@ mod tests { fn from_versioned_runtime_call_to_runtime_call_works() { let call = RuntimeCall::System(Call::remark_with_event { remark: "pop".as_bytes().to_vec() }); - assert_eq!(RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())), call); + assert_eq!( + RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())).encode(), + call.encode() + ); } #[test] fn from_versioned_runtime_read_to_runtime_read_works() { let read = RuntimeRead::Fungibles(fungibles::Read::::TotalSupply(42)); - assert_eq!(RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())), read); + assert_eq!( + RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())).encode(), + read.encode() + ); } #[test] @@ -189,21 +196,21 @@ mod tests { let result = RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)); let v0 = 0; assert_eq!( - VersionedRuntimeResult::try_from((result.clone(), v0)), - Ok(VersionedRuntimeResult::V0(result.clone())) + VersionedRuntimeResult::try_from((result.clone(), v0)).unwrap().encode(), + VersionedRuntimeResult::V0(result.clone()).encode() ); } #[test] fn versioned_runtime_result_fails() { // Unknown version 1. - assert_eq!( - VersionedRuntimeResult::try_from(( - RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), - 1 - )), - Err(pallet_contracts::Error::::DecodingFailed.into()) - ); + let err = VersionedRuntimeResult::try_from(( + RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), + 1, + )) + .unwrap_err(); + let expected_err: DispatchError = pallet_contracts::Error::::DecodingFailed.into(); + assert_eq!(err.encode(), expected_err.encode()); } #[test] diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index 16f168fa..784cf766 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -39,7 +39,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -use pallet_api::fungibles; +use pallet_api::{fungibles, nonfungibles}; use pallet_balances::Call as BalancesCall; use pallet_ismp::mmr::{Leaf, Proof, ProofKeys}; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; @@ -634,6 +634,8 @@ mod runtime { // Pop API #[runtime::pallet_index(150)] pub type Fungibles = fungibles::Pallet; + #[runtime::pallet_index(151)] + pub type NonFungibles = nonfungibles::Pallet; } #[cfg(feature = "runtime-benchmarks")] @@ -641,6 +643,7 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] + [nonfungibles, NonFungibles], [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From f4d1ba462feb85e5042780370627eae2d566db4f Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:15:10 +0700 Subject: [PATCH 23/79] feat(nonfungibles): pop-api --- pop-api/src/v0/nonfungibles/mod.rs | 236 ++++++++++++++++++++++++--- pop-api/src/v0/nonfungibles/types.rs | 48 +++++- 2 files changed, 260 insertions(+), 24 deletions(-) diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 601dd99e..292285bc 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -13,7 +13,8 @@ pub use traits::*; pub use types::*; use crate::{ - primitives::{AccountId, Balance, TokenId}, + constants::NONFUNGIBLES, + primitives::{AccountId, BlockNumber}, ChainExtensionMethodApi, Result, StatusCode, }; @@ -22,32 +23,225 @@ pub mod events; pub mod traits; pub mod types; -/// Returns the total item supply for a specified collection. -/// -/// # Parameters -/// - `collection` - The collection. #[inline] -pub fn total_supply(collection: CollectionId) -> Result { +pub fn total_supply(collection: CollectionId) -> Result { build_read_state(TOTAL_SUPPLY) - .input::() - .output::, true>() + .input::() + .output::, true>() .handle_error_code::() - .call(&(token)) + .call(&(collection)) } -/// Returns the account balance for a specified `token` and `owner`. Returns `0` if -/// the account is non-existent. -/// -/// # Parameters -/// - `token` - The token. -/// - `owner` - The account whose balance is being queried. #[inline] -pub fn balance_of(token: TokenId, owner: AccountId) -> Result { +pub fn balance_of(collection: CollectionId, owner: AccountId) -> Result { build_read_state(BALANCE_OF) - .input::<(TokenId, AccountId)>() - .output::, true>() + .input::<(CollectionId, AccountId)>() + .output::, true>() .handle_error_code::() - .call(&(token, owner)) + .call(&(collection, owner)) +} + +#[inline] +pub fn allowance( + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, +) -> Result { + build_read_state(ALLOWANCE) + .input::<(CollectionId, AccountId, AccountId, Option)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, owner, operator, item)) +} + +#[inline] +pub fn transfer(collection: CollectionId, item: ItemId, to: AccountId) -> Result<()> { + build_dispatch(TRANSFER) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, to)) +} + +#[inline] +pub fn approve( + collection: CollectionId, + item: ItemId, + operator: AccountId, + approved: bool, +) -> Result<()> { + build_read_state(APPROVE) + .input::<(CollectionId, ItemId, AccountId, bool)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, operator, approved)) +} + +#[inline] +pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { + build_read_state(OWNER_OF) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn get_attribute( + collection: CollectionId, + item: AccountId, + namespace: AttributeNamespace, + key: Vec, +) -> Result> { + build_read_state(GET_ATTRIBUTE) + .input::<(CollectionId, AccountId, AttributeNamespace, Vec)>() + .output::>, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key)) +} + +#[inline] +pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { + build_read_state(CREATE) + .input::<(AccountId, CreateCollectionConfig)>() + .output::, true>() + .handle_error_code::() + .call(&(admin, config)) +} + +#[inline] +pub fn destroy(collection: CollectionId, witness: DestroyWitness) -> Result<()> { + build_read_state(DESTROY) + .input::<(CollectionId, DestroyWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, witness)) +} + +#[inline] +pub fn collection(collection: CollectionId) -> Result> { + build_read_state(COLLECTION) + .input::() + .output::>, true>() + .handle_error_code::() + .call(&(collection)) +} + +// TODO: ItemDetails. +#[inline] +pub fn item(collection: CollectionId, item: ItemId) -> Result { + build_read_state(ITEM) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn set_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<()> { + build_read_state(SET_ATTRIBUTE) + .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key, value)) +} + +#[inline] +pub fn clear_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<()> { + build_read_state(CLEAR_ATTRIBUTE) + .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key)) +} + +#[inline] +pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Result<()> { + build_read_state(SET_METADATA) + .input::<(CollectionId, ItemId, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, data)) +} + +#[inline] +pub fn clear_metadata(collection: CollectionId, item: ItemId) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn approve_item_attributes( + collection: CollectionId, + item: ItemId, + delegate: AccountId, +) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate)) +} + +#[inline] +pub fn cancel_item_attributes_approval( + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, +) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate, witness)) +} + +#[inline] +pub fn set_max_supply(collection: CollectionId, max_supply: u32) -> Result<()> { + build_read_state(SET_MAX_SUPPLY) + .input::<(CollectionId, u32)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, max_supply)) +} + +#[inline] +pub fn mint( + to: AccountId, + collection: CollectionId, + item: ItemId, + mint_price: Option, +) -> Result<()> { + build_read_state(MINT) + .input::<(AccountId, CollectionId, ItemId, Option)>() + .output::, true>() + .handle_error_code::() + .call(&(to, collection, item, mint_price)) +} + +#[inline] +pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { + build_read_state(BURN) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) } mod constants { @@ -86,7 +280,7 @@ mod constants { // Parameters: // - 'dispatchable': The index of the dispatchable function within the module. fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { - crate::v0::build_dispatch(FUNGIBLES, dispatchable) + crate::v0::build_dispatch(NONFUNGIBLES, dispatchable) } // Helper method to build a call to read state. @@ -94,5 +288,5 @@ fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { // Parameters: // - 'state_query': The index of the runtime state query. fn build_read_state(state_query: u8) -> ChainExtensionMethodApi { - crate::v0::build_read_state(FUNGIBLES, state_query) + crate::v0::build_read_state(NONFUNGIBLES, state_query) } diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs index 0cae037f..2357f1ab 100644 --- a/pop-api/src/v0/nonfungibles/types.rs +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -2,14 +2,34 @@ use super::*; use crate::primitives::AccountId; pub type ItemId = u32; -pub type Collection = u32; +pub type CollectionId = u32; +pub type Balance = u32; +/// Information about a collection. #[derive(Debug, PartialEq, Eq)] #[ink::scale_derive(Encode, Decode, TypeInfo)] -pub struct CreateCollectionConfig { +pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: Balance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CreateCollectionConfig { pub max_supply: Option, pub mint_type: MintType, - pub price: Option, + pub price: Option, pub start_block: Option, pub end_block: Option, } @@ -39,3 +59,25 @@ pub enum MintType { /// Only holders of items in specified collection could mint new items. HolderOf(CollectionId), } + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, +} + +/// Witness data for the destroy transactions. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct DestroyWitness { + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + #[codec(compact)] + pub item_configs: u32, + /// The total number of attributes for this collection. + #[codec(compact)] + pub attributes: u32, +} From d3c9295c8d97781af921114db6f6d762d23a8242 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:42:23 +0700 Subject: [PATCH 24/79] test(nonfungibles): pallet unit testing --- pallets/api/src/nonfungibles/mod.rs | 39 +++++++++--------- pallets/api/src/nonfungibles/tests.rs | 59 ++++++++++++++++----------- pallets/nfts/src/types.rs | 4 +- pop-api/src/v0/nonfungibles/mod.rs | 27 ++++-------- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 4939b7ee..1c437324 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -17,8 +17,9 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, - MintSettings, MintWitness, + ItemMetadata, ItemMetadataOf, MintSettings, MintWitness, }; + use sp_runtime::BoundedVec; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, @@ -62,12 +63,11 @@ pub mod pallet { /// Details of a collection. #[codec(index = 9)] Collection(CollectionIdOf), - /// Details of a collection item. - #[codec(index = 10)] - Item { collection: CollectionIdOf, item: ItemIdOf }, /// Next collection ID. - #[codec(index = 11)] + #[codec(index = 10)] NextCollectionId, + #[codec(index = 11)] + ItemMetadata { collection: CollectionIdOf, item: ItemIdOf }, } /// Results of state reads for the non-fungibles API. @@ -86,10 +86,10 @@ pub mod pallet { GetAttribute(Option>), /// Details of a collection. Collection(Option>), - /// Details of a collection item. - Item(Option>), /// Next collection ID. NextCollectionId(Option>), + /// Collection item metadata. + ItemMetadata(Option>), } impl ReadResult { @@ -101,10 +101,10 @@ pub mod pallet { TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), Collection(result) => result.encode(), - Item(result) => result.encode(), Allowance(result) => result.encode(), GetAttribute(result) => result.encode(), NextCollectionId(result) => result.encode(), + ItemMetadata(result) => result.encode(), } } } @@ -253,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(12)] + #[pallet::call_index(11)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -266,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(13)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -278,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(14)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -289,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(15)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -299,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(16)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -315,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(17)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -333,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(18)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -343,7 +343,7 @@ pub mod pallet { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } - #[pallet::call_index(19)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( origin: OriginFor, @@ -371,7 +371,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(20)] + #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( origin: OriginFor, @@ -429,8 +429,9 @@ pub mod pallet { ), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), - Item { collection, item } => - ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data), + ), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), ), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index ad575469..7556e0ec 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -82,18 +82,6 @@ mod encoding_read_result { ); } - #[test] - fn item() { - let mut item_details = Some(ItemDetails { - owner: account(ALICE), - approvals: BoundedBTreeMap::default(), - deposit: ItemDeposit { amount: 0, account: account(BOB) }, - }); - assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); - item_details = None; - assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); - } - #[test] fn next_collection_id_works() { let mut next_collection_id = Some(0); @@ -107,6 +95,14 @@ mod encoding_read_result { next_collection_id.encode() ); } + + #[test] + fn item_metadata_works() { + let mut data = Some(BoundedVec::truncate_from("some metadata".as_bytes().to_vec())); + assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); + data = None; + assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); + } } #[test] @@ -303,7 +299,33 @@ fn get_attribute_works() { #[test] fn set_metadata_works() { - unimplemented!() + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + let value = BoundedVec::truncate_from("some metadata".as_bytes().to_vec()); + assert_ok!(NonFungibles::set_metadata(signed(ALICE), collection, item, value.clone())); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + Some(value).encode() + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_ok!(NonFungibles::set_metadata( + signed(ALICE), + collection, + item, + BoundedVec::truncate_from("some metadata".as_bytes().to_vec()) + )); + assert_ok!(NonFungibles::clear_metadata(signed(ALICE), collection, item)); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + ReadResult::::ItemMetadata(None).encode() + ); + }); } #[test] @@ -454,17 +476,6 @@ fn collection_works() { }); } -#[test] -fn item_works() { - new_test_ext().execute_with(|| { - let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); - assert_eq!( - NonFungibles::read(Item { collection, item }).encode(), - pallet_nfts::Item::::get(&collection, &item).encode(), - ); - }); -} - #[test] fn balance_of_works() { new_test_ext().execute_with(|| { diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 061352c0..8a5153c3 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -184,11 +184,11 @@ pub struct ItemMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. - pub(super) deposit: Deposit, + pub deposit: Deposit, /// General information concerning this item. Limited in length by `StringLimit`. This will /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. - pub(super) data: BoundedVec, + pub data: BoundedVec, } /// Information about the tip. diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 292285bc..afed937b 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -128,16 +128,6 @@ pub fn collection(collection: CollectionId) -> Result> .call(&(collection)) } -// TODO: ItemDetails. -#[inline] -pub fn item(collection: CollectionId, item: ItemId) -> Result { - build_read_state(ITEM) - .input::<(CollectionId, ItemId)>() - .output::, true>() - .handle_error_code::() - .call(&(collection, item)) -} - #[inline] pub fn set_attribute( collection: CollectionId, @@ -260,15 +250,14 @@ mod constants { pub(super) const CREATE: u8 = 7; pub(super) const DESTROY: u8 = 8; pub(super) const COLLECTION: u8 = 9; - pub(super) const ITEM: u8 = 10; - pub(super) const NEXT_COLLECTION_ID: u8 = 11; - pub(super) const SET_ATTRIBUTE: u8 = 12; - pub(super) const CLEAR_ATTRIBUTE: u8 = 13; - pub(super) const SET_METADATA: u8 = 14; - pub(super) const CLEAR_METADATA: u8 = 15; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; - pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; - pub(super) const SET_MAX_SUPPLY: u8 = 18; + pub(super) const NEXT_COLLECTION_ID: u8 = 10; + pub(super) const SET_ATTRIBUTE: u8 = 11; + pub(super) const CLEAR_ATTRIBUTE: u8 = 12; + pub(super) const SET_METADATA: u8 = 13; + pub(super) const CLEAR_METADATA: u8 = 14; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 16; + pub(super) const SET_MAX_SUPPLY: u8 = 17; /// 4. PSP-34 Mintable & Burnable pub(super) const MINT: u8 = 19; From b6ebd4865b36c494fda0db08ef558724162d156d Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:44:42 +0700 Subject: [PATCH 25/79] fix: [nonfungibles, NonFungibles] --- runtime/devnet/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index 784cf766..af9c85aa 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -643,7 +643,7 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] - [nonfungibles, NonFungibles], + [nonfungibles, NonFungibles] [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 356a253a1c0f81de591420ce385b02b3a30e4bb3 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:17:09 +0700 Subject: [PATCH 26/79] fix: base call filter --- pop-api/integration-tests/src/nonfungibles/mod.rs | 0 runtime/devnet/src/config/api/mod.rs | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 pop-api/integration-tests/src/nonfungibles/mod.rs diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index a77abfb2..1bceec67 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -178,7 +178,7 @@ impl> Contains f ) ) }; - T::BaseCallFilter::contains(c) && contains_fungibles && contains_nonfungibles + T::BaseCallFilter::contains(c) && (contains_fungibles || contains_nonfungibles) } } @@ -204,12 +204,11 @@ impl Contains for Filter { TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | OwnerOf { .. } | GetAttribute { .. } | - Collection { .. } | Item { .. } | - NextCollectionId + Collection { .. } | NextCollectionId ) ) }; - contains_fungibles && contains_nonfungibles + contains_fungibles || contains_nonfungibles } } From b953b42a143e5189d70d4e7648661d605a4bb5a2 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:59:04 +0700 Subject: [PATCH 27/79] feat(nonfungibles): pop-api integration tests utils --- pop-api/integration-tests/Cargo.toml | 1 + pop-api/integration-tests/src/lib.rs | 1 + .../integration-tests/src/nonfungibles/mod.rs | 12 + .../src/nonfungibles/utils.rs | 233 ++++++++++++++++++ pop-api/src/v0/nonfungibles/mod.rs | 8 +- 5 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 pop-api/integration-tests/src/nonfungibles/utils.rs diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 536a8a17..43ca3791 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -18,6 +18,7 @@ pallet-balances = { version = "37.0.0", default-features = false } pallet-contracts = { version = "35.0.0", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", + "nonfungibles", ] } pop-primitives = { path = "../../primitives", default-features = false } pop-runtime-devnet = { path = "../../runtime/devnet", default-features = false } diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index 9e6e20fc..bc82c9c4 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -13,6 +13,7 @@ use scale::{Decode, Encode}; use sp_runtime::{AccountId32, BuildStorage, DispatchError}; mod fungibles; +mod nonfungibles; type Balance = u128; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index e69de29b..1361014b 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -0,0 +1,12 @@ +use pop_api::nonfungibles::{ + events::{Approval, AttributeSet, Transfer}, + types::*, +}; +use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*}; +use utils::*; + +use super::*; + +mod utils; + +const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs new file mode 100644 index 00000000..024709fa --- /dev/null +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -0,0 +1,233 @@ +use super::*; + +fn do_bare_call(function: &str, addr: &AccountId32, params: Vec) -> ExecReturnValue { + let function = function_selector(function); + let params = [function, params].concat(); + bare_call(addr.clone(), params, 0).expect("should work") +} + +// TODO - issue #263 - why result.data[1..] +pub(super) fn decoded(result: ExecReturnValue) -> Result { + ::decode(&mut &result.data[1..]).map_err(|_| result) +} + +pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { + let result = do_bare_call("total_supply", addr, collection.encode()); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn balance_of( + addr: &AccountId32, + collection: CollectionId, + owner: AccountId32, +) -> Result { + let params = [collection.encode(), owner.encode()].concat(); + let result = do_bare_call("balance_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn allowance( + addr: &AccountId32, + collection: CollectionId, + owner: AccountId32, + operator: AccountId32, + item: Option, +) -> Result { + let params = [collection.encode(), owner.encode(), operator.encode(), item.encode()].concat(); + let result = do_bare_call("allowance", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn transfer( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + to: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), to.encode()].concat(); + let result = do_bare_call("transfer", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + operator: AccountId32, + approved: bool, +) -> Result<(), Error> { + let params = + [collection.encode(), item.encode(), operator.encode(), approved.encode()].concat(); + let result = do_bare_call("approve", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn owner_of( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("owner_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn get_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result, Error> { + let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); + let result = do_bare_call("get_attribute", &addr, params); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn create( + addr: &AccountId32, + admin: AccountId32, + config: CreateCollectionConfig, +) -> Result<(), Error> { + let params = [admin.encode(), config.encode()].concat(); + let result = do_bare_call("create", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn destroy( + addr: &AccountId32, + collection: CollectionId, + witness: DestroyWitness, +) -> Result<(), Error> { + let params = [collection.encode(), witness.encode()].concat(); + let result = do_bare_call("destroy", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn collection( + addr: &AccountId32, + collection: CollectionId, +) -> Result, Error> { + let result = do_bare_call("collection", &addr, collection.encode()); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<(), Error> { + let params = + [collection.encode(), item.encode(), namespace.encode(), key.encode(), value.encode()] + .concat(); + let result = do_bare_call("set_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); + let result = do_bare_call("clear_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + data: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), data.encode()].concat(); + let result = do_bare_call("set_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("clear_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve_item_attributes( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode()].concat(); + let result = do_bare_call("approve_item_attributes", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn cancel_item_attributes_approval( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, + witness: CancelAttributesApprovalWitness, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode(), witness.encode()].concat(); + let result = do_bare_call("cancel_item_attributes_approval", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_max_supply( + addr: &AccountId32, + collection: CollectionId, + max_supply: u32, +) -> Result<(), Error> { + let params = [collection.encode(), max_supply.encode()].concat(); + let result = do_bare_call("set_max_supply", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +/// Get the last event from pallet contracts. +pub(super) fn last_contract_event() -> Vec { + let events = System::read_events_for_pallet::>(); + let contract_events = events + .iter() + .filter_map(|event| match event { + pallet_contracts::Event::::ContractEmitted { data, .. } => + Some(data.as_slice()), + _ => None, + }) + .collect::>(); + contract_events.last().unwrap().to_vec() +} + +/// Decodes a byte slice into an `AccountId` as defined in `primitives`. +/// +/// This is used to resolve type mismatches between the `AccountId` in the integration tests and the +/// contract environment. +pub fn account_id_from_slice(s: &[u8; 32]) -> pop_api::primitives::AccountId { + pop_api::primitives::AccountId::decode(&mut &s[..]).expect("Should be decoded to AccountId") +} diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index afed937b..8c7083ba 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -90,12 +90,12 @@ pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { #[inline] pub fn get_attribute( collection: CollectionId, - item: AccountId, + item: ItemId, namespace: AttributeNamespace, key: Vec, ) -> Result> { build_read_state(GET_ATTRIBUTE) - .input::<(CollectionId, AccountId, AttributeNamespace, Vec)>() + .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() .output::>, true>() .handle_error_code::() .call(&(collection, item, namespace, key)) @@ -256,8 +256,8 @@ mod constants { pub(super) const SET_METADATA: u8 = 13; pub(super) const CLEAR_METADATA: u8 = 14; pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; - pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 16; - pub(super) const SET_MAX_SUPPLY: u8 = 17; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; + pub(super) const SET_MAX_SUPPLY: u8 = 18; /// 4. PSP-34 Mintable & Burnable pub(super) const MINT: u8 = 19; From 914513de0313afe6c6e93529d152640226692c62 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 19:00:03 +0700 Subject: [PATCH 28/79] fix: benchmark listing --- runtime/devnet/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index af9c85aa..c64a07ff 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -643,7 +643,6 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] - [nonfungibles, NonFungibles] [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 6816dffa45ef492a83c1570f364402d8a5c3813e Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:32:19 +0700 Subject: [PATCH 29/79] fix: test --- runtime/devnet/src/config/api/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 1bceec67..1740540d 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -375,14 +375,13 @@ mod tests { operator: ACCOUNT, }), NonFungibles(OwnerOf { collection: 1, item: 1 }), - // NonFungibles(GetAttribute { - // collection: 0, - // item: Some(0), - // namespace: pallet_nfts::AttributeNamespace::CollectionOwner, - // key: bounded_vec![], - // }), + NonFungibles(GetAttribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: bounded_vec![], + }), NonFungibles(Collection(1)), - NonFungibles(Item { collection: 1, item: 1 }), NonFungibles(NextCollectionId), ] .iter() From 0a94a25cbdf7c74c32d8cbd3c15ce60d17a2c9f3 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:26:48 +0700 Subject: [PATCH 30/79] feat: add integration test contract --- pallets/api/src/nonfungibles/mod.rs | 22 +- pallets/api/src/nonfungibles/tests.rs | 9 +- .../contracts/nonfungibles/Cargo.toml | 20 ++ .../contracts/nonfungibles/lib.rs | 254 ++++++++++++++++++ pop-api/src/v0/nonfungibles/events.rs | 25 +- pop-api/src/v0/nonfungibles/mod.rs | 36 ++- 6 files changed, 329 insertions(+), 37 deletions(-) create mode 100644 pop-api/integration-tests/contracts/nonfungibles/Cargo.toml create mode 100644 pop-api/integration-tests/contracts/nonfungibles/lib.rs diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 1c437324..9161ff6c 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -75,7 +75,7 @@ pub mod pallet { #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total item supply of a collection. - TotalSupply(u32), + TotalSupply(u128), /// Account balance for a specified collection. BalanceOf(u32), /// Allowance for an operator approved by an owner, for a specified collection or item. @@ -253,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(11)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -266,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(12)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -278,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(13)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -289,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(14)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -299,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(15)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -315,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(16)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -333,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(17)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -343,7 +343,7 @@ pub mod pallet { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } - #[pallet::call_index(18)] + #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( origin: OriginFor, @@ -371,7 +371,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(19)] + #[pallet::call_index(20)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( origin: OriginFor, @@ -414,7 +414,7 @@ pub mod pallet { use Read::*; match value { TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default(), + NftsOf::::collection_items(collection).unwrap_or_default() as u128, ), BalanceOf { collection, owner } => ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 7556e0ec..dbeaaef4 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -23,7 +23,7 @@ mod encoding_read_result { #[test] fn total_supply() { - let total_supply: u32 = 1_000_000; + let total_supply: u128 = 1_000_000; assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); } @@ -456,10 +456,13 @@ fn total_supply_works() { let collection = nfts::create_collection(owner); (0..10).into_iter().for_each(|i| { assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); - assert_eq!(NonFungibles::read(TotalSupply(collection)).encode(), (i + 1).encode()); assert_eq!( NonFungibles::read(TotalSupply(collection)).encode(), - Nfts::collection_items(collection).unwrap_or_default().encode() + ((i + 1) as u128).encode() + ); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() ); }); }); diff --git a/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml new file mode 100644 index 00000000..47b105fb --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "nonfungibles" +version = "0.1.0" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +pop-api = { path = "../../../../pop-api", default-features = false, features = [ "nonfungibles" ] } + +[lib] +path = "lib.rs" + +[features] +default = [ "std" ] +e2e-tests = [ ] +ink-as-dependency = [ ] +std = [ + "ink/std", + "pop-api/std", +] diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs new file mode 100644 index 00000000..cfbd2bf0 --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -0,0 +1,254 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +/// 1. PSP-34 +/// 2. PSP-34 Metadata +/// 3. Management +/// 4. PSP-34 Mintable & Burnable +use ink::prelude::vec::Vec; +use pop_api::{ + nonfungibles::{ + self as api, + events::{Approval, AttributeSet, Transfer}, + AttributeNamespace, CancelAttributesApprovalWitness, CollectionDetails, CollectionId, + CreateCollectionConfig, DestroyWitness, ItemId, + }, + StatusCode, +}; + +pub type Result = core::result::Result; + +#[ink::contract] +mod nonfungibles { + use super::*; + + #[ink(storage)] + #[derive(Default)] + pub struct NonFungibles; + + impl NonFungibles { + #[ink(constructor, payable)] + pub fn new() -> Self { + ink::env::debug_println!("PopApiNonFungiblesExample::new"); + Default::default() + } + + /// 1. PSP-34 Interface: + /// - total_supply + /// - balance_of + /// - allowance + /// - transfer + /// - approve + /// - owner_of + + #[ink(message)] + pub fn total_supply(&self, collection: CollectionId) -> Result { + api::total_supply(collection) + } + + #[ink(message)] + pub fn balance_of(&self, collection: CollectionId, owner: AccountId) -> Result { + api::balance_of(collection, owner) + } + + #[ink(message)] + pub fn allowance( + &self, + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, + ) -> Result { + api::allowance(collection, owner, operator, item) + } + + #[ink(message)] + pub fn transfer( + &mut self, + collection: CollectionId, + item: ItemId, + to: AccountId, + ) -> Result<()> { + api::transfer(collection, item, to)?; + self.env().emit_event(Transfer { + from: Some(self.env().account_id()), + to: Some(to), + item, + }); + Ok(()) + } + + #[ink(message)] + pub fn approve( + &mut self, + collection: CollectionId, + item: Option, + operator: AccountId, + approved: bool, + ) -> Result<()> { + api::approve(collection, item, operator, approved)?; + self.env().emit_event(Approval { + owner: self.env().account_id(), + operator, + item, + approved, + }); + Ok(()) + } + + #[ink(message)] + pub fn owner_of( + &self, + collection: CollectionId, + item: ItemId, + ) -> Result> { + api::owner_of(collection, item) + } + + /// 2. PSP-34 Metadata Interface: + /// - get_attribute + + #[ink(message)] + pub fn get_attribute( + &self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result>> { + api::get_attribute(collection, item, namespace, key) + } + + /// 3. Asset Management: + /// - create + /// - destroy + /// - collection + /// - set_attribute + /// - clear_attribute + /// - set_metadata + /// - clear_metadata + /// - approve_item_attributes + /// - cancel_item_attributes_approval + /// - set_max_supply + /// - item_metadata + + #[ink(message)] + pub fn create(&mut self, admin: AccountId, config: CreateCollectionConfig) -> Result<()> { + api::create(admin, config) + } + + #[ink(message)] + pub fn destroy(&mut self, collection: CollectionId, witness: DestroyWitness) -> Result<()> { + api::destroy(collection, witness) + } + + #[ink(message)] + pub fn collection(&self, collection: CollectionId) -> Result> { + api::collection(collection) + } + + #[ink(message)] + pub fn set_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, + ) -> Result<()> { + api::set_attribute(collection, item, namespace, key.clone(), value.clone())?; + self.env().emit_event(AttributeSet { item, key, data: value }); + Ok(()) + } + + #[ink(message)] + pub fn clear_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result<()> { + api::clear_attribute(collection, item, namespace, key) + } + + #[ink(message)] + pub fn set_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + data: Vec, + ) -> Result<()> { + api::set_metadata(collection, item, data) + } + + #[ink(message)] + pub fn clear_metadata(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::clear_metadata(collection, item) + } + + #[ink(message)] + pub fn approve_item_attributes( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + ) -> Result<()> { + api::approve_item_attributes(collection, item, delegate) + } + + #[ink(message)] + pub fn cancel_item_attributes_approval( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, + ) -> Result<()> { + api::cancel_item_attributes_approval(collection, item, delegate, witness) + } + + #[ink(message)] + pub fn set_max_supply(&mut self, collection: CollectionId, max_supply: u32) -> Result<()> { + api::set_max_supply(collection, max_supply) + } + + #[ink(message)] + pub fn item_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + ) -> Result>> { + api::item_metadata(collection, item) + } + + /// 4. PSP-22 Mintable & Burnable Interface: + /// - mint + /// - burn + + #[ink(message)] + pub fn mint( + &mut self, + to: AccountId, + collection: CollectionId, + item: ItemId, + mint_price: Option, + ) -> Result<()> { + api::mint(to, collection, item, mint_price) + } + + #[ink(message)] + pub fn burn(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::burn(collection, item) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + PopApiNonFungiblesExample::new(); + } + } +} diff --git a/pop-api/src/v0/nonfungibles/events.rs b/pop-api/src/v0/nonfungibles/events.rs index e11b2f31..3c409868 100644 --- a/pop-api/src/v0/nonfungibles/events.rs +++ b/pop-api/src/v0/nonfungibles/events.rs @@ -13,33 +13,38 @@ use super::*; #[ink::event] pub struct Transfer { /// The source of the transfer. `None` when minting. - from: Option, + #[ink(topic)] + pub from: Option, /// The recipient of the transfer. `None` when burning. - to: Option, + #[ink(topic)] + pub to: Option, /// The item transferred (or minted/burned). - item: ItemId, + pub item: ItemId, } /// Event emitted when a token approve occurs. #[ink::event] pub struct Approval { /// The owner providing the allowance. - owner: AccountId, + #[ink(topic)] + pub owner: AccountId, /// The beneficiary of the allowance. - operator: AccountId, + #[ink(topic)] + pub operator: AccountId, /// The item which is (dis)approved. `None` for all owner's items. - item: Option, + pub item: Option, /// Whether allowance is set or removed. - approved: bool, + pub approved: bool, } /// Event emitted when an attribute is set for a token. #[ink::event] pub struct AttributeSet { /// The item which attribute is set. - item: ItemId, + #[ink(topic)] + pub item: ItemId, /// The key for the attribute. - key: Vec, + pub key: Vec, /// The data for the attribute. - data: Vec, + pub data: Vec, } diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 8c7083ba..af50b766 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -24,10 +24,10 @@ pub mod traits; pub mod types; #[inline] -pub fn total_supply(collection: CollectionId) -> Result { +pub fn total_supply(collection: CollectionId) -> Result { build_read_state(TOTAL_SUPPLY) .input::() - .output::, true>() + .output::, true>() .handle_error_code::() .call(&(collection)) } @@ -67,22 +67,22 @@ pub fn transfer(collection: CollectionId, item: ItemId, to: AccountId) -> Result #[inline] pub fn approve( collection: CollectionId, - item: ItemId, + item: Option, operator: AccountId, approved: bool, ) -> Result<()> { build_read_state(APPROVE) - .input::<(CollectionId, ItemId, AccountId, bool)>() + .input::<(CollectionId, Option, AccountId, bool)>() .output::, true>() .handle_error_code::() .call(&(collection, item, operator, approved)) } #[inline] -pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { +pub fn owner_of(collection: CollectionId, item: ItemId) -> Result> { build_read_state(OWNER_OF) .input::<(CollectionId, ItemId)>() - .output::, true>() + .output::>, true>() .handle_error_code::() .call(&(collection, item)) } @@ -93,10 +93,10 @@ pub fn get_attribute( item: ItemId, namespace: AttributeNamespace, key: Vec, -) -> Result> { +) -> Result>> { build_read_state(GET_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() - .output::>, true>() + .output::>>, true>() .handle_error_code::() .call(&(collection, item, namespace, key)) } @@ -128,6 +128,15 @@ pub fn collection(collection: CollectionId) -> Result> .call(&(collection)) } +#[inline] +pub fn item_metadata(collection: CollectionId, item: ItemId) -> Result>> { + build_read_state(BURN) + .input::<(CollectionId, ItemId)>() + .output::>>, true>() + .handle_error_code::() + .call(&(collection, item)) +} + #[inline] pub fn set_attribute( collection: CollectionId, @@ -251,11 +260,12 @@ mod constants { pub(super) const DESTROY: u8 = 8; pub(super) const COLLECTION: u8 = 9; pub(super) const NEXT_COLLECTION_ID: u8 = 10; - pub(super) const SET_ATTRIBUTE: u8 = 11; - pub(super) const CLEAR_ATTRIBUTE: u8 = 12; - pub(super) const SET_METADATA: u8 = 13; - pub(super) const CLEAR_METADATA: u8 = 14; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; + pub(super) const ITEM_METADATA: u8 = 11; + pub(super) const SET_ATTRIBUTE: u8 = 12; + pub(super) const CLEAR_ATTRIBUTE: u8 = 13; + pub(super) const SET_METADATA: u8 = 14; + pub(super) const CLEAR_METADATA: u8 = 15; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; pub(super) const SET_MAX_SUPPLY: u8 = 18; From f2cd4a00930edc1173cbcd282150579c7b0c2b93 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:48:49 +0700 Subject: [PATCH 31/79] test(nonfungibles): integration tests skeleton --- pop-api/integration-tests/Cargo.toml | 2 + .../contracts/nonfungibles/lib.rs | 20 ++-- pop-api/integration-tests/src/lib.rs | 2 +- .../integration-tests/src/nonfungibles/mod.rs | 62 ++++++++++- .../src/nonfungibles/utils.rs | 100 +++++++++++++++++- pop-api/src/v0/nonfungibles/errors.rs | 2 + pop-api/src/v0/nonfungibles/mod.rs | 8 ++ pop-api/src/v0/nonfungibles/types.rs | 2 +- 8 files changed, 178 insertions(+), 20 deletions(-) diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 43ca3791..afc7ba43 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4.22" pallet-assets = { version = "37.0.0", default-features = false } pallet-balances = { version = "37.0.0", default-features = false } pallet-contracts = { version = "35.0.0", default-features = false } +pallet-nfts = { path = "../../pallets/nfts", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", "nonfungibles", @@ -38,6 +39,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-contracts/std", + "pallet-nfts/std", "pop-api/std", "pop-primitives/std", "pop-runtime-devnet/std", diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs index cfbd2bf0..5cdc85f6 100644 --- a/pop-api/integration-tests/contracts/nonfungibles/lib.rs +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -132,8 +132,14 @@ mod nonfungibles { /// - item_metadata #[ink(message)] - pub fn create(&mut self, admin: AccountId, config: CreateCollectionConfig) -> Result<()> { - api::create(admin, config) + pub fn create( + &mut self, + admin: AccountId, + config: CreateCollectionConfig, + ) -> Result { + let next_collection_id = api::next_collection_id(); + api::create(admin, config)?; + next_collection_id } #[ink(message)] @@ -241,14 +247,4 @@ mod nonfungibles { api::burn(collection, item) } } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - PopApiNonFungiblesExample::new(); - } - } } diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index bc82c9c4..a21da998 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -8,7 +8,7 @@ use frame_support::{ weights::Weight, }; use pallet_contracts::{Code, CollectEvents, Determinism, ExecReturnValue}; -use pop_runtime_devnet::{Assets, Contracts, Runtime, RuntimeOrigin, System, UNIT}; +use pop_runtime_devnet::{Assets, Contracts, Nfts, Runtime, RuntimeOrigin, System, UNIT}; use scale::{Decode, Encode}; use sp_runtime::{AccountId32, BuildStorage, DispatchError}; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 1361014b..e91ce1e6 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -1,6 +1,10 @@ -use pop_api::nonfungibles::{ - events::{Approval, AttributeSet, Transfer}, - types::*, +use pallet_nfts::CollectionConfig; +use pop_api::{ + nonfungibles::{ + events::{Approval, AttributeSet, Transfer}, + types::*, + }, + primitives::BlockNumber, }; use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*}; use utils::*; @@ -9,4 +13,54 @@ use super::*; mod utils; -const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; +const COLLECTION_ID: CollectionId = 0; +const ITEM_ID: ItemId = 1; +const CONTRACT: &str = "contracts/nonfungibles/target/ink/nonfungibles.wasm"; + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(None)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&addr, &ALICE, &ALICE, COLLECTION_ID); + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(Some(100))); + }); +} + +// Testing a contract that creates a token in the constructor. +#[test] +fn instantiate_and_create_fungible_works() { + new_test_ext().execute_with(|| { + // let contract = + // "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; + // // Token already exists. + // assets::create(&ALICE, 0, 1); + // assert_eq!( + // instantiate_and_create_fungible(contract, 0, 1), + // Err(Module { index: 52, error: [5, 0] }) + // ); + // // Successfully create a token when instantiating the contract. + // let result_with_address = instantiate_and_create_fungible(contract, TOKEN_ID, 1); + // let instantiator = result_with_address.clone().ok(); + // assert_ok!(result_with_address); + // assert_eq!(&Assets::owner(TOKEN_ID), &instantiator); + // assert!(Assets::asset_exists(TOKEN_ID)); + // // Successfully emit event. + // let instantiator = account_id_from_slice(instantiator.unwrap().as_ref()); + // let expected = + // Created { id: TOKEN_ID, creator: instantiator.clone(), admin: instantiator }.encode(); + // assert_eq!(last_contract_event(), expected.as_slice()); + }); +} diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 024709fa..d52e3067 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -11,9 +11,12 @@ pub(super) fn decoded(result: ExecReturnValue) -> Result::decode(&mut &result.data[1..]).map_err(|_| result) } -pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { +pub(super) fn total_supply( + addr: &AccountId32, + collection: CollectionId, +) -> Result, Error> { let result = do_bare_call("total_supply", addr, collection.encode()); - decoded::>(result.clone()) + decoded::, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } @@ -210,6 +213,99 @@ pub(super) fn set_max_supply( .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } +pub(super) fn item_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result>, Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("item_metadata", &addr, params); + decoded::>, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) mod nfts { + use super::*; + + pub(crate) fn create_collection_and_mint_to( + owner: &AccountId32, + admin: &AccountId32, + to: &AccountId32, + item: ItemId, + ) -> (CollectionId, ItemId) { + let collection = create_collection(owner, admin); + mint(owner, to, collection, item); + (collection, item) + } + + pub(crate) fn create_collection(owner: &AccountId32, admin: &AccountId32) -> CollectionId { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(owner.clone()), + owner.clone().into(), + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } + + pub(crate) fn mint( + owner: &AccountId32, + to: &AccountId32, + collection: CollectionId, + item: ItemId, + ) -> ItemId { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner.clone()), + collection, + item, + owner.clone().into(), + None + )); + item + } + + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig { + CollectionConfig { + settings: pallet_nfts::CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: pallet_nfts::MintSettings::default(), + } + } +} + +pub(super) fn instantiate_and_create_nonfungible( + contract: &str, + admin: AccountId32, + config: CreateCollectionConfig, +) -> Result { + let function = function_selector("new"); + let input = [function, admin.encode(), config.encode()].concat(); + let wasm_binary = std::fs::read(contract).expect("could not read .wasm file"); + let result = Contracts::bare_instantiate( + ALICE, + INIT_VALUE, + GAS_LIMIT, + None, + Code::Upload(wasm_binary), + input, + vec![], + DEBUG_OUTPUT, + CollectEvents::Skip, + ) + .result + .expect("should work"); + let address = result.account_id; + let result = result.result; + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|_| address) +} + /// Get the last event from pallet contracts. pub(super) fn last_contract_event() -> Vec { let events = System::read_events_for_pallet::>(); diff --git a/pop-api/src/v0/nonfungibles/errors.rs b/pop-api/src/v0/nonfungibles/errors.rs index f7e2324c..c955bf60 100644 --- a/pop-api/src/v0/nonfungibles/errors.rs +++ b/pop-api/src/v0/nonfungibles/errors.rs @@ -1,6 +1,8 @@ //! A set of errors for use in smart contracts that interact with the nonfungibles api. This //! includes errors compliant to standards. +use ink::prelude::string::{String, ToString}; + use super::*; /// The PSP34 error. diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index af50b766..7052e5f5 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -243,6 +243,14 @@ pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { .call(&(collection, item)) } +#[inline] +pub fn next_collection_id() -> Result { + build_read_state(NEXT_COLLECTION_ID) + .output::, true>() + .handle_error_code::() + .call(&()) +} + mod constants { /// 1. PSP-34 pub(super) const TOTAL_SUPPLY: u8 = 0; diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs index 2357f1ab..3b0174b9 100644 --- a/pop-api/src/v0/nonfungibles/types.rs +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -3,7 +3,7 @@ use crate::primitives::AccountId; pub type ItemId = u32; pub type CollectionId = u32; -pub type Balance = u32; +pub(super) type Balance = u32; /// Information about a collection. #[derive(Debug, PartialEq, Eq)] From 09ce31dfc3fef9688cd426143cd94bd1a3ee0d27 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:02:32 +0700 Subject: [PATCH 32/79] fix(nonfungibles): integration tests --- pop-api/integration-tests/src/nonfungibles/mod.rs | 10 +++++----- pop-api/integration-tests/src/nonfungibles/utils.rs | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index e91ce1e6..ced0add8 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -25,17 +25,17 @@ fn total_supply_works() { // No tokens in circulation. assert_eq!( total_supply(&addr, COLLECTION_ID), - Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) ); - assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(None)); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); // Tokens in circulation. - nfts::create_collection_and_mint_to(&addr, &ALICE, &ALICE, COLLECTION_ID); + nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); assert_eq!( total_supply(&addr, COLLECTION_ID), - Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) ); - assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(Some(100))); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index d52e3067..035180d9 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -11,12 +11,9 @@ pub(super) fn decoded(result: ExecReturnValue) -> Result::decode(&mut &result.data[1..]).map_err(|_| result) } -pub(super) fn total_supply( - addr: &AccountId32, - collection: CollectionId, -) -> Result, Error> { +pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { let result = do_bare_call("total_supply", addr, collection.encode()); - decoded::, Error>>(result.clone()) + decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } From a0d9a2550fa7d3c7b4feca3fceff3d7df0a9fb38 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:09:44 +0700 Subject: [PATCH 33/79] test(nonfungibles): add balance_of_works --- .../integration-tests/src/nonfungibles/mod.rs | 22 +++++++++++++++++++ .../src/nonfungibles/utils.rs | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index ced0add8..73765e52 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -39,6 +39,28 @@ fn total_supply_works() { }); } +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); + }); +} + // Testing a contract that creates a token in the constructor. #[test] fn instantiate_and_create_fungible_works() { diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 035180d9..c306c191 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -265,6 +265,10 @@ pub(super) mod nfts { item } + pub(crate) fn balance_of(collection: CollectionId, owner: AccountId32) -> u32 { + pallet_nfts::AccountBalance::::get(collection, owner) + } + pub(super) fn collection_config_with_all_settings_enabled( ) -> CollectionConfig { CollectionConfig { From 7ec11c0f1f3f1cc08d4e438405d8f4a7a1e31248 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:35:46 +0700 Subject: [PATCH 34/79] test(nonfungibles): add more integration tests --- .../integration-tests/src/nonfungibles/mod.rs | 128 +++++++++++++++--- .../src/nonfungibles/utils.rs | 28 +++- pop-api/src/v0/nonfungibles/mod.rs | 24 ++-- 3 files changed, 142 insertions(+), 38 deletions(-) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 73765e52..8f26a6ab 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -1,3 +1,4 @@ +use frame_support::BoundedVec; use pallet_nfts::CollectionConfig; use pop_api::{ nonfungibles::{ @@ -61,28 +62,113 @@ fn balance_of_works() { }); } -// Testing a contract that creates a token in the constructor. #[test] -fn instantiate_and_create_fungible_works() { +fn allowance_works() { new_test_ext().execute_with(|| { - // let contract = - // "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; - // // Token already exists. - // assets::create(&ALICE, 0, 1); - // assert_eq!( - // instantiate_and_create_fungible(contract, 0, 1), - // Err(Module { index: 52, error: [5, 0] }) - // ); - // // Successfully create a token when instantiating the contract. - // let result_with_address = instantiate_and_create_fungible(contract, TOKEN_ID, 1); - // let instantiator = result_with_address.clone().ok(); - // assert_ok!(result_with_address); - // assert_eq!(&Assets::owner(TOKEN_ID), &instantiator); - // assert!(Assets::asset_exists(TOKEN_ID)); - // // Successfully emit event. - // let instantiator = account_id_from_slice(instantiator.unwrap().as_ref()); - // let expected = - // Created { id: TOKEN_ID, creator: instantiator.clone(), admin: instantiator }.encode(); - // assert_eq!(last_contract_event(), expected.as_slice()); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + // No tokens in circulation. + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), + Ok(!Nfts::check_allowance(&COLLECTION_ID, &None, &addr, &ALICE).is_err()), + ); + assert_eq!(allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), Ok(false)); + + let (collection, item) = + nfts::create_collection_mint_and_approve(&addr, &addr, ITEM_ID, &addr, &ALICE); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), + Ok(Nfts::check_allowance(&COLLECTION_ID, &Some(item), &addr.clone(), &ALICE).is_ok()), + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), + Ok(true) + ); + }); +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let before_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_ok!(transfer(&addr, collection, item, ALICE)); + let after_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_eq!(after_transfer_balance - before_transfer_balance, 1); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(approve(&addr, collection, Some(item), ALICE, true)); + assert!(Nfts::check_allowance(&collection, &Some(item), &addr.clone(), &ALICE).is_ok(),); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(ALICE), collection, item, BOB.into())); + }); +} + +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + }); +} + +// TODO +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(addr.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + get_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + ), + Ok("some value".as_bytes().to_vec()) + ); + }); +} + +// TODO +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + }); +} + +// TODO +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index c306c191..ba174c39 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -56,7 +56,7 @@ pub(super) fn transfer( pub(super) fn approve( addr: &AccountId32, collection: CollectionId, - item: ItemId, + item: Option, operator: AccountId32, approved: bool, ) -> Result<(), Error> { @@ -231,7 +231,25 @@ pub(super) mod nfts { item: ItemId, ) -> (CollectionId, ItemId) { let collection = create_collection(owner, admin); - mint(owner, to, collection, item); + mint(collection, item, owner, to); + (collection, item) + } + + pub(crate) fn create_collection_mint_and_approve( + owner: &AccountId32, + admin: &AccountId32, + item: ItemId, + to: &AccountId32, + operator: &AccountId32, + ) -> (u32, u32) { + let (collection, item) = create_collection_and_mint_to(&owner.clone(), admin, to, item); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(to.clone()), + collection, + Some(item), + operator.clone().into(), + None + )); (collection, item) } @@ -250,16 +268,16 @@ pub(super) mod nfts { } pub(crate) fn mint( - owner: &AccountId32, - to: &AccountId32, collection: CollectionId, item: ItemId, + owner: &AccountId32, + to: &AccountId32, ) -> ItemId { assert_ok!(Nfts::mint( RuntimeOrigin::signed(owner.clone()), collection, item, - owner.clone().into(), + to.clone().into(), None )); item diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 7052e5f5..7018685c 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -71,7 +71,7 @@ pub fn approve( operator: AccountId, approved: bool, ) -> Result<()> { - build_read_state(APPROVE) + build_dispatch(APPROVE) .input::<(CollectionId, Option, AccountId, bool)>() .output::, true>() .handle_error_code::() @@ -103,7 +103,7 @@ pub fn get_attribute( #[inline] pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { - build_read_state(CREATE) + build_dispatch(CREATE) .input::<(AccountId, CreateCollectionConfig)>() .output::, true>() .handle_error_code::() @@ -112,7 +112,7 @@ pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { #[inline] pub fn destroy(collection: CollectionId, witness: DestroyWitness) -> Result<()> { - build_read_state(DESTROY) + build_dispatch(DESTROY) .input::<(CollectionId, DestroyWitness)>() .output::, true>() .handle_error_code::() @@ -145,7 +145,7 @@ pub fn set_attribute( key: Vec, value: Vec, ) -> Result<()> { - build_read_state(SET_ATTRIBUTE) + build_dispatch(SET_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() .output::, true>() .handle_error_code::() @@ -159,7 +159,7 @@ pub fn clear_attribute( namespace: AttributeNamespace, key: Vec, ) -> Result<()> { - build_read_state(CLEAR_ATTRIBUTE) + build_dispatch(CLEAR_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() .output::, true>() .handle_error_code::() @@ -168,7 +168,7 @@ pub fn clear_attribute( #[inline] pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Result<()> { - build_read_state(SET_METADATA) + build_dispatch(SET_METADATA) .input::<(CollectionId, ItemId, Vec)>() .output::, true>() .handle_error_code::() @@ -177,7 +177,7 @@ pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Re #[inline] pub fn clear_metadata(collection: CollectionId, item: ItemId) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId)>() .output::, true>() .handle_error_code::() @@ -190,7 +190,7 @@ pub fn approve_item_attributes( item: ItemId, delegate: AccountId, ) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId, AccountId)>() .output::, true>() .handle_error_code::() @@ -204,7 +204,7 @@ pub fn cancel_item_attributes_approval( delegate: AccountId, witness: CancelAttributesApprovalWitness, ) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() .output::, true>() .handle_error_code::() @@ -213,7 +213,7 @@ pub fn cancel_item_attributes_approval( #[inline] pub fn set_max_supply(collection: CollectionId, max_supply: u32) -> Result<()> { - build_read_state(SET_MAX_SUPPLY) + build_dispatch(SET_MAX_SUPPLY) .input::<(CollectionId, u32)>() .output::, true>() .handle_error_code::() @@ -227,7 +227,7 @@ pub fn mint( item: ItemId, mint_price: Option, ) -> Result<()> { - build_read_state(MINT) + build_dispatch(MINT) .input::<(AccountId, CollectionId, ItemId, Option)>() .output::, true>() .handle_error_code::() @@ -236,7 +236,7 @@ pub fn mint( #[inline] pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { - build_read_state(BURN) + build_dispatch(BURN) .input::<(CollectionId, ItemId)>() .output::, true>() .handle_error_code::() From d82d3f9f2bf1c3707bba11f30214eeb3e7fe85da Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:30:38 +0700 Subject: [PATCH 35/79] test(nonfungibles): add more tests --- pallets/api/src/nonfungibles/mod.rs | 10 +- pallets/api/src/nonfungibles/tests.rs | 51 +++- pop-api/integration-tests/src/lib.rs | 2 +- .../integration-tests/src/nonfungibles/mod.rs | 237 +++++++++++++++++- .../src/nonfungibles/utils.rs | 31 ++- pop-api/src/v0/nonfungibles/mod.rs | 14 +- 6 files changed, 305 insertions(+), 40 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 9161ff6c..0c7985aa 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -17,14 +17,14 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, - ItemMetadata, ItemMetadataOf, MintSettings, MintWitness, + ItemMetadataOf, MintSettings, MintWitness, }; use sp_runtime::BoundedVec; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, - NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, NftsOf, + NftsWeightInfoOf, }; use super::*; @@ -56,7 +56,7 @@ pub mod pallet { #[codec(index = 6)] GetAttribute { collection: CollectionIdOf, - item: Option>, + item: ItemIdOf, namespace: AttributeNamespaceOf, key: BoundedVec, }, @@ -424,7 +424,7 @@ pub mod pallet { OwnerOf { collection, item } => ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( - pallet_nfts::Attribute::::get((collection, item, namespace, key)) + pallet_nfts::Attribute::::get((collection, Some(item), namespace, key)) .map(|attribute| attribute.0), ), Collection(collection) => diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index dbeaaef4..c119188a 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -2,15 +2,15 @@ use codec::Encode; use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ - AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, + AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, DestroyWitness, ItemDetails, MintSettings, }; -use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; +use sp_runtime::{BoundedVec, DispatchError::BadOrigin}; use super::types::{CollectionIdOf, ItemIdOf}; use crate::{ mock::*, - nonfungibles::{Event, Read::*, ReadResult}, + nonfungibles::{CreateCollectionConfig, Event, Read::*, ReadResult}, Read, }; @@ -259,7 +259,6 @@ fn owner_of_works() { fn get_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); - assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; @@ -267,7 +266,7 @@ fn get_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute.clone() }) @@ -287,7 +286,7 @@ fn get_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute }) @@ -333,7 +332,7 @@ fn clear_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let result: Option::ValueLimit>> = None; assert_ok!(Nfts::set_attribute( signed(ALICE), @@ -354,7 +353,7 @@ fn clear_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute }) @@ -386,7 +385,7 @@ fn approve_item_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::Account(account(BOB)), key: attribute }) @@ -403,7 +402,6 @@ fn cancel_item_attribute_approval_works() { assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - let result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); assert_ok!(Nfts::set_attribute( @@ -468,6 +466,39 @@ fn total_supply_works() { }); } +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let next_collection_id = pallet_nfts::NextCollectionId::::get().unwrap_or_default(); + assert_ok!(NonFungibles::create( + signed(owner), + account(owner), + CreateCollectionConfig { + max_supply: None, + mint_type: pallet_nfts::MintType::Public, + price: None, + start_block: None, + end_block: None, + }, + )); + assert_eq!(Nfts::collection_owner(next_collection_id), Some(account(owner))); + }); +} + +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let collection = nfts::create_collection(ALICE); + assert_ok!(NonFungibles::destroy( + signed(ALICE), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(Nfts::collection_owner(collection), None); + }); +} + #[test] fn collection_works() { new_test_ext().execute_with(|| { diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index a21da998..228b6eb2 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -21,7 +21,7 @@ const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); const BOB: AccountId32 = AccountId32::new([2_u8; 32]); const DEBUG_OUTPUT: pallet_contracts::DebugInfo = pallet_contracts::DebugInfo::UnsafeDebug; const FERDIE: AccountId32 = AccountId32::new([3_u8; 32]); -const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +const GAS_LIMIT: Weight = Weight::from_parts(500_000_000_000, 3 * 1024 * 1024); const INIT_AMOUNT: Balance = 100_000_000 * UNIT; const INIT_VALUE: Balance = 100 * UNIT; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 8f26a6ab..299ac04a 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -116,19 +116,17 @@ fn approve_works() { fn owner_of_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); }); } -// TODO #[test] fn get_attribute_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(addr.clone()), @@ -146,29 +144,248 @@ fn get_attribute_works() { AttributeNamespace::CollectionOwner, "some attribute".as_bytes().to_vec(), ), - Ok("some value".as_bytes().to_vec()) + Ok(Some("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn set_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + + assert_ok!(set_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + "some value".as_bytes().to_vec(), + )); + + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn clear_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(addr.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(clear_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec() + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + None + ); + }); +} + +#[test] +fn approve_item_attributes_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(approve_item_attributes(&addr.clone(), collection, item, ALICE)); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn cancel_item_attributes_approval_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(addr.clone()), + collection, + item, + ALICE.into() + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(cancel_item_attributes_approval( + &addr.clone(), + collection, + item, + ALICE, + CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + ) + .is_err()); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(set_metadata(&addr.clone(), collection, item, vec![])); + assert_eq!( + pallet_nfts::ItemMetadataOf::::get(collection, item) + .map(|metadata| metadata.data), + Some(MetadataData::default()) + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::set_metadata( + RuntimeOrigin::signed(addr.clone()), + collection, + item, + MetadataData::default() + )); + assert_ok!(clear_metadata(&addr.clone(), collection, item)); + assert_eq!( + pallet_nfts::ItemMetadataOf::::get(collection, item) + .map(|metadata| metadata.data), + None ); }); } -// TODO #[test] fn create_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); - assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + let collection = nfts::next_collection_id(); + assert_ok!(create( + &addr.clone(), + addr.clone(), + CreateCollectionConfig { + max_supply: Some(100), + mint_type: MintType::Public, + price: None, + start_block: None, + end_block: None, + } + )); + assert_eq!( + pallet_nfts::Collection::::get(collection), + Some(pallet_nfts::CollectionDetails { + owner: addr.clone(), + owner_deposit: 100000000000, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }) + ); }); } -// TODO #[test] fn destroy_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); - assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + let collection = nfts::create_collection(&addr, &addr); + assert_ok!(destroy( + &addr.clone(), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(pallet_nfts::Collection::::get(collection), None); + }); +} + +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let value = 10; + + let collection = nfts::create_collection(&addr, &addr); + assert_ok!(set_max_supply(&addr.clone(), collection, value)); + + (0..value).into_iter().for_each(|i| { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(addr.clone()), + collection, + i, + ALICE.into(), + None + )); + }); + assert!(Nfts::mint( + RuntimeOrigin::signed(addr.clone()), + collection, + value + 1, + ALICE.into(), + None + ) + .is_err()); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index ba174c39..bcec2bd5 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -1,5 +1,9 @@ use super::*; +pub(super) type AttributeKey = BoundedVec::KeyLimit>; +pub(super) type AttributeValue = BoundedVec::ValueLimit>; +pub(super) type MetadataData = BoundedVec::StringLimit>; + fn do_bare_call(function: &str, addr: &AccountId32, params: Vec) -> ExecReturnValue { let function = function_selector(function); let params = [function, params].concat(); @@ -84,11 +88,18 @@ pub(super) fn get_attribute( item: ItemId, namespace: AttributeNamespace, key: Vec, -) -> Result, Error> { - let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); +) -> Result>, Error> { + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + ] + .concat(); let result = do_bare_call("get_attribute", &addr, params); - decoded::, Error>>(result.clone()) + decoded::, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) } pub(super) fn create( @@ -130,9 +141,14 @@ pub(super) fn set_attribute( key: Vec, value: Vec, ) -> Result<(), Error> { - let params = - [collection.encode(), item.encode(), namespace.encode(), key.encode(), value.encode()] - .concat(); + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + AttributeValue::truncate_from(value).encode(), + ] + .concat(); let result = do_bare_call("set_attribute", &addr, params); decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) @@ -219,6 +235,7 @@ pub(super) fn item_metadata( let result = do_bare_call("item_metadata", &addr, params); decoded::>, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) } pub(super) mod nfts { @@ -263,7 +280,7 @@ pub(super) mod nfts { next_id } - pub(super) fn next_collection_id() -> u32 { + pub(crate) fn next_collection_id() -> u32 { pallet_nfts::NextCollectionId::::get().unwrap_or_default() } diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 7018685c..27e6d551 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -146,10 +146,10 @@ pub fn set_attribute( value: Vec, ) -> Result<()> { build_dispatch(SET_ATTRIBUTE) - .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() + .input::<(CollectionId, Option, AttributeNamespace, Vec, Vec)>() .output::, true>() .handle_error_code::() - .call(&(collection, item, namespace, key, value)) + .call(&(collection, Some(item), namespace, key, value)) } #[inline] @@ -160,10 +160,10 @@ pub fn clear_attribute( key: Vec, ) -> Result<()> { build_dispatch(CLEAR_ATTRIBUTE) - .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() + .input::<(CollectionId, Option, AttributeNamespace, Vec)>() .output::, true>() .handle_error_code::() - .call(&(collection, item, namespace, key)) + .call(&(collection, Some(item), namespace, key)) } #[inline] @@ -190,7 +190,7 @@ pub fn approve_item_attributes( item: ItemId, delegate: AccountId, ) -> Result<()> { - build_dispatch(CLEAR_METADATA) + build_dispatch(APPROVE_ITEM_ATTRIBUTES) .input::<(CollectionId, ItemId, AccountId)>() .output::, true>() .handle_error_code::() @@ -204,7 +204,7 @@ pub fn cancel_item_attributes_approval( delegate: AccountId, witness: CancelAttributesApprovalWitness, ) -> Result<()> { - build_dispatch(CLEAR_METADATA) + build_dispatch(CANCEL_ITEM_ATTRIBUTES_APPROVAL) .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() .output::, true>() .handle_error_code::() @@ -273,7 +273,7 @@ mod constants { pub(super) const CLEAR_ATTRIBUTE: u8 = 13; pub(super) const SET_METADATA: u8 = 14; pub(super) const CLEAR_METADATA: u8 = 15; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; + pub(super) const APPROVE_ITEM_ATTRIBUTES: u8 = 16; pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; pub(super) const SET_MAX_SUPPLY: u8 = 18; From d6f418e388140416a16186753fd1d5746390194c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:20:50 +0700 Subject: [PATCH 36/79] fix: devnet api config --- runtime/devnet/src/config/api/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 1740540d..3c6dfbd1 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -377,7 +377,7 @@ mod tests { NonFungibles(OwnerOf { collection: 1, item: 1 }), NonFungibles(GetAttribute { collection: 0, - item: Some(0), + item: 0, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: bounded_vec![], }), From 1943669fd14a2d80ab2ff5241d2e5d0559674941 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:04:52 +0700 Subject: [PATCH 37/79] chore: update weight pallet-nfts file --- pallets/nfts/src/weights.rs | 684 +++++++++++++++++++----------------- runtime/devnet/src/lib.rs | 1 + 2 files changed, 355 insertions(+), 330 deletions(-) diff --git a/pallets/nfts/src/weights.rs b/pallets/nfts/src/weights.rs index c5fb60a2..a6ec8215 100644 --- a/pallets/nfts/src/weights.rs +++ b/pallets/nfts/src/weights.rs @@ -1,30 +1,14 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. //! Autogenerated weights for `pallet_nfts` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 +//! DATE: 2024-10-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `R0GUE`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// ./target/release/pop-node // benchmark // pallet // --chain=dev @@ -34,12 +18,11 @@ // --no-storage-info // --no-median-slopes // --no-min-squares -// --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/nfts/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --output=./pallets/nfts/src/weights.rs +// --template=./scripts/pallet-weights-template.hbs +// --extrinsic= #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -107,10 +90,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -126,10 +109,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -141,6 +124,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -152,15 +137,19 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1005_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(1005_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -174,18 +163,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -195,18 +186,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -218,22 +211,24 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -245,6 +240,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -253,12 +250,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -269,12 +266,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -286,10 +283,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -299,10 +296,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -312,10 +309,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -329,10 +326,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -342,10 +339,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -355,10 +352,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -368,10 +365,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -381,10 +378,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -400,10 +397,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,10 +410,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -430,30 +427,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -461,12 +458,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -485,10 +482,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -502,10 +499,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,10 +516,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -536,10 +533,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -549,10 +546,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -560,10 +557,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -571,10 +568,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -582,10 +579,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -595,10 +592,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -608,10 +605,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -625,10 +622,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -644,28 +641,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -673,10 +672,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -686,10 +685,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -705,18 +704,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -726,6 +727,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -739,22 +742,22 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -766,12 +769,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -794,10 +797,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -813,10 +816,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -828,6 +831,8 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -839,15 +844,19 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1005_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(RocksDbWeight::get().writes(1005_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -861,18 +870,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -882,18 +893,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -905,22 +918,24 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -932,6 +947,8 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -940,12 +957,12 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -956,12 +973,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -973,10 +990,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -986,10 +1003,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -999,10 +1016,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1016,10 +1033,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1029,10 +1046,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1042,10 +1059,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1055,10 +1072,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1068,10 +1085,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1087,10 +1104,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1100,10 +1117,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1117,30 +1134,30 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1148,12 +1165,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1172,10 +1189,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1189,10 +1206,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1206,10 +1223,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1223,10 +1240,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1236,10 +1253,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1247,10 +1264,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1258,10 +1275,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1269,10 +1286,10 @@ impl WeightInfo for () { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1282,10 +1299,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1295,10 +1312,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1312,10 +1329,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1331,28 +1348,30 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -1360,10 +1379,10 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1373,10 +1392,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1392,18 +1411,20 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -1413,6 +1434,8 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1426,22 +1449,22 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -1453,12 +1476,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1466,3 +1489,4 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } } + diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index c64a07ff..95bf1a5a 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -647,6 +647,7 @@ mod benches { [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] [pallet_message_queue, MessageQueue] + [pallet_nfts, Nfts] [pallet_sudo, Sudo] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] From e477c00d9d18e6111b7fd7d3c5126b6687e51391 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:38:18 +0700 Subject: [PATCH 38/79] chore: rebase --- Cargo.lock | 1 + pallets/api/Cargo.toml | 7 +- pallets/api/src/fungibles/tests.rs | 286 ++++++++++++++++---------- pallets/api/src/lib.rs | 1 + pallets/api/src/mock.rs | 74 ++++++- pallets/api/src/nonfungibles/mod.rs | 260 +++++++++++++++++++++++ pallets/api/src/nonfungibles/tests.rs | 183 ++++++++++++++++ pallets/api/src/nonfungibles/types.rs | 61 ++++++ 8 files changed, 752 insertions(+), 121 deletions(-) create mode 100644 pallets/api/src/nonfungibles/mod.rs create mode 100644 pallets/api/src/nonfungibles/tests.rs create mode 100644 pallets/api/src/nonfungibles/types.rs diff --git a/Cargo.lock b/Cargo.lock index e9a8bb0e..b42da35a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7380,6 +7380,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "pallet-ismp", + "pallet-nfts", "parity-scale-codec", "pop-chain-extension", "scale-info", diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index b626cafc..99598c01 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -7,7 +7,7 @@ name = "pallet-api" version = "0.1.0" [package.metadata.docs.rs] -targets = [ "x86_64-unknown-linux-gnu" ] +targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec.workspace = true @@ -23,6 +23,7 @@ frame-support.workspace = true frame-system.workspace = true pallet-assets.workspace = true sp-core.workspace = true +pallet-nfts.workspace = true sp-runtime.workspace = true sp-std.workspace = true @@ -37,12 +38,13 @@ pallet-balances.workspace = true sp-io.workspace = true [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pop-chain-extension/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -55,6 +57,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-ismp/std", + "pallet-nfts/std", "pop-chain-extension/std", "scale-info/std", "sp-core/std", diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index f5c560bb..6e181a6f 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -83,17 +83,21 @@ fn transfer_works() { let to = BOB; for origin in vec![root(), none()] { - assert_noop!(Fungibles::transfer(origin, token, to, value), BadOrigin); + assert_noop!(Fungibles::transfer(origin, token, account(to), value), BadOrigin); } // Check error works for `Assets::transfer_keep_alive()`. - assert_noop!(Fungibles::transfer(signed(from), token, to, value), AssetsError::Unknown); + assert_noop!( + Fungibles::transfer(signed(from), token, account(to), value), + AssetsError::Unknown + ); assets::create_and_mint_to(from, token, from, value * 2); - let balance_before_transfer = Assets::balance(token, &to); - assert_ok!(Fungibles::transfer(signed(from), token, to, value)); - let balance_after_transfer = Assets::balance(token, &to); + let balance_before_transfer = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::transfer(signed(from), token, account(to), value)); + let balance_after_transfer = Assets::balance(token, &account(to)); assert_eq!(balance_after_transfer, balance_before_transfer + value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), + Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } + .into(), ); }); } @@ -108,26 +112,36 @@ fn transfer_from_works() { let spender = CHARLIE; for origin in vec![root(), none()] { - assert_noop!(Fungibles::transfer_from(origin, token, from, to, value), BadOrigin); + assert_noop!( + Fungibles::transfer_from(origin, token, account(from), account(to), value), + BadOrigin + ); } // Check error works for `Assets::transfer_approved()`. assert_noop!( - Fungibles::transfer_from(signed(spender), token, from, to, value), + Fungibles::transfer_from(signed(spender), token, account(from), account(to), value), AssetsError::Unknown ); // Approve `spender` to transfer up to `value`. assets::create_mint_and_approve(spender, token, from, value * 2, spender, value); // Successfully call transfer from. - let from_balance_before_transfer = Assets::balance(token, &from); - let to_balance_before_transfer = Assets::balance(token, &to); - assert_ok!(Fungibles::transfer_from(signed(spender), token, from, to, value)); - let from_balance_after_transfer = Assets::balance(token, &from); - let to_balance_after_transfer = Assets::balance(token, &to); + let from_balance_before_transfer = Assets::balance(token, &account(from)); + let to_balance_before_transfer = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::transfer_from( + signed(spender), + token, + account(from), + account(to), + value + )); + let from_balance_after_transfer = Assets::balance(token, &account(from)); + let to_balance_after_transfer = Assets::balance(token, &account(to)); // Check that `to` has received the `value` tokens from `from`. assert_eq!(to_balance_after_transfer, to_balance_before_transfer + value); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), + Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } + .into(), ); }); } @@ -144,7 +158,7 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, spender, value), + Fungibles::approve(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } @@ -161,20 +175,20 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, spender, value), + Fungibles::approve(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()` in `Greater` match arm. assert_noop!( - Fungibles::approve(signed(owner), token, spender, value), + Fungibles::approve(signed(owner), token, account(spender), value), AssetsError::Unknown.with_weight(WeightInfo::approve(1, 0)) ); assets::create_mint_and_approve(owner, token, owner, value, spender, value); // Check error works for `Assets::cancel_approval()` in `Less` match arm. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); @@ -193,38 +207,61 @@ mod approve { // Approves a value to spend that is higher than the current allowance. assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(Assets::allowance(token, &owner, &spender), 0); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); assert_eq!( - Fungibles::approve(signed(owner), token, spender, value), + Fungibles::approve(signed(owner), token, account(spender), value), Ok(Some(WeightInfo::approve(1, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value); - System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + System::assert_last_event( + Event::Approval { token, owner: account(owner), spender: account(spender), value } + .into(), + ); // Approves a value to spend that is lower than the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); // Approves a value to spend that is equal to the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, spender, value / 2), + Fungibles::approve(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); // Sets allowance to zero. assert_eq!( - Fungibles::approve(signed(owner), token, spender, 0), + Fungibles::approve(signed(owner), token, account(spender), 0), Ok(Some(WeightInfo::approve(0, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), 0); - System::assert_last_event(Event::Approval { token, owner, spender, value: 0 }.into()); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); + System::assert_last_event( + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: 0, + } + .into(), + ); }); } } @@ -239,25 +276,34 @@ fn increase_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::increase_allowance(origin, token, spender, value), + Fungibles::increase_allowance(origin, token, account(spender), value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()`. assert_noop!( - Fungibles::increase_allowance(signed(owner), token, spender, value), + Fungibles::increase_allowance(signed(owner), token, account(spender), value), AssetsError::Unknown.with_weight(AssetsWeightInfo::approve_transfer()) ); assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(0, Assets::allowance(token, &owner, &spender)); - assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); - assert_eq!(Assets::allowance(token, &owner, &spender), value); - System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); + assert_eq!(0, Assets::allowance(token, &account(owner), &account(spender))); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + System::assert_last_event( + Event::Approval { token, owner: account(owner), spender: account(spender), value } + .into(), + ); // Additive. - assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); - assert_eq!(Assets::allowance(token, &owner, &spender), value * 2); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value * 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value * 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value * 2, + } + .into(), ); }); } @@ -272,40 +318,46 @@ fn decrease_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::decrease_allowance(origin, token, spender, 0), + Fungibles::decrease_allowance(origin, token, account(spender), 0), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } assets::create_mint_and_approve(owner, token, owner, value, spender, value); - assert_eq!(Assets::allowance(token, &owner, &spender), value); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); // Check error works for `Assets::cancel_approval()`. No error test for `approve_transfer` // because it is not possible. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); // Owner balance is not changed if decreased by zero. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, spender, 0), + Fungibles::decrease_allowance(signed(owner), token, account(spender), 0), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); // "Unapproved" error is returned if the current allowance is less than amount to decrease // with. assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, spender, value * 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value * 2), AssetsError::Unapproved ); // Decrease allowance successfully. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), + Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); + assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); System::assert_last_event( - Event::Approval { token, owner, spender, value: value / 2 }.into(), + Event::Approval { + token, + owner: account(owner), + spender: account(spender), + value: value / 2, + } + .into(), ); }); } @@ -318,14 +370,19 @@ fn create_works() { let admin = ALICE; for origin in vec![root(), none()] { - assert_noop!(Fungibles::create(origin, id, admin, 100), BadOrigin); + assert_noop!(Fungibles::create(origin, id, account(admin), 100), BadOrigin); } assert!(!Assets::asset_exists(id)); - assert_ok!(Fungibles::create(signed(creator), id, admin, 100)); + assert_ok!(Fungibles::create(signed(creator), id, account(admin), 100)); assert!(Assets::asset_exists(id)); - System::assert_last_event(Event::Created { id, creator, admin }.into()); + System::assert_last_event( + Event::Created { id, creator: account(creator), admin: account(admin) }.into(), + ); // Check error works for `Assets::create()`. - assert_noop!(Fungibles::create(signed(creator), id, admin, 100), AssetsError::InUse); + assert_noop!( + Fungibles::create(signed(creator), id, account(admin), 100), + AssetsError::InUse + ); }); } @@ -336,11 +393,11 @@ fn start_destroy_works() { // Check error works for `Assets::start_destroy()`. assert_noop!(Fungibles::start_destroy(signed(ALICE), token), AssetsError::Unknown); - assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); assert_ok!(Fungibles::start_destroy(signed(ALICE), token)); // Check that the token is not live after starting the destroy process. assert_noop!( - Assets::mint(signed(ALICE), token, ALICE, 10 * UNIT), + Assets::mint(signed(ALICE), token, account(ALICE), 10 * UNIT), AssetsError::AssetNotLive ); }); @@ -359,7 +416,7 @@ fn set_metadata_works() { Fungibles::set_metadata(signed(ALICE), token, name.clone(), symbol.clone(), decimals), AssetsError::Unknown ); - assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); assert_ok!(Fungibles::set_metadata( signed(ALICE), token, @@ -398,16 +455,16 @@ fn mint_works() { // Check error works for `Assets::mint()`. assert_noop!( - Fungibles::mint(signed(from), token, to, value), + Fungibles::mint(signed(from), token, account(to), value), sp_runtime::TokenError::UnknownAsset ); - assert_ok!(Assets::create(signed(from), token, from, 1)); - let balance_before_mint = Assets::balance(token, &to); - assert_ok!(Fungibles::mint(signed(from), token, to, value)); - let balance_after_mint = Assets::balance(token, &to); + assert_ok!(Assets::create(signed(from), token, account(from), 1)); + let balance_before_mint = Assets::balance(token, &account(to)); + assert_ok!(Fungibles::mint(signed(from), token, account(to), value)); + let balance_after_mint = Assets::balance(token, &account(to)); assert_eq!(balance_after_mint, balance_before_mint + value); System::assert_last_event( - Event::Transfer { token, from: None, to: Some(to), value }.into(), + Event::Transfer { token, from: None, to: Some(account(to)), value }.into(), ); }); } @@ -423,27 +480,30 @@ fn burn_works() { // "BalanceLow" error is returned if token is not created. assert_noop!( - Fungibles::burn(signed(owner), token, from, value), + Fungibles::burn(signed(owner), token, account(from), value), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); assets::create_and_mint_to(owner, token, from, total_supply); assert_eq!(Assets::total_supply(TOKEN), total_supply); // Check error works for `Assets::burn()`. assert_ok!(Assets::freeze_asset(signed(owner), token)); - assert_noop!(Fungibles::burn(signed(owner), token, from, value), AssetsError::AssetNotLive); + assert_noop!( + Fungibles::burn(signed(owner), token, account(from), value), + AssetsError::AssetNotLive + ); assert_ok!(Assets::thaw_asset(signed(owner), token)); // "BalanceLow" error is returned if the balance is less than amount to burn. assert_noop!( - Fungibles::burn(signed(owner), token, from, total_supply * 2), + Fungibles::burn(signed(owner), token, account(from), total_supply * 2), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); - let balance_before_burn = Assets::balance(token, &from); - assert_ok!(Fungibles::burn(signed(owner), token, from, value)); + let balance_before_burn = Assets::balance(token, &account(from)); + assert_ok!(Fungibles::burn(signed(owner), token, account(from), value)); assert_eq!(Assets::total_supply(TOKEN), total_supply - value); - let balance_after_burn = Assets::balance(token, &from); + let balance_after_burn = Assets::balance(token, &account(from)); assert_eq!(balance_after_burn, balance_before_burn - value); System::assert_last_event( - Event::Transfer { token, from: Some(from), to: None, value }.into(), + Event::Transfer { token, from: Some(account(from)), to: None, value }.into(), ); }); } @@ -470,17 +530,17 @@ fn balance_of_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), ReadResult::BalanceOf(Default::default()) ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, value); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), ReadResult::BalanceOf(value) ); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }).encode(), - Assets::balance(TOKEN, ALICE).encode(), + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + Assets::balance(TOKEN, account(ALICE)).encode(), ); }); } @@ -490,17 +550,30 @@ fn allowance_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }), ReadResult::Allowance(Default::default()) ); assets::create_mint_and_approve(ALICE, TOKEN, ALICE, value * 2, BOB, value); assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }), ReadResult::Allowance(value) ); assert_eq!( - Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }).encode(), - Assets::allowance(TOKEN, &ALICE, &BOB).encode(), + Fungibles::read(Allowance { + token: TOKEN, + owner: account(ALICE), + spender: account(BOB) + }) + .encode(), + Assets::allowance(TOKEN, &account(ALICE), &account(BOB)).encode(), ); }); } @@ -534,7 +607,7 @@ fn token_metadata_works() { fn token_exists_works() { new_test_ext().execute_with(|| { assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(false)); - assert_ok!(Assets::create(signed(ALICE), TOKEN, ALICE, 1)); + assert_ok!(Assets::create(signed(ALICE), TOKEN, account(ALICE), 1)); assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(true)); assert_eq!( Fungibles::read(TokenExists(TOKEN)).encode(), @@ -543,8 +616,8 @@ fn token_exists_works() { }); } -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) +fn signed(account_id: u8) -> RuntimeOrigin { + RuntimeOrigin::signed(account(account_id)) } fn root() -> RuntimeOrigin { @@ -559,36 +632,31 @@ fn none() -> RuntimeOrigin { mod assets { use super::*; - pub(super) fn create_and_mint_to( - owner: AccountId, - token: TokenId, - to: AccountId, - value: Balance, - ) { - assert_ok!(Assets::create(signed(owner), token, owner, 1)); - assert_ok!(Assets::mint(signed(owner), token, to, value)); + pub(super) fn create_and_mint_to(owner: u8, token: TokenId, to: u8, value: Balance) { + assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); + assert_ok!(Assets::mint(signed(owner), token, account(to), value)); } pub(super) fn create_mint_and_approve( - owner: AccountId, + owner: u8, token: TokenId, - to: AccountId, + to: u8, mint: Balance, - spender: AccountId, + spender: u8, approve: Balance, ) { create_and_mint_to(owner, token, to, mint); - assert_ok!(Assets::approve_transfer(signed(to), token, spender, approve,)); + assert_ok!(Assets::approve_transfer(signed(to), token, account(spender), approve,)); } pub(super) fn create_and_set_metadata( - owner: AccountId, + owner: u8, token: TokenId, name: Vec, symbol: Vec, decimals: u8, ) { - assert_ok!(Assets::create(signed(owner), token, owner, 1)); + assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); assert_ok!(Assets::set_metadata(signed(owner), token, name, symbol, decimals)); } } @@ -613,11 +681,11 @@ mod read_weights { fn new() -> Self { Self { total_supply: Fungibles::weight(&TotalSupply(TOKEN)), - balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: ALICE }), + balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: account(ALICE) }), allowance: Fungibles::weight(&Allowance { token: TOKEN, - owner: ALICE, - spender: BOB, + owner: account(ALICE), + spender: account(BOB), }), token_name: Fungibles::weight(&TokenName(TOKEN)), token_symbol: Fungibles::weight(&TokenSymbol(TOKEN)), @@ -699,15 +767,15 @@ mod ensure_codec_indexes { [ (TotalSupply::(Default::default()), 0u8, "TotalSupply"), ( - BalanceOf:: { token: Default::default(), owner: Default::default() }, + BalanceOf:: { token: Default::default(), owner: account(Default::default()) }, 1, "BalanceOf", ), ( Allowance:: { token: Default::default(), - owner: Default::default(), - spender: Default::default(), + owner: account(Default::default()), + spender: account(Default::default()), }, 2, "Allowance", @@ -731,7 +799,7 @@ mod ensure_codec_indexes { ( transfer { token: Default::default(), - to: Default::default(), + to: account(Default::default()), value: Default::default(), }, 3u8, @@ -740,8 +808,8 @@ mod ensure_codec_indexes { ( transfer_from { token: Default::default(), - from: Default::default(), - to: Default::default(), + from: account(Default::default()), + to: account(Default::default()), value: Default::default(), }, 4, @@ -750,7 +818,7 @@ mod ensure_codec_indexes { ( approve { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 5, @@ -759,7 +827,7 @@ mod ensure_codec_indexes { ( increase_allowance { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 6, @@ -768,7 +836,7 @@ mod ensure_codec_indexes { ( decrease_allowance { token: Default::default(), - spender: Default::default(), + spender: account(Default::default()), value: Default::default(), }, 7, @@ -777,7 +845,7 @@ mod ensure_codec_indexes { ( create { id: Default::default(), - admin: Default::default(), + admin: account(Default::default()), min_balance: Default::default(), }, 11, @@ -798,7 +866,7 @@ mod ensure_codec_indexes { ( mint { token: Default::default(), - account: Default::default(), + account: account(Default::default()), value: Default::default(), }, 19, @@ -807,7 +875,7 @@ mod ensure_codec_indexes { ( burn { token: Default::default(), - account: Default::default(), + account: account(Default::default()), value: Default::default(), }, 20, diff --git a/pallets/api/src/lib.rs b/pallets/api/src/lib.rs index 65b69598..a05d0cf9 100644 --- a/pallets/api/src/lib.rs +++ b/pallets/api/src/lib.rs @@ -8,6 +8,7 @@ pub mod fungibles; pub mod messaging; #[cfg(test)] mod mock; +pub mod nonfungibles; /// Trait for performing reads of runtime state. pub trait Read { diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index 42c8bf0e..920d590f 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -1,25 +1,28 @@ use frame_support::{ derive_impl, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, Everything}, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Everything}, }; use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_nfts::PalletFeatures; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, }; -pub(crate) const ALICE: AccountId = 1; -pub(crate) const BOB: AccountId = 2; -pub(crate) const CHARLIE: AccountId = 3; +pub(crate) const ALICE: u8 = 1; +pub(crate) const BOB: u8 = 2; +pub(crate) const CHARLIE: u8 = 3; pub(crate) const INIT_AMOUNT: Balance = 100_000_000 * UNIT; pub(crate) const UNIT: Balance = 10_000_000_000; type Block = frame_system::mocking::MockBlock; -pub(crate) type AccountId = u64; +pub(crate) type AccountId = ::AccountId; pub(crate) type Balance = u128; // For terminology in tests. pub(crate) type TokenId = u32; +type Signature = MultiSignature; +type AccountPublic = ::Signer; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -29,6 +32,8 @@ frame_support::construct_runtime!( Assets: pallet_assets::, Balances: pallet_balances, Fungibles: crate::fungibles, + Nfts: pallet_nfts, + NonFungibles: crate::nonfungibles } ); @@ -91,10 +96,10 @@ impl pallet_assets::Config for Test { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); type CallbackHandle = (); - type CreateOrigin = AsEnsureOriginWithArg>; + type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; type Extra = (); - type ForceOrigin = EnsureRoot; + type ForceOrigin = EnsureRoot; type Freezer = (); type MetadataDepositBase = ConstU128<1>; type MetadataDepositPerByte = ConstU128<1>; @@ -110,13 +115,62 @@ impl crate::fungibles::Config for Test { type WeightInfo = (); } +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Test { + type ApprovalsLimit = ConstU32<10>; + type AttributeDepositBase = ConstU128<1>; + type CollectionDeposit = ConstU128<2>; + type CollectionId = u32; + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type DepositPerByte = ConstU128<1>; + type Features = Features; + type ForceOrigin = frame_system::EnsureRoot; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type ItemAttributesApprovalsLimit = ConstU32<2>; + type ItemDeposit = ConstU128<1>; + type ItemId = u32; + type KeyLimit = ConstU32<50>; + type Locker = (); + type MaxAttributesPerCall = ConstU32<2>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxTips = ConstU32<10>; + type MetadataDepositBase = ConstU128<1>; + /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. + type OffchainPublic = AccountPublic; + /// Off-chain = signature On-chain - therefore no conversion needed. + /// It needs to be From for benchmarking. + type OffchainSignature = Signature; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type WeightInfo = (); +} + +impl crate::nonfungibles::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +/// Initialize a new account ID. +pub(crate) fn account(id: u8) -> AccountId { + [id; 32].into() +} + pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() .expect("Frame system builds valid default genesis config"); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INIT_AMOUNT), (BOB, INIT_AMOUNT), (CHARLIE, INIT_AMOUNT)], + balances: vec![ + (account(ALICE), INIT_AMOUNT), + (account(BOB), INIT_AMOUNT), + (account(CHARLIE), INIT_AMOUNT), + ], } .assimilate_storage(&mut t) .expect("Pallet balances storage can be assimilated"); diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs new file mode 100644 index 00000000..cac0cbba --- /dev/null +++ b/pallets/api/src/nonfungibles/mod.rs @@ -0,0 +1,260 @@ +//! The non-fungibles pallet offers a streamlined interface for interacting with non-fungible +//! assets. The goal is to provide a simplified, consistent API that adheres to standards in the +//! smart contract space. + +use frame_support::traits::nonfungibles_v2::InspectEnumerable; +pub use pallet::*; +use pallet_nfts::WeightInfo; +use sp_runtime::traits::StaticLookup; + +#[cfg(test)] +mod tests; +mod types; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + use sp_std::vec::Vec; + use types::{AccountIdOf, CollectionIdOf, ItemDetails, ItemIdOf, NftsOf, NftsWeightInfoOf}; + + use super::*; + + /// State reads for the fungibles API with required input. + #[derive(Encode, Decode, Debug, MaxEncodedLen)] + #[repr(u8)] + #[allow(clippy::unnecessary_cast)] + pub enum Read { + /// Returns the owner of an item. + #[codec(index = 0)] + OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, + /// Returns the owner of a collection. + #[codec(index = 1)] + CollectionOwner(CollectionIdOf), + /// Number of items existing in a concrete collection. + #[codec(index = 2)] + TotalSupply(CollectionIdOf), + /// Returns the total number of items in the collection owned by the account. + #[codec(index = 3)] + BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, + /// Returns the details of a collection. + #[codec(index = 4)] + Collection(CollectionIdOf), + /// Returns the details of an item. + #[codec(index = 5)] + Item { collection: CollectionIdOf, item: ItemIdOf }, + /// Whether a spender is allowed to transfer an item or items from owner. + #[codec(index = 6)] + Allowance { spender: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf }, + } + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_nfts::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// The events that can be emitted. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event emitted when allowance by `owner` to `spender` canceled. + CancelApproval { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The beneficiary of the allowance. + spender: AccountIdOf, + }, + /// Event emitted when allowance by `owner` to `spender` changes. + Approval { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The owner providing the allowance. + owner: AccountIdOf, + /// The beneficiary of the allowance. + spender: AccountIdOf, + }, + /// Event emitted when new item is minted to the account. + Mint { + /// The owner of the item. + to: AccountIdOf, + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + }, + /// Event emitted when item is burned. + Burn { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + }, + /// Event emitted when an item transfer occurs. + Transfer { + /// The collection ID. + collection: CollectionIdOf, + /// the item ID. + item: ItemIdOf, + /// The source of the transfer. + from: AccountIdOf, + /// The recipient of the transfer. + to: AccountIdOf, + }, + } + + #[pallet::call] + impl Pallet { + /// Create a new non-fungible token to the collection. + /// + /// # Parameters + /// - `to` - The owner of the collection item. + /// - `collection` - The collection ID. + /// - `item` - The item ID. + #[pallet::call_index(0)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to.clone()), None)?; + Self::deposit_event(Event::Mint { to, collection, item }); + Ok(()) + } + + /// Destroy a new non-fungible token to the collection. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + #[pallet::call_index(1)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Burn { collection, item }); + Ok(()) + } + + /// Transfer a token from one account to the another account. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `to` - The recipient account. + #[pallet::call_index(2)] + #[pallet::weight(NftsWeightInfoOf::::transfer())] + pub fn transfer( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + to: AccountIdOf, + ) -> DispatchResult { + let from = ensure_signed(origin.clone())?; + NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; + Self::deposit_event(Event::Transfer { from, to, collection, item }); + Ok(()) + } + + /// Delegate a permission to perform actions on the collection item to an account. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `spender` - The account that is allowed to transfer the collection item. + #[pallet::call_index(3)] + #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] + pub fn approve( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + NftsOf::::approve_transfer( + origin, + collection, + item, + T::Lookup::unlookup(spender.clone()), + None, + )?; + Self::deposit_event(Event::Approval { collection, item, spender, owner }); + Ok(()) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// # Parameters + /// - `collection` - The collection ID. + /// - `item` - The item ID. + /// - `spender` - The account that is revoked permission to transfer the collection item. + #[pallet::call_index(4)] + #[pallet::weight(NftsWeightInfoOf::::cancel_approval())] + pub fn cancel_approval( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> DispatchResult { + NftsOf::::cancel_approval( + origin, + collection, + item, + T::Lookup::unlookup(spender.clone()), + )?; + Self::deposit_event(Event::CancelApproval { collection, item, spender }); + Ok(()) + } + } + + impl Pallet { + /// Reads fungible asset state based on the provided value. + /// + /// This function matches the value to determine the type of state query and returns the + /// encoded result. + /// + /// # Parameter + /// - `value` - An instance of `Read`, which specifies the type of state query and the + /// associated parameters. + pub fn read_state(value: Read) -> Vec { + use Read::*; + match value { + OwnerOf { collection, item } => NftsOf::::owner(collection, item).encode(), + CollectionOwner(collection) => NftsOf::::collection_owner(collection).encode(), + TotalSupply(collection) => (NftsOf::::items(&collection).count() as u8).encode(), + Collection(collection) => pallet_nfts::Collection::::get(&collection).encode(), + Item { collection, item } => pallet_nfts::Item::::get(collection, item).encode(), + Allowance { collection, item, spender } => + Self::allowance(collection, item, spender).encode(), + BalanceOf { collection, owner } => + (NftsOf::::owned_in_collection(&collection, &owner).count() as u8).encode(), + } + } + + /// Check if the `spender` is approved to transfer the collection item + pub(super) fn allowance( + collection: CollectionIdOf, + item: ItemIdOf, + spender: AccountIdOf, + ) -> bool { + let data = pallet_nfts::Item::::get(collection, item).encode(); + if let Ok(detail) = ItemDetails::::decode(&mut data.as_slice()) { + return detail.approvals.contains_key(&spender); + } + false + } + } +} diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs new file mode 100644 index 00000000..9e9da5c2 --- /dev/null +++ b/pallets/api/src/nonfungibles/tests.rs @@ -0,0 +1,183 @@ +use codec::Encode; +use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; + +use super::types::*; +use crate::{ + mock::*, + nonfungibles::{Event, Read::*}, +}; + +const ITEM: u32 = 1; + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let collection = create_collection(owner.clone()); + // Successfully mint a new collection item. + assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); + System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + System::assert_last_event(Event::Burn { collection, item }.into()); + }); +} + +#[test] +fn transfer() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let dest = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + System::assert_last_event( + Event::Transfer { collection, item, from: owner, to: dest }.into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully approve `spender` to transfer the collection item. + assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); + System::assert_last_event( + Event::Approval { collection, item, owner, spender: spender.clone() }.into(), + ); + // Successfully transfer the item by the delegated account `spender`. + assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + // Successfully cancel the transfer approval of `spender` by `owner`. + assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); + // Failed to transfer the item by `spender` without permission. + assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + }); +} + +#[test] +fn owner_of_works() {} + +#[test] +fn collection_owner_works() { + new_test_ext().execute_with(|| { + let collection = create_collection(account(ALICE)); + assert_eq!( + NonFungibles::read_state(CollectionOwner(collection)), + Nfts::collection_owner(collection).encode() + ); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read_state(TotalSupply(collection)), + (Nfts::items(&collection).count() as u8).encode() + ); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read_state(Collection(collection)), + pallet_nfts::Collection::::get(&collection).encode(), + ); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, _) = create_collection_mint(owner.clone(), ITEM); + assert_eq!( + NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), + (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() + ); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + assert_eq!( + NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), + super::Pallet::::allowance(collection, item, spender).encode() + ); + }); +} + +fn signed(account: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account) +} + +fn create_collection_mint_and_approve( + owner: AccountIdOf, + item: ItemIdOf, + spender: AccountIdOf, +) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner.clone(), item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); + (collection, item) +} + +fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner.clone()); + assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); + (collection, item) +} + +fn create_collection(owner: AccountIdOf) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner.clone()), + owner.clone(), + collection_config_with_all_settings_enabled() + )); + next_id +} + +fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs new file mode 100644 index 00000000..0174ef77 --- /dev/null +++ b/pallets/api/src/nonfungibles/types.rs @@ -0,0 +1,61 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_runtime::BoundedBTreeMap; + +use super::Config; + +pub(super) type AccountIdOf = ::AccountId; + +pub(super) type NftsOf = pallet_nfts::Pallet; + +/// Weight information for extrinsics in this pallet. +pub(super) type NftsWeightInfoOf = ::WeightInfo; + +/// A type alias for the collection ID. +pub(super) type CollectionIdOf = + as Inspect<::AccountId>>::CollectionId; + +/// A type alias for the collection item ID. +pub(super) type ItemIdOf = + as Inspect<::AccountId>>::ItemId; + +// TODO: Even though this serves the `allowance` method, it creates the maintenance cost. + +/// A type that holds the deposit for a single item. +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; + +/// A type alias for handling balance deposits. +pub(super) type DepositBalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +/// A type alias for keeping track of approvals used by a single item. +pub(super) type ApprovalsOf = BoundedBTreeMap< + AccountIdOf, + Option>, + ::ApprovalsLimit, +>; + +/// Information concerning the ownership of a single unique item. +#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub(super) struct ItemDetails { + /// The owner of this item. + pub(super) owner: AccountIdOf, + /// The approved transferrer of this item, if one is set. + pub(super) approvals: ApprovalsOf, + /// The amount held in the pallet's default account for this item. Free-hold items will have + /// this as zero. + pub(super) deposit: ItemDepositOf, +} + +/// Information about the reserved item deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub struct ItemDeposit { + /// A depositor account. + pub(super) account: AccountId, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} From ca14de7d5d741652eca9fa5e571f7550fc93ad8b Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:47:40 +0700 Subject: [PATCH 39/79] feat: add nonfungibles implementation --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b42da35a..b4ff50b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -7380,7 +7380,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "pallet-ismp", - "pallet-nfts", + "pallet-nfts 31.0.0", "parity-scale-codec", "pop-chain-extension", "scale-info", From d8a15f8e0bdd0d74af8dfe045316278f943157c9 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:12:39 +0700 Subject: [PATCH 40/79] chore: add nfts pallet --- pallets/api/src/nonfungibles/mod.rs | 171 +++++++++--- pallets/api/src/nonfungibles/tests.rs | 367 +++++++++++++------------- pallets/api/src/nonfungibles/types.rs | 57 ++-- pallets/nfts/Cargo.toml | 1 - 4 files changed, 356 insertions(+), 240 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index cac0cbba..c5e8cffb 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -2,10 +2,9 @@ //! assets. The goal is to provide a simplified, consistent API that adheres to standards in the //! smart contract space. -use frame_support::traits::nonfungibles_v2::InspectEnumerable; pub use pallet::*; use pallet_nfts::WeightInfo; -use sp_runtime::traits::StaticLookup; +use sp_runtime::{traits::StaticLookup, RuntimeDebug}; #[cfg(test)] mod tests; @@ -16,12 +15,16 @@ pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; use sp_std::vec::Vec; - use types::{AccountIdOf, CollectionIdOf, ItemDetails, ItemIdOf, NftsOf, NftsWeightInfoOf}; + use types::{ + AccountIdOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, + NftsOf, NftsWeightInfoOf, + }; use super::*; - /// State reads for the fungibles API with required input. + /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -45,7 +48,41 @@ pub mod pallet { Item { collection: CollectionIdOf, item: ItemIdOf }, /// Whether a spender is allowed to transfer an item or items from owner. #[codec(index = 6)] - Allowance { spender: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf }, + Allowance { + collection: CollectionIdOf, + owner: AccountIdOf, + operator: AccountIdOf, + item: Option>, + }, + } + + /// Results of state reads for the non-fungibles API. + #[derive(Debug)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + pub enum ReadResult { + OwnerOf(Option>), + CollectionOwner(Option>), + TotalSupply(u32), + BalanceOf(BalanceOf), + Collection(Option>), + Item(Option>), + Allowance(bool), + } + + impl ReadResult { + /// Encodes the result. + pub fn encode(&self) -> Vec { + use ReadResult::*; + match self { + OwnerOf(result) => result.encode(), + CollectionOwner(result) => result.encode(), + TotalSupply(result) => result.encode(), + BalanceOf(result) => result.encode(), + Collection(result) => result.encode(), + Item(result) => result.encode(), + Allowance(result) => result.encode(), + } + } } /// Configure the pallet by specifying the parameters and types on which it depends. @@ -55,6 +92,32 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + #[pallet::storage] + type AccountBalance = StorageNMap< + Key = ( + // Collection ID + NMapKey>, + // Collection Owner ID + NMapKey>, + ), + Value = BalanceOf, + QueryKind = ValueQuery, + >; + + #[pallet::storage] + type Allowances = StorageNMap< + Key = ( + // Collection ID + NMapKey>, + // Collection Owner ID + NMapKey>, + // Collection Operator ID + NMapKey>, + ), + Value = bool, + QueryKind = ValueQuery, + >; + #[pallet::pallet] pub struct Pallet(_); @@ -221,40 +284,86 @@ pub mod pallet { } impl Pallet { - /// Reads fungible asset state based on the provided value. - /// - /// This function matches the value to determine the type of state query and returns the - /// encoded result. - /// - /// # Parameter - /// - `value` - An instance of `Read`, which specifies the type of state query and the - /// associated parameters. - pub fn read_state(value: Read) -> Vec { - use Read::*; - match value { - OwnerOf { collection, item } => NftsOf::::owner(collection, item).encode(), - CollectionOwner(collection) => NftsOf::::collection_owner(collection).encode(), - TotalSupply(collection) => (NftsOf::::items(&collection).count() as u8).encode(), - Collection(collection) => pallet_nfts::Collection::::get(&collection).encode(), - Item { collection, item } => pallet_nfts::Item::::get(collection, item).encode(), - Allowance { collection, item, spender } => - Self::allowance(collection, item, spender).encode(), - BalanceOf { collection, owner } => - (NftsOf::::owned_in_collection(&collection, &owner).count() as u8).encode(), - } + /// Check if the `spender` is approved to transfer the collection item. + pub(super) fn allowance( + collection: CollectionIdOf, + owner: AccountIdOf, + operator: AccountIdOf, + maybe_item: Option>, + ) -> bool { + // Check if has a permission to transfer all collection items. + Allowances::::get((collection, owner, operator.clone())) || + maybe_item + .and_then(|item| Some(Self::allowance_item(collection, operator, item))) + .unwrap_or(false) } - /// Check if the `spender` is approved to transfer the collection item - pub(super) fn allowance( + // Check the permission for the single item. + pub(super) fn allowance_item( collection: CollectionIdOf, + operator: AccountIdOf, item: ItemIdOf, - spender: AccountIdOf, ) -> bool { let data = pallet_nfts::Item::::get(collection, item).encode(); - if let Ok(detail) = ItemDetails::::decode(&mut data.as_slice()) { - return detail.approvals.contains_key(&spender); + if let Ok(detail) = ItemDetailsFor::::decode(&mut data.as_slice()) { + return detail.approvals.contains_key(&operator); } false } } + + impl crate::Read for Pallet { + /// The type of read requested. + type Read = Read; + /// The type or result returned. + type Result = ReadResult; + + /// Determines the weight of the requested read, used to charge the appropriate weight + /// before the read is performed. + /// + /// # Parameters + /// - `request` - The read request. + fn weight(_request: &Self::Read) -> Weight { + Default::default() + } + + /// Performs the requested read and returns the result. + /// + /// # Parameters + /// - `request` - The read request. + fn read(value: Self::Read) -> Self::Result { + use Read::*; + match value { + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), + CollectionOwner(collection) => + ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), + TotalSupply(collection) => { + let data = pallet_nfts::Collection::::get(collection).encode(); + ReadResult::TotalSupply( + CollectionDetailsFor::::decode(&mut data.as_slice()) + .map(|detail| detail.items) + .unwrap_or_default(), + ) + }, + Collection(collection) => { + let data = pallet_nfts::Collection::::get(collection).encode(); + ReadResult::Collection( + Option::>::decode(&mut data.as_slice()) + .unwrap_or(None), + ) + }, + Item { collection, item } => { + let data = pallet_nfts::Item::::get(collection, item).encode(); + ReadResult::Item( + Option::>::decode(&mut data.as_slice()).unwrap_or(None), + ) + }, + Allowance { collection, owner, operator, item } => + ReadResult::Allowance(Self::allowance(collection, owner, operator, item)), + BalanceOf { collection, owner } => + ReadResult::BalanceOf(AccountBalance::::get((collection, owner))), + } + } + } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 9e9da5c2..54d85516 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,183 +1,184 @@ -use codec::Encode; -use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; -use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; - -use super::types::*; -use crate::{ - mock::*, - nonfungibles::{Event, Read::*}, -}; - -const ITEM: u32 = 1; - -#[test] -fn mint_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let collection = create_collection(owner.clone()); - // Successfully mint a new collection item. - assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); - System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); - }); -} - -#[test] -fn burn_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); - System::assert_last_event(Event::Burn { collection, item }.into()); - }); -} - -#[test] -fn transfer() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let dest = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); - System::assert_last_event( - Event::Transfer { collection, item, from: owner, to: dest }.into(), - ); - }); -} - -#[test] -fn approve_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully approve `spender` to transfer the collection item. - assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); - System::assert_last_event( - Event::Approval { collection, item, owner, spender: spender.clone() }.into(), - ); - // Successfully transfer the item by the delegated account `spender`. - assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); - }); -} - -#[test] -fn cancel_approval_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - // Successfully cancel the transfer approval of `spender` by `owner`. - assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); - // Failed to transfer the item by `spender` without permission. - assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); - }); -} - -#[test] -fn owner_of_works() {} - -#[test] -fn collection_owner_works() { - new_test_ext().execute_with(|| { - let collection = create_collection(account(ALICE)); - assert_eq!( - NonFungibles::read_state(CollectionOwner(collection)), - Nfts::collection_owner(collection).encode() - ); - }); -} - -#[test] -fn total_supply_works() { - new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); - assert_eq!( - NonFungibles::read_state(TotalSupply(collection)), - (Nfts::items(&collection).count() as u8).encode() - ); - }); -} - -#[test] -fn collection_works() { - new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); - assert_eq!( - NonFungibles::read_state(Collection(collection)), - pallet_nfts::Collection::::get(&collection).encode(), - ); - }); -} - -#[test] -fn balance_of_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, _) = create_collection_mint(owner.clone(), ITEM); - assert_eq!( - NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), - (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() - ); - }); -} - -#[test] -fn allowance_works() { - new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - assert_eq!( - NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), - super::Pallet::::allowance(collection, item, spender).encode() - ); - }); -} - -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) -} - -fn create_collection_mint_and_approve( - owner: AccountIdOf, - item: ItemIdOf, - spender: AccountIdOf, -) -> (u32, u32) { - let (collection, item) = create_collection_mint(owner.clone(), item); - assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); - (collection, item) -} - -fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { - let collection = create_collection(owner.clone()); - assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); - (collection, item) -} - -fn create_collection(owner: AccountIdOf) -> u32 { - let next_id = next_collection_id(); - assert_ok!(Nfts::create( - signed(owner.clone()), - owner.clone(), - collection_config_with_all_settings_enabled() - )); - next_id -} - -fn next_collection_id() -> u32 { - pallet_nfts::NextCollectionId::::get().unwrap_or_default() -} - -fn collection_config_with_all_settings_enabled( -) -> CollectionConfig, CollectionIdOf> { - CollectionConfig { - settings: CollectionSettings::all_enabled(), - max_supply: None, - mint_settings: MintSettings::default(), - } -} +// TODO +// use codec::Encode; +// use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +// use frame_system::pallet_prelude::BlockNumberFor; +// use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; + +// use super::types::*; +// use crate::{ +// mock::*, +// nonfungibles::{Event, Read::*}, +// }; + +// const ITEM: u32 = 1; + +// #[test] +// fn mint_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let collection = create_collection(owner.clone()); +// // Successfully mint a new collection item. +// assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); +// System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); +// }); +// } + +// #[test] +// fn burn_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully burn an existing new collection item. +// assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); +// System::assert_last_event(Event::Burn { collection, item }.into()); +// }); +// } + +// #[test] +// fn transfer() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let dest = account(BOB); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully burn an existing new collection item. +// assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); +// System::assert_last_event( +// Event::Transfer { collection, item, from: owner, to: dest }.into(), +// ); +// }); +// } + +// #[test] +// fn approve_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = create_collection_mint(owner.clone(), ITEM); +// // Successfully approve `spender` to transfer the collection item. +// assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); +// System::assert_last_event( +// Event::Approval { collection, item, owner, spender: spender.clone() }.into(), +// ); +// // Successfully transfer the item by the delegated account `spender`. +// assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); +// }); +// } + +// #[test] +// fn cancel_approval_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = +// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); +// // Successfully cancel the transfer approval of `spender` by `owner`. +// assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); +// // Failed to transfer the item by `spender` without permission. +// assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); +// }); +// } + +// #[test] +// fn owner_of_works() {} + +// #[test] +// fn collection_owner_works() { +// new_test_ext().execute_with(|| { +// let collection = create_collection(account(ALICE)); +// assert_eq!( +// NonFungibles::read_state(CollectionOwner(collection)), +// Nfts::collection_owner(collection).encode() +// ); +// }); +// } + +// #[test] +// fn total_supply_works() { +// new_test_ext().execute_with(|| { +// let (collection, _) = create_collection_mint(account(ALICE), ITEM); +// assert_eq!( +// NonFungibles::read_state(TotalSupply(collection)), +// (Nfts::items(&collection).count() as u8).encode() +// ); +// }); +// } + +// #[test] +// fn collection_works() { +// new_test_ext().execute_with(|| { +// let (collection, _) = create_collection_mint(account(ALICE), ITEM); +// assert_eq!( +// NonFungibles::read_state(Collection(collection)), +// pallet_nfts::Collection::::get(&collection).encode(), +// ); +// }); +// } + +// #[test] +// fn balance_of_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let (collection, _) = create_collection_mint(owner.clone(), ITEM); +// assert_eq!( +// NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), +// (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() +// ); +// }); +// } + +// #[test] +// fn allowance_works() { +// new_test_ext().execute_with(|| { +// let owner = account(ALICE); +// let spender = account(BOB); +// let (collection, item) = +// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); +// assert_eq!( +// NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), +// super::Pallet::::allowance(collection, item, spender).encode() +// ); +// }); +// } + +// fn signed(account: AccountId) -> RuntimeOrigin { +// RuntimeOrigin::signed(account) +// } + +// fn create_collection_mint_and_approve( +// owner: AccountIdOf, +// item: ItemIdOf, +// spender: AccountIdOf, +// ) -> (u32, u32) { +// let (collection, item) = create_collection_mint(owner.clone(), item); +// assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); +// (collection, item) +// } + +// fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { +// let collection = create_collection(owner.clone()); +// assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); +// (collection, item) +// } + +// fn create_collection(owner: AccountIdOf) -> u32 { +// let next_id = next_collection_id(); +// assert_ok!(Nfts::create( +// signed(owner.clone()), +// owner.clone(), +// collection_config_with_all_settings_enabled() +// )); +// next_id +// } + +// fn next_collection_id() -> u32 { +// pallet_nfts::NextCollectionId::::get().unwrap_or_default() +// } + +// fn collection_config_with_all_settings_enabled( +// ) -> CollectionConfig, CollectionIdOf> { +// CollectionConfig { +// settings: CollectionSettings::all_enabled(), +// max_supply: None, +// mint_settings: MintSettings::default(), +// } +// } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index 0174ef77..a55a8ec2 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -4,34 +4,22 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_runtime::BoundedBTreeMap; -use super::Config; +use super::*; pub(super) type AccountIdOf = ::AccountId; - pub(super) type NftsOf = pallet_nfts::Pallet; - /// Weight information for extrinsics in this pallet. pub(super) type NftsWeightInfoOf = ::WeightInfo; - /// A type alias for the collection ID. pub(super) type CollectionIdOf = - as Inspect<::AccountId>>::CollectionId; - + as Inspect<::AccountId>>::CollectionId; /// A type alias for the collection item ID. pub(super) type ItemIdOf = - as Inspect<::AccountId>>::ItemId; - -// TODO: Even though this serves the `allowance` method, it creates the maintenance cost. - -/// A type that holds the deposit for a single item. -pub(super) type ItemDepositOf = - ItemDeposit, ::AccountId>; - + as Inspect<::AccountId>>::ItemId; /// A type alias for handling balance deposits. -pub(super) type DepositBalanceOf = <::Currency as Currency< +pub(super) type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; - /// A type alias for keeping track of approvals used by a single item. pub(super) type ApprovalsOf = BoundedBTreeMap< AccountIdOf, @@ -39,23 +27,42 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< ::ApprovalsLimit, >; +pub(super) type ItemDetailsFor = ItemDetails, BalanceOf, ApprovalsOf>; +pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; + /// Information concerning the ownership of a single unique item. -#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -pub(super) struct ItemDetails { +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemDetails { /// The owner of this item. - pub(super) owner: AccountIdOf, + pub owner: AccountId, /// The approved transferrer of this item, if one is set. - pub(super) approvals: ApprovalsOf, + pub 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: ItemDepositOf, + pub deposit: Deposit, } - /// Information about the reserved item deposit. -#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemDeposit { /// A depositor account. - pub(super) account: AccountId, + account: AccountId, /// An amount that gets reserved. - pub(super) amount: DepositBalance, + amount: DepositBalance, +} +/// Information about a collection. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: DepositBalance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, } diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index 19d35803..b5639547 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -9,7 +9,6 @@ readme = "README.md" repository.workspace = true version = "31.0.0" - [package.metadata.docs.rs] targets = [ "x86_64-unknown-linux-gnu" ] From 37cd4bc28d3a1b4405c082a364d512fc15c18896 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:23:52 +0700 Subject: [PATCH 41/79] feat: add new storage items to pallet-nfts --- pallets/api/src/nonfungibles/mod.rs | 101 +++--------------- pallets/api/src/nonfungibles/types.rs | 68 +++--------- pallets/nfts/src/common_functions.rs | 7 +- pallets/nfts/src/features/approvals.rs | 85 ++++++++++++++- .../src/features/create_delete_collection.rs | 3 + .../nfts/src/features/create_delete_item.rs | 6 ++ pallets/nfts/src/features/transfer.rs | 8 ++ pallets/nfts/src/lib.rs | 88 ++++++++++++++- 8 files changed, 220 insertions(+), 146 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index c5e8cffb..cf2b14e7 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -4,7 +4,7 @@ pub use pallet::*; use pallet_nfts::WeightInfo; -use sp_runtime::{traits::StaticLookup, RuntimeDebug}; +use sp_runtime::traits::StaticLookup; #[cfg(test)] mod tests; @@ -16,8 +16,8 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_std::vec::Vec; use types::{ - AccountIdOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, - NftsOf, NftsWeightInfoOf, + AccountIdOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, NftsOf, + NftsWeightInfoOf, }; use super::*; @@ -63,7 +63,7 @@ pub mod pallet { OwnerOf(Option>), CollectionOwner(Option>), TotalSupply(u32), - BalanceOf(BalanceOf), + BalanceOf(u32), Collection(Option>), Item(Option>), Allowance(bool), @@ -92,32 +92,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::storage] - type AccountBalance = StorageNMap< - Key = ( - // Collection ID - NMapKey>, - // Collection Owner ID - NMapKey>, - ), - Value = BalanceOf, - QueryKind = ValueQuery, - >; - - #[pallet::storage] - type Allowances = StorageNMap< - Key = ( - // Collection ID - NMapKey>, - // Collection Owner ID - NMapKey>, - // Collection Operator ID - NMapKey>, - ), - Value = bool, - QueryKind = ValueQuery, - >; - #[pallet::pallet] pub struct Pallet(_); @@ -283,35 +257,6 @@ pub mod pallet { } } - impl Pallet { - /// Check if the `spender` is approved to transfer the collection item. - pub(super) fn allowance( - collection: CollectionIdOf, - owner: AccountIdOf, - operator: AccountIdOf, - maybe_item: Option>, - ) -> bool { - // Check if has a permission to transfer all collection items. - Allowances::::get((collection, owner, operator.clone())) || - maybe_item - .and_then(|item| Some(Self::allowance_item(collection, operator, item))) - .unwrap_or(false) - } - - // Check the permission for the single item. - pub(super) fn allowance_item( - collection: CollectionIdOf, - operator: AccountIdOf, - item: ItemIdOf, - ) -> bool { - let data = pallet_nfts::Item::::get(collection, item).encode(); - if let Ok(detail) = ItemDetailsFor::::decode(&mut data.as_slice()) { - return detail.approvals.contains_key(&operator); - } - false - } - } - impl crate::Read for Pallet { /// The type of read requested. type Read = Read; @@ -338,31 +283,19 @@ pub mod pallet { ReadResult::OwnerOf(NftsOf::::owner(collection, item)), CollectionOwner(collection) => ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), - TotalSupply(collection) => { - let data = pallet_nfts::Collection::::get(collection).encode(); - ReadResult::TotalSupply( - CollectionDetailsFor::::decode(&mut data.as_slice()) - .map(|detail| detail.items) - .unwrap_or_default(), - ) - }, - Collection(collection) => { - let data = pallet_nfts::Collection::::get(collection).encode(); - ReadResult::Collection( - Option::>::decode(&mut data.as_slice()) - .unwrap_or(None), - ) - }, - Item { collection, item } => { - let data = pallet_nfts::Item::::get(collection, item).encode(); - ReadResult::Item( - Option::>::decode(&mut data.as_slice()).unwrap_or(None), - ) - }, - Allowance { collection, owner, operator, item } => - ReadResult::Allowance(Self::allowance(collection, owner, operator, item)), - BalanceOf { collection, owner } => - ReadResult::BalanceOf(AccountBalance::::get((collection, owner))), + TotalSupply(collection) => ReadResult::TotalSupply( + NftsOf::::collection_items(collection).unwrap_or_default(), + ), + Collection(collection) => + ReadResult::Collection(pallet_nfts::Collection::::get(collection)), + Item { collection, item } => + ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + Allowance { collection, owner, operator, item } => ReadResult::Allowance( + NftsOf::::allowance(collection, item, owner, operator).unwrap_or(false), + ), + BalanceOf { collection, owner } => ReadResult::BalanceOf( + pallet_nfts::AccountBalance::::get((collection, owner)), + ), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index a55a8ec2..f81ea535 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,68 +1,28 @@ -use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use scale_info::TypeInfo; +use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; use sp_runtime::BoundedBTreeMap; -use super::*; - -pub(super) type AccountIdOf = ::AccountId; +// Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; -/// Weight information for extrinsics in this pallet. pub(super) type NftsWeightInfoOf = ::WeightInfo; -/// A type alias for the collection ID. +// Type aliases for pallet-nfts storage items. +pub(super) type AccountIdOf = ::AccountId; +pub(super) type BalanceOf = <>::Currency as Currency< + ::AccountId, +>>::Balance; pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; -/// A type alias for the collection item ID. pub(super) type ItemIdOf = as Inspect<::AccountId>>::ItemId; -/// A type alias for handling balance deposits. -pub(super) type BalanceOf = <::Currency as Currency< - ::AccountId, ->>::Balance; -/// A type alias for keeping track of approvals used by a single item. -pub(super) type ApprovalsOf = BoundedBTreeMap< +type ApprovalsOf = BoundedBTreeMap< AccountIdOf, Option>, ::ApprovalsLimit, >; - -pub(super) type ItemDetailsFor = ItemDetails, BalanceOf, ApprovalsOf>; -pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; - -/// Information concerning the ownership of a single unique item. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ItemDetails { - /// The owner of this item. - pub owner: AccountId, - /// The approved transferrer of this item, if one is set. - pub approvals: Approvals, - /// The amount held in the pallet's default account for this item. Free-hold items will have - /// this as zero. - pub deposit: Deposit, -} -/// Information about the reserved item deposit. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ItemDeposit { - /// A depositor account. - account: AccountId, - /// An amount that gets reserved. - amount: DepositBalance, -} -/// Information about a collection. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct CollectionDetails { - /// Collection's owner. - pub owner: AccountId, - /// The total balance deposited by the owner for all the storage data associated with this - /// collection. Used by `destroy`. - pub owner_deposit: DepositBalance, - /// The total number of outstanding items of this collection. - pub items: u32, - /// The total number of outstanding item metadata of this collection. - pub item_metadatas: u32, - /// The total number of outstanding item configs of this collection. - pub item_configs: u32, - /// The total number of attributes for this collection. - pub attributes: u32, -} +// TODO: Multi-instances. +pub(super) type ItemDepositOf = ItemDeposit, AccountIdOf>; +pub(super) type CollectionDetailsFor = + CollectionDetails, BalanceOf>; +pub(super) type ItemDetailsFor = + ItemDetails, ItemDepositOf, ApprovalsOf>; diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index f51de192..6fe483f1 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -34,6 +34,11 @@ impl, I: 'static> Pallet { Collection::::get(collection).map(|i| i.owner) } + /// Get the total number of items in the collection, if the collection exists. + pub fn collection_items(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.items) + } + /// Validates the signature of the given data with the provided signer's account ID. /// /// # Errors @@ -46,7 +51,7 @@ impl, I: 'static> Pallet { signer: &T::AccountId, ) -> DispatchResult { if signature.verify(&**data, &signer) { - return Ok(()) + return Ok(()); } // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index ad5d93c2..f626a9fe 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -65,7 +65,6 @@ impl, I: 'static> Pallet { if let Some(check_origin) = maybe_check_origin { ensure!(check_origin == details.owner, Error::::NoPermission); } - let now = frame_system::Pallet::::block_number(); let deadline = maybe_deadline.map(|d| d.saturating_add(now)); @@ -74,15 +73,13 @@ impl, I: 'static> Pallet { .try_insert(delegate.clone(), deadline) .map_err(|_| Error::::ReachedApprovalLimit)?; Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::TransferApproved { collection, - item, + item: Some(item), owner: details.owner, delegate, deadline, }); - Ok(()) } @@ -129,7 +126,7 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::ApprovalCancelled { collection, - item, + item: Some(item), owner: details.owner, delegate, }); @@ -173,4 +170,82 @@ impl, I: 'static> Pallet { Ok(()) } + + pub(crate) fn do_approve_transfer_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let collection_owner = + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + 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 { + ensure!(check_origin == collection_owner, Error::::NoPermission); + } + + Allowances::::mutate((&collection, &collection_owner, &delegate), |allowance| { + *allowance = true; + }); + + Self::deposit_event(Event::TransferApproved { + collection, + item: None, + owner: collection_owner, + delegate, + deadline: None, + }); + Ok(()) + } + + pub(crate) fn do_cancel_approval_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + let collection_owner = + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == collection_owner, Error::::NoPermission); + } + + Allowances::::remove((&collection, &collection_owner, &delegate)); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + owner: collection_owner, + item: None, + delegate, + }); + + Ok(()) + } + + pub fn allowance( + collection: T::CollectionId, + item: Option, + owner: T::AccountId, + delegate: T::AccountId, + ) -> Option { + // Check if a `delegate` has a permission to spend the collection. + if Allowances::::get((&collection, &owner, &delegate)) { + return Some(true); + } + // Check if a `delegate` has a permission to spend the collection item. + item.map(|item| { + Item::::get(&collection, &item) + .map(|detail| detail.approvals.contains_key(&delegate)) + }) + .unwrap_or_default() + } } diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 348ec6b9..2ea5cd73 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -137,6 +137,9 @@ impl, I: 'static> Pallet { } } + let _ = + AccountBalance::::clear_prefix((collection,), collection_details.items, None); + let _ = Allowances::::clear_prefix((collection,), collection_details.items, None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index e9843b2e..036a63b7 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -69,6 +69,9 @@ impl, I: 'static> Pallet { } collection_details.items.saturating_inc(); + AccountBalance::::mutate((collection, &mint_to), |balance| { + balance.saturating_inc(); + }); let collection_config = Self::get_collection_config(&collection)?; let deposit_amount = match collection_config @@ -263,6 +266,9 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); + AccountBalance::::mutate((collection, &owner), |balance| { + balance.saturating_dec(); + }); if remove_config { ItemConfigOf::::remove(&collection, &item); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index b7223a7c..0aa83fe8 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -87,6 +87,14 @@ impl, I: 'static> Pallet { // Perform the transfer with custom details using the provided closure. with_details(&collection_details, &mut details)?; + // Update account balances. + AccountBalance::::mutate((collection, &details.owner), |balance| { + balance.saturating_dec(); + }); + AccountBalance::::mutate((collection, &dest), |balance| { + balance.saturating_dec(); + }); + // Update account ownership information. Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 89bfb963..9f4d3aed 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -402,6 +402,36 @@ pub mod pallet { pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + /// Number of collection items that accounts own. + #[pallet::storage] + pub type AccountBalance, I: 'static = ()> = StorageNMap< + _, + ( + // Collection Id. + NMapKey, + // Collection Owner Id. + NMapKey, + ), + u32, + ValueQuery, + >; + + /// Permission for the delegate to transfer all owner's items within a collection. + #[pallet::storage] + pub type Allowances, I: 'static = ()> = StorageNMap< + _, + ( + // Collection ID. + NMapKey, + // Collection Owner Id. + NMapKey, + // Delegate Id. + NMapKey, + ), + bool, + ValueQuery, + >; + /// Config of an item. #[pallet::storage] pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< @@ -460,7 +490,7 @@ pub mod pallet { /// a `delegate`. TransferApproved { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, deadline: Option>, @@ -469,7 +499,7 @@ pub mod pallet { /// `collection` was cancelled by its `owner`. ApprovalCancelled { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, }, @@ -1931,6 +1961,60 @@ pub mod pallet { Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; Self::do_set_attributes_pre_signed(origin, data, signer) } + + /// Approve an item to be transferred by a delegated third-party account. + /// + /// 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. + /// - `delegate`: The account to delegate permission to transfer the item. + /// + /// Emits `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(39)] + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub fn approve_transfer_collection( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `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 collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to loose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(40)] + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub fn cancel_approval_collection( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> 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 delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate) + } } } From 204bab4afe4f8f958be4876cf32eae41c5f13e69 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:30:14 +0700 Subject: [PATCH 42/79] feat: check allowance --- pallets/api/src/nonfungibles/mod.rs | 102 +++++++------------------ pallets/nfts/src/features/approvals.rs | 28 ++++--- pallets/nfts/src/features/transfer.rs | 6 +- pallets/nfts/src/lib.rs | 94 ++++++----------------- pallets/nfts/src/tests.rs | 93 +++++++++++++--------- 5 files changed, 128 insertions(+), 195 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index cf2b14e7..bc728ac8 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -62,7 +62,7 @@ pub mod pallet { pub enum ReadResult { OwnerOf(Option>), CollectionOwner(Option>), - TotalSupply(u32), + TotalSupply(u128), BalanceOf(u32), Collection(Option>), Item(Option>), @@ -99,25 +99,18 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Event emitted when allowance by `owner` to `spender` canceled. - CancelApproval { - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - /// The beneficiary of the allowance. - spender: AccountIdOf, - }, - /// Event emitted when allowance by `owner` to `spender` changes. + /// Event emitted when allowance by `owner` to `operator` changes. Approval { /// The collection ID. collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, /// The owner providing the allowance. owner: AccountIdOf, /// The beneficiary of the allowance. - spender: AccountIdOf, + operator: AccountIdOf, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option>, + /// Whether allowance is set or removed. + approved: bool, }, /// Event emitted when new item is minted to the account. Mint { @@ -150,12 +143,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Create a new non-fungible token to the collection. - /// - /// # Parameters - /// - `to` - The owner of the collection item. - /// - `collection` - The collection ID. - /// - `item` - The item ID. #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -169,11 +156,6 @@ pub mod pallet { Ok(()) } - /// Destroy a new non-fungible token to the collection. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. #[pallet::call_index(1)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( @@ -186,12 +168,6 @@ pub mod pallet { Ok(()) } - /// Transfer a token from one account to the another account. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `to` - The recipient account. #[pallet::call_index(2)] #[pallet::weight(NftsWeightInfoOf::::transfer())] pub fn transfer( @@ -206,53 +182,33 @@ pub mod pallet { Ok(()) } - /// Delegate a permission to perform actions on the collection item to an account. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `spender` - The account that is allowed to transfer the collection item. #[pallet::call_index(3)] #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] pub fn approve( origin: OriginFor, collection: CollectionIdOf, - item: ItemIdOf, - spender: AccountIdOf, + item: Option>, + operator: AccountIdOf, + approved: bool, ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; - NftsOf::::approve_transfer( - origin, - collection, - item, - T::Lookup::unlookup(spender.clone()), - None, - )?; - Self::deposit_event(Event::Approval { collection, item, spender, owner }); - Ok(()) - } - - /// Cancel one of the transfer approvals for a specific item. - /// - /// # Parameters - /// - `collection` - The collection ID. - /// - `item` - The item ID. - /// - `spender` - The account that is revoked permission to transfer the collection item. - #[pallet::call_index(4)] - #[pallet::weight(NftsWeightInfoOf::::cancel_approval())] - pub fn cancel_approval( - origin: OriginFor, - collection: CollectionIdOf, - item: ItemIdOf, - spender: AccountIdOf, - ) -> DispatchResult { - NftsOf::::cancel_approval( - origin, - collection, - item, - T::Lookup::unlookup(spender.clone()), - )?; - Self::deposit_event(Event::CancelApproval { collection, item, spender }); + if approved { + NftsOf::::approve_transfer( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + None, + )?; + } else { + NftsOf::::cancel_approval( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + )?; + } + Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); Ok(()) } } @@ -284,14 +240,14 @@ pub mod pallet { CollectionOwner(collection) => ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default(), + NftsOf::::collection_items(collection).unwrap_or_default().into(), ), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( - NftsOf::::allowance(collection, item, owner, operator).unwrap_or(false), + NftsOf::::allowance(&collection, &item, &owner, &operator).is_ok(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index f626a9fe..979035fa 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -232,20 +232,26 @@ impl, I: 'static> Pallet { } pub fn allowance( - collection: T::CollectionId, - item: Option, - owner: T::AccountId, - delegate: T::AccountId, - ) -> Option { + collection: &T::CollectionId, + item: &Option, + owner: &T::AccountId, + delegate: &T::AccountId, + ) -> Result<(), DispatchError> { // Check if a `delegate` has a permission to spend the collection. if Allowances::::get((&collection, &owner, &delegate)) { - return Some(true); + return Ok(()); } // Check if a `delegate` has a permission to spend the collection item. - item.map(|item| { - Item::::get(&collection, &item) - .map(|detail| detail.approvals.contains_key(&delegate)) - }) - .unwrap_or_default() + if let Some(item) = item { + let details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let deadline = details.approvals.get(&delegate).ok_or(Error::::NoPermission)?; + if let Some(d) = deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *d, Error::::ApprovalExpired); + } + }; + Ok(()) } } diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index 0aa83fe8..04d9f4fe 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -92,7 +92,7 @@ impl, I: 'static> Pallet { balance.saturating_dec(); }); AccountBalance::::mutate((collection, &dest), |balance| { - balance.saturating_dec(); + balance.saturating_inc(); }); // Update account ownership information. @@ -145,7 +145,7 @@ impl, I: 'static> Pallet { // Check if the `origin` is the current owner of the collection. ensure!(origin == details.owner, Error::::NoPermission); if details.owner == new_owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. @@ -220,7 +220,7 @@ impl, I: 'static> Pallet { Collection::::try_mutate(collection, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; if details.owner == owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 9f4d3aed..14689293 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -1060,12 +1060,7 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { - 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); - } + Self::allowance(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) @@ -1120,10 +1115,10 @@ pub mod pallet { 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 + continue; } } else { - continue + continue; } details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); @@ -1322,7 +1317,7 @@ pub mod pallet { pub fn approve_transfer( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, maybe_deadline: Option>, ) -> DispatchResult { @@ -1330,13 +1325,17 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; - Self::do_approve_transfer( - maybe_check_origin, - collection, - item, - delegate, - maybe_deadline, - ) + match maybe_item { + Some(item) => Self::do_approve_transfer( + maybe_check_origin, + collection, + item, + delegate, + maybe_deadline, + ), + None => + Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate), + } } /// Cancel one of the transfer approvals for a specific item. @@ -1358,14 +1357,19 @@ pub mod pallet { pub fn cancel_approval( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) + match maybe_item { + Some(item) => + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate), + None => + Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate), + } } /// Cancel all the approvals of a specific item. @@ -1961,60 +1965,6 @@ pub mod pallet { Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; Self::do_set_attributes_pre_signed(origin, data, signer) } - - /// Approve an item to be transferred by a delegated third-party account. - /// - /// 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. - /// - `delegate`: The account to delegate permission to transfer the item. - /// - /// Emits `TransferApproved` on success. - /// - /// Weight: `O(1)` - #[pallet::call_index(39)] - #[pallet::weight(T::WeightInfo::approve_transfer())] - pub fn approve_transfer_collection( - origin: OriginFor, - collection: T::CollectionId, - delegate: AccountIdLookupOf, - ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate) - } - - /// Cancel one of the transfer approvals for a specific item. - /// - /// Origin must be either: - /// - the `Force` origin; - /// - `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 collection of whose approval will be cancelled. - /// - `delegate`: The account that is going to loose their approval. - /// - /// Emits `ApprovalCancelled` on success. - /// - /// Weight: `O(1)` - #[pallet::call_index(40)] - #[pallet::weight(T::WeightInfo::cancel_approval())] - pub fn cancel_approval_collection( - origin: OriginFor, - collection: T::CollectionId, - delegate: AccountIdLookupOf, - ) -> 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 delegate = T::Lookup::lookup(delegate)?; - Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate) - } } } diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 44f2f32a..4002c48e 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -487,7 +487,7 @@ fn transfer_should_work() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(3)), 0, - 42, + Some(42), account(2), None )); @@ -1777,7 +1777,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); @@ -1791,7 +1791,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(2), None )); @@ -1819,7 +1819,7 @@ fn approval_lifecycle_works() { Nfts::approve_transfer( RuntimeOrigin::signed(account(1)), collection_id, - 1, + Some(1), account(2), None ), @@ -1847,30 +1847,35 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, Some(42), account(3)), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(3)), Error::::NotDelegate ); @@ -1887,18 +1892,23 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, Some(42), account(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(RuntimeOrigin::signed(account(5)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(5)), + 0, + Some(42), + account(3) + )); assert_eq!(approvals(0, 69), vec![]); }); } @@ -1924,21 +1934,21 @@ fn approving_multiple_accounts_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(5), Some(2) )); @@ -1979,14 +1989,20 @@ fn approvals_limit_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(i), None )); } // the limit is 10 assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), + Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(14), + None + ), Error::::ReachedApprovalLimit ); }); @@ -2015,7 +2031,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); @@ -2034,7 +2050,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(6), Some(4) )); @@ -2063,26 +2079,31 @@ fn cancel_approval_works_with_admin() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2107,26 +2128,26 @@ fn cancel_approval_works_with_force() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2151,14 +2172,14 @@ fn clear_all_transfer_approvals_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); @@ -3169,7 +3190,7 @@ fn pallet_level_feature_flags_should_work() { Nfts::approve_transfer( RuntimeOrigin::signed(user_id.clone()), collection_id, - item_id, + Some(item_id), account(2), None ), From 75ff645b39573d7763a173ab981235cb3ee14d05 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:03:43 +0700 Subject: [PATCH 43/79] test(nfts): check account balance & test total supply --- pallets/nfts/src/tests.rs | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 4002c48e..f75c25d8 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -164,6 +164,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 42)]); assert_ok!(Nfts::force_create( @@ -173,6 +174,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); + assert_eq!(AccountBalance::::get((1, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } @@ -204,6 +206,7 @@ fn lifecycle_should_work() { account(10), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(10))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 6); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), @@ -212,8 +215,10 @@ fn lifecycle_should_work() { account(20), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(20))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 7); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -221,6 +226,8 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); + assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); @@ -238,6 +245,7 @@ fn lifecycle_should_work() { Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), Error::::CollectionNotEmpty ); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(account(1)), @@ -248,7 +256,9 @@ fn lifecycle_should_work() { bvec![0], )); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); + assert_eq!(AccountBalance::::get((0, account(10))), 0); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); + assert_eq!(AccountBalance::::get((0, account(10))), 0); assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); let w = Nfts::get_destroy_witness(&0).unwrap(); @@ -256,6 +266,7 @@ fn lifecycle_should_work() { assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); @@ -305,6 +316,7 @@ fn destroy_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_noop!( Nfts::destroy( RuntimeOrigin::signed(account(1)), @@ -323,6 +335,7 @@ fn destroy_should_work() { 0, Nfts::get_destroy_witness(&0).unwrap() )); + assert_eq!(AccountBalance::::get((0, account(1))), 0); assert!(!ItemConfigOf::::contains_key(0, 42)); assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); @@ -337,6 +350,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get((0, account(1))), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -402,6 +416,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); + assert_eq!(AccountBalance::::get((0, account(2))), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -440,6 +455,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); + assert_eq!(AccountBalance::::get((1, account(2))), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -478,6 +494,8 @@ fn transfer_should_work() { )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(AccountBalance::::get((0, account(2))), 0); + assert_eq!(AccountBalance::::get((0, account(3))), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -492,7 +510,9 @@ fn transfer_should_work() { None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - + assert_eq!(AccountBalance::::get((0, account(2))), 0); + assert_eq!(AccountBalance::::get((0, account(3))), 0); + assert_eq!(AccountBalance::::get((0, account(4))), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1746,15 +1766,17 @@ fn burn_works() { account(5), default_item_config() )); + assert_eq!(AccountBalance::::get((0, account(5))), 2); assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), Error::::NoPermission ); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); + assert_eq!(AccountBalance::::get((0, account(5))), 1); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); + assert_eq!(AccountBalance::::get((0, account(5))), 0); assert_eq!(Balances::reserved_balance(account(1)), 0); }); } @@ -2209,6 +2231,36 @@ fn clear_all_transfer_approvals_works() { }); } +#[test] +fn total_supply_should_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let total_items = 10; + + // no collection. + assert_eq!(Nfts::collection_items(collection_id), None); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + // mint items and validate the total supply. + (0..total_items).into_iter().for_each(|i| { + assert_ok!(Nfts::force_mint( + RuntimeOrigin::root(), + collection_id, + i, + user_id.clone(), + ItemConfig::default() + )); + }); + assert_eq!(Nfts::collection_items(collection_id), Some(total_items)); + }); +} + #[test] fn max_supply_should_work() { new_test_ext().execute_with(|| { @@ -2545,6 +2597,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -2912,6 +2965,8 @@ fn claim_swap_should_work() { default_item_config(), )); + assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1.clone()), collection_id, @@ -3002,6 +3057,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); + assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); + assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); From 5653b66d75631b15c8a620a26903bd88c72010c8 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:39:53 +0700 Subject: [PATCH 44/79] test(nfts): allowance works --- pallets/api/src/nonfungibles/mod.rs | 2 +- pallets/nfts/src/features/approvals.rs | 8 +++- pallets/nfts/src/lib.rs | 2 +- pallets/nfts/src/tests.rs | 57 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index bc728ac8..4b6558e9 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -247,7 +247,7 @@ pub mod pallet { Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( - NftsOf::::allowance(&collection, &item, &owner, &operator).is_ok(), + NftsOf::::check_allowance(&collection, &item, &owner, &operator).is_ok(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 979035fa..89b8fc6a 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -231,7 +231,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn allowance( + pub fn check_allowance( collection: &T::CollectionId, item: &Option, owner: &T::AccountId, @@ -239,6 +239,9 @@ impl, I: 'static> Pallet { ) -> Result<(), DispatchError> { // Check if a `delegate` has a permission to spend the collection. if Allowances::::get((&collection, &owner, &delegate)) { + if let Some(item) = item { + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + }; return Ok(()); } // Check if a `delegate` has a permission to spend the collection item. @@ -251,7 +254,8 @@ impl, I: 'static> Pallet { let block_number = frame_system::Pallet::::block_number(); ensure!(block_number <= *d, Error::::ApprovalExpired); } + return Ok(()); }; - Ok(()) + Err(Error::::NoPermission.into()) } } diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 14689293..c7d826b5 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -1060,7 +1060,7 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { - Self::allowance(&collection, &Some(item), &details.owner, &origin)?; + Self::check_allowance(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index f75c25d8..5ad13e6e 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1850,6 +1850,63 @@ fn approval_lifecycle_works() { }); } +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(2), + None + )); + + // collection transfer approved. + assert_noop!( + Nfts::check_allowance(&0, &Some(43), &account(1), &account(2)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::check_allowance(&0, &Some(42), &account(1), &account(3)), + Error::::NoPermission + ); + assert_ok!(Nfts::check_allowance(&0, &None, &account(1), &account(2))); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(1), &account(2))); + + // collection item transfer approved. + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3), + None + )); + + assert_noop!( + Nfts::check_allowance(&0, &Some(43), &account(2), &account(3)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::check_allowance(&0, &Some(42), &account(2), &account(4)), + Error::::NoPermission + ); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(2), &account(3))); + }); +} + #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { From 15f72edcd114a63825603560ced6c0ea3abff773 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:40:34 +0700 Subject: [PATCH 45/79] refactor(test): check_allowance_works --- pallets/nfts/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 5ad13e6e..9c3cce0d 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1851,7 +1851,7 @@ fn approval_lifecycle_works() { } #[test] -fn allowance_works() { +fn check_allowance_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), From 2c64d91cb45d14dffc3e5060e8fc5e6eca576fad Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:18:49 +0700 Subject: [PATCH 46/79] fix(nfts): cancel / approve collection transfer --- pallets/nfts/src/features/approvals.rs | 27 ++++------ pallets/nfts/src/tests.rs | 72 ++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 89b8fc6a..05696fee 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -180,27 +180,24 @@ impl, I: 'static> Pallet { Self::is_pallet_feature_enabled(PalletFeature::Approvals), Error::::MethodDisabled ); - let collection_owner = - Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; - + if !Collection::::contains_key(collection) { + return Err(Error::::UnknownCollection.into()); + } 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 { - ensure!(check_origin == collection_owner, Error::::NoPermission); - } - - Allowances::::mutate((&collection, &collection_owner, &delegate), |allowance| { + let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; + Allowances::::mutate((&collection, &origin, &delegate), |allowance| { *allowance = true; }); Self::deposit_event(Event::TransferApproved { collection, item: None, - owner: collection_owner, + owner: origin, delegate, deadline: None, }); @@ -212,18 +209,16 @@ impl, I: 'static> Pallet { collection: T::CollectionId, delegate: T::AccountId, ) -> DispatchResult { - let collection_owner = - Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_origin) = maybe_check_origin { - ensure!(check_origin == collection_owner, Error::::NoPermission); + if !Collection::::contains_key(collection) { + return Err(Error::::UnknownCollection.into()); } - Allowances::::remove((&collection, &collection_owner, &delegate)); + let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; + Allowances::::remove((&collection, &origin, &delegate)); Self::deposit_event(Event::ApprovalCancelled { collection, - owner: collection_owner, + owner: origin, item: None, delegate, }); diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 9c3cce0d..07f2a74d 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1876,12 +1876,12 @@ fn check_allowance_works() { // collection transfer approved. assert_noop!( - Nfts::check_allowance(&0, &Some(43), &account(1), &account(2)), - Error::::UnknownItem + Nfts::check_allowance(&1, &None, &account(1), &account(2)), + Error::::NoPermission ); assert_noop!( - Nfts::check_allowance(&0, &Some(42), &account(1), &account(3)), - Error::::NoPermission + Nfts::check_allowance(&1, &Some(43), &account(1), &account(2)), + Error::::UnknownItem ); assert_ok!(Nfts::check_allowance(&0, &None, &account(1), &account(2))); assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(1), &account(2))); @@ -1992,6 +1992,43 @@ fn cancel_approval_works() { }); } +#[test] +fn cancel_approval_collection_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + None, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, None, account(3)), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, None, account(3))); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), + Error::::NoPermission + ); + }); +} + #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { @@ -2087,6 +2124,33 @@ fn approvals_limit_works() { }); } +#[test] +fn approval_collection_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(3), + None + )); + assert_eq!(Allowances::::get((0, account(1), account(3))), true); + }); +} + #[test] fn approval_deadline_works() { new_test_ext().execute_with(|| { From d29b841b01b5391f4d4e7a91acd4433692026ab0 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:17:38 +0700 Subject: [PATCH 47/79] test(nfts): cancel approval collection --- pallets/nfts/src/tests.rs | 49 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 07f2a74d..4fbec4de 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -2021,6 +2021,13 @@ fn cancel_approval_collection_works() { ); assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, None, account(3))); + assert!(events().contains(&Event::::ApprovalCancelled { + collection: 0, + item: None, + owner: account(2), + delegate: account(3) + })); + assert_eq!(Allowances::::get((0, account(2), account(3))), false); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), @@ -2127,6 +2134,11 @@ fn approvals_limit_works() { #[test] fn approval_collection_works() { new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), account(1), @@ -2140,14 +2152,47 @@ fn approval_collection_works() { default_item_config() )); - assert_ok!(Nfts::approve_transfer( + // Error::ItemsNonTransferable. + assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(account(1)), + 1, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), + Error::::ItemsNonTransferable + ); + + // Error::MethodDisabled. + Features::set(&PalletFeatures::from_disabled(PalletFeature::Approvals.into())); + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), + Error::::MethodDisabled + ); + Features::set(&PalletFeatures::all_enabled()); + + // Error::UnknownCollection. + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 2, None, account(3), None), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), 0, None, account(3), None )); - assert_eq!(Allowances::::get((0, account(1), account(3))), true); + assert!(events().contains(&Event::::TransferApproved { + collection: 0, + item: None, + owner: account(2), + delegate: account(3), + deadline: None + })); + assert_eq!(Allowances::::get((0, account(2), account(3))), true); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); }); } From b47d42d9fe42bc527cb1d51c33a0e13f1b63c98a Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:23:23 +0700 Subject: [PATCH 48/79] test(nfts): update approval test cases --- pallets/nfts/src/tests.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 4fbec4de..10c95bd6 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -1993,7 +1993,7 @@ fn cancel_approval_works() { } #[test] -fn cancel_approval_collection_works() { +fn cancel_approval_collection_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), @@ -2132,7 +2132,7 @@ fn approvals_limit_works() { } #[test] -fn approval_collection_works() { +fn approval_collection_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), @@ -2163,14 +2163,6 @@ fn approval_collection_works() { Error::::ItemsNonTransferable ); - // Error::MethodDisabled. - Features::set(&PalletFeatures::from_disabled(PalletFeature::Approvals.into())); - assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), - Error::::MethodDisabled - ); - Features::set(&PalletFeatures::all_enabled()); - // Error::UnknownCollection. assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 2, None, account(3), None), From 22dac996ad77bc063ab03c3feba8873ee523736c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:18:20 +0700 Subject: [PATCH 49/79] feat(nfts): get attribute read method --- pallets/api/src/nonfungibles/mod.rs | 105 ++++++++++++++++++-------- pallets/api/src/nonfungibles/types.rs | 6 +- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 4b6558e9..83d23585 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -14,10 +14,11 @@ mod types; pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; + use pallet_nfts::MintWitness; use sp_std::vec::Vec; use types::{ - AccountIdOf, CollectionDetailsFor, CollectionIdOf, ItemDetailsFor, ItemIdOf, NftsOf, - NftsWeightInfoOf, + AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, + ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, }; use super::*; @@ -54,6 +55,14 @@ pub mod pallet { operator: AccountIdOf, item: Option>, }, + /// Returns the attribute of `item` for the given `key`. + #[codec(index = 6)] + GetAttribute { + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + }, } /// Results of state reads for the non-fungibles API. @@ -67,6 +76,7 @@ pub mod pallet { Collection(Option>), Item(Option>), Allowance(bool), + GetAttribute(Option>), } impl ReadResult { @@ -81,6 +91,7 @@ pub mod pallet { Collection(result) => result.encode(), Item(result) => result.encode(), Allowance(result) => result.encode(), + GetAttribute(result) => result.encode(), } } } @@ -103,46 +114,46 @@ pub mod pallet { Approval { /// The collection ID. collection: CollectionIdOf, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option>, /// The owner providing the allowance. owner: AccountIdOf, /// The beneficiary of the allowance. operator: AccountIdOf, - /// The item which is (dis)approved. `None` for all owner's items. - item: Option>, /// Whether allowance is set or removed. approved: bool, }, - /// Event emitted when new item is minted to the account. - Mint { - /// The owner of the item. - to: AccountIdOf, - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - }, - /// Event emitted when item is burned. - Burn { - /// The collection ID. - collection: CollectionIdOf, - /// the item ID. - item: ItemIdOf, - }, - /// Event emitted when an item transfer occurs. + /// Event emitted when a token transfer occurs. + // Differing style: event name abides by the PSP22 standard. Transfer { /// The collection ID. collection: CollectionIdOf, - /// the item ID. + /// The collection item ID. item: ItemIdOf, - /// The source of the transfer. - from: AccountIdOf, - /// The recipient of the transfer. - to: AccountIdOf, + /// The source of the transfer. `None` when minting. + from: Option>, + /// The recipient of the transfer. `None` when burning. + to: Option>, + /// The amount minted. + value: Option>, }, } #[pallet::call] impl Pallet { + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + + // TODO: Fix weight + #[pallet::call_index(21)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn destroy(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -150,9 +161,24 @@ pub mod pallet { to: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf, + mint_price: Option>, ) -> DispatchResult { - NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to.clone()), None)?; - Self::deposit_event(Event::Mint { to, collection, item }); + let account = ensure_signed(origin.clone())?; + let witness_data = MintWitness { mint_price, owned_item: Some(item) }; + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness_data), + )?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: None, + to: Some(account), + value: mint_price, + }); Ok(()) } @@ -163,8 +189,15 @@ pub mod pallet { collection: CollectionIdOf, item: ItemIdOf, ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Burn { collection, item }); + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(account), + to: None, + value: None, + }); Ok(()) } @@ -178,12 +211,18 @@ pub mod pallet { ) -> DispatchResult { let from = ensure_signed(origin.clone())?; NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; - Self::deposit_event(Event::Transfer { from, to, collection, item }); + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(from), + to: Some(to), + value: None, + }); Ok(()) } #[pallet::call_index(3)] - #[pallet::weight(NftsWeightInfoOf::::approve_transfer())] + #[pallet::weight(NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::cancel_approval())] pub fn approve( origin: OriginFor, collection: CollectionIdOf, @@ -252,6 +291,10 @@ pub mod pallet { BalanceOf { collection, owner } => ReadResult::BalanceOf( pallet_nfts::AccountBalance::::get((collection, owner)), ), + GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( + pallet_nfts::Attribute::::get((collection, item, namespace, key)) + .map(|attribute| attribute.0), + ), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index f81ea535..da949171 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,6 +1,6 @@ use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; +use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails}; use sp_runtime::BoundedBTreeMap; // Type aliases for pallet-nfts. @@ -15,14 +15,16 @@ pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; pub(super) type ItemIdOf = as Inspect<::AccountId>>::ItemId; -type ApprovalsOf = BoundedBTreeMap< +pub(super) type ApprovalsOf = BoundedBTreeMap< AccountIdOf, Option>, ::ApprovalsLimit, >; +pub(super) type ItemPriceOf = BalanceOf; // TODO: Multi-instances. pub(super) type ItemDepositOf = ItemDeposit, AccountIdOf>; pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; pub(super) type ItemDetailsFor = ItemDetails, ItemDepositOf, ApprovalsOf>; +pub(super) type AttributeNamespaceOf = AttributeNamespace>; From 91bdcc81d44cdee9e099b576c2e9dd3b697bee69 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:45:16 +0700 Subject: [PATCH 50/79] feat(nonfungibles + nfts): add new methods to manage attributes --- pallets/api/src/nonfungibles/mod.rs | 198 ++++++++++++++---- pallets/api/src/nonfungibles/types.rs | 17 +- pallets/nfts/src/features/approvals.rs | 4 +- .../src/features/create_delete_collection.rs | 4 +- .../nfts/src/features/create_delete_item.rs | 4 +- pallets/nfts/src/features/transfer.rs | 4 +- pallets/nfts/src/lib.rs | 18 +- pallets/nfts/src/tests.rs | 66 +++--- 8 files changed, 224 insertions(+), 91 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 83d23585..efef2386 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -14,11 +14,14 @@ mod types; pub mod pallet { use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; - use pallet_nfts::MintWitness; + use pallet_nfts::{ + CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, + MintSettings, MintWitness, + }; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, }; use super::*; @@ -29,24 +32,12 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// Returns the owner of an item. - #[codec(index = 0)] - OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Returns the owner of a collection. - #[codec(index = 1)] - CollectionOwner(CollectionIdOf), /// Number of items existing in a concrete collection. #[codec(index = 2)] TotalSupply(CollectionIdOf), /// Returns the total number of items in the collection owned by the account. #[codec(index = 3)] BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, - /// Returns the details of a collection. - #[codec(index = 4)] - Collection(CollectionIdOf), - /// Returns the details of an item. - #[codec(index = 5)] - Item { collection: CollectionIdOf, item: ItemIdOf }, /// Whether a spender is allowed to transfer an item or items from owner. #[codec(index = 6)] Allowance { @@ -55,6 +46,9 @@ pub mod pallet { operator: AccountIdOf, item: Option>, }, + /// Returns the owner of an item. + #[codec(index = 0)] + OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, /// Returns the attribute of `item` for the given `key`. #[codec(index = 6)] GetAttribute { @@ -63,20 +57,29 @@ pub mod pallet { namespace: AttributeNamespaceOf, key: BoundedVec, }, + /// Returns the owner of a collection. + #[codec(index = 1)] + CollectionOwner(CollectionIdOf), + /// Returns the details of a collection. + #[codec(index = 4)] + Collection(CollectionIdOf), + /// Returns the details of an item. + #[codec(index = 5)] + Item { collection: CollectionIdOf, item: ItemIdOf }, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - OwnerOf(Option>), - CollectionOwner(Option>), TotalSupply(u128), BalanceOf(u32), - Collection(Option>), - Item(Option>), Allowance(bool), + OwnerOf(Option>), GetAttribute(Option>), + CollectionOwner(Option>), + Collection(Option>), + Item(Option>), } impl ReadResult { @@ -141,19 +144,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::call_index(20)] - #[pallet::weight(NftsWeightInfoOf::::create())] - pub fn create(_origin: OriginFor) -> DispatchResult { - Ok(()) - } - - // TODO: Fix weight - #[pallet::call_index(21)] - #[pallet::weight(NftsWeightInfoOf::::create())] - pub fn destroy(_origin: OriginFor) -> DispatchResult { - Ok(()) - } - #[pallet::call_index(0)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -250,6 +240,131 @@ pub mod pallet { Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); Ok(()) } + + #[pallet::call_index(4)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create( + origin: OriginFor, + admin: AccountIdOf, + config: CreateCollectionConfigFor, + ) -> DispatchResult { + let collection_config = CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: config.max_supply, + mint_settings: MintSettings { + mint_type: config.mint_type, + start_block: config.start_block, + end_block: config.end_block, + ..MintSettings::default() + }, + }; + NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), collection_config)?; + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(NftsWeightInfoOf::::destroy( + witness.item_metadatas, + witness.item_configs, + witness.attributes, + ))] + pub fn destroy( + origin: OriginFor, + collection: CollectionIdOf, + witness: DestroyWitness, + ) -> DispatchResultWithPostInfo { + NftsOf::::destroy(origin, collection, witness) + } + + #[pallet::call_index(6)] + #[pallet::weight(NftsWeightInfoOf::::set_attribute())] + pub fn set_attribute( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + NftsOf::::set_attribute(origin, collection, item, namespace, key, value) + } + + #[pallet::call_index(7)] + #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] + pub fn clear_attribute( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec, + ) -> DispatchResult { + NftsOf::::clear_attribute(origin, collection, item, namespace, key) + } + + #[pallet::call_index(8)] + #[pallet::weight(NftsWeightInfoOf::::set_metadata())] + pub fn set_metadata( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + data: BoundedVec, + ) -> DispatchResult { + NftsOf::::set_metadata(origin, collection, item, data) + } + + #[pallet::call_index(9)] + #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::clear_metadata(origin, collection, item) + } + + #[pallet::call_index(10)] + #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] + pub fn approve_item_attributes( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + delegate: AccountIdOf, + ) -> DispatchResult { + NftsOf::::approve_item_attributes( + origin, + collection, + item, + T::Lookup::unlookup(delegate.clone()), + ) + } + + #[pallet::call_index(11)] + #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] + pub fn cancel_item_attributes_approval( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + delegate: AccountIdOf, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + NftsOf::::cancel_item_attributes_approval( + origin, + collection, + item, + T::Lookup::unlookup(delegate.clone()), + witness, + ) + } + + #[pallet::call_index(12)] + #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] + pub fn set_max_supply( + origin: OriginFor, + collection: CollectionIdOf, + max_supply: u32, + ) -> DispatchResult { + NftsOf::::set_collection_max_supply(origin, collection, max_supply) + } } impl crate::Read for Pallet { @@ -274,27 +389,26 @@ pub mod pallet { fn read(value: Self::Read) -> Self::Result { use Read::*; match value { - OwnerOf { collection, item } => - ReadResult::OwnerOf(NftsOf::::owner(collection, item)), - CollectionOwner(collection) => - ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), TotalSupply(collection) => ReadResult::TotalSupply( NftsOf::::collection_items(collection).unwrap_or_default().into(), ), - Collection(collection) => - ReadResult::Collection(pallet_nfts::Collection::::get(collection)), - Item { collection, item } => - ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + BalanceOf { collection, owner } => + ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( NftsOf::::check_allowance(&collection, &item, &owner, &operator).is_ok(), ), - BalanceOf { collection, owner } => ReadResult::BalanceOf( - pallet_nfts::AccountBalance::::get((collection, owner)), - ), + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( pallet_nfts::Attribute::::get((collection, item, namespace, key)) .map(|attribute| attribute.0), ), + CollectionOwner(collection) => + ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), + Collection(collection) => + ReadResult::Collection(pallet_nfts::Collection::::get(collection)), + Item { collection, item } => + ReadResult::Item(pallet_nfts::Item::::get(collection, item)), } } } diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index da949171..8c53ffd3 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,7 +1,9 @@ +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails}; -use sp_runtime::BoundedBTreeMap; +use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; +use scale_info::TypeInfo; +use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; @@ -28,3 +30,14 @@ pub(super) type CollectionDetailsFor = pub(super) type ItemDetailsFor = ItemDetails, ItemDepositOf, ApprovalsOf>; pub(super) type AttributeNamespaceOf = AttributeNamespace>; +pub(super) type CreateCollectionConfigFor = + CreateCollectionConfig, BlockNumberFor, CollectionIdOf>; + +#[derive(Clone, Copy, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CreateCollectionConfig { + pub max_supply: Option, + pub mint_type: MintType, + pub price: Option, + pub start_block: Option, + pub end_block: Option, +} diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 05696fee..6d71c1a2 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -171,7 +171,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub(crate) fn do_approve_transfer_collection( + pub(crate) fn do_approve_collection( maybe_check_origin: Option, collection: T::CollectionId, delegate: T::AccountId, @@ -204,7 +204,7 @@ impl, I: 'static> Pallet { Ok(()) } - pub(crate) fn do_cancel_approval_collection( + pub(crate) fn do_cancel_collection( maybe_check_origin: Option, collection: T::CollectionId, delegate: T::AccountId, diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 2ea5cd73..b7efd03a 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -137,8 +137,10 @@ impl, I: 'static> Pallet { } } + // TODO: Do we need another storage item to keep track of number of holders of a + // collection let _ = - AccountBalance::::clear_prefix((collection,), collection_details.items, None); + AccountBalance::::clear_prefix(collection, collection_details.items, None); let _ = Allowances::::clear_prefix((collection,), collection_details.items, None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index 036a63b7..cc29f8da 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -69,7 +69,7 @@ impl, I: 'static> Pallet { } collection_details.items.saturating_inc(); - AccountBalance::::mutate((collection, &mint_to), |balance| { + AccountBalance::::mutate(collection, &mint_to, |balance| { balance.saturating_inc(); }); @@ -266,7 +266,7 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); - AccountBalance::::mutate((collection, &owner), |balance| { + AccountBalance::::mutate(collection, &owner, |balance| { balance.saturating_dec(); }); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index 04d9f4fe..3b25b014 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -88,10 +88,10 @@ impl, I: 'static> Pallet { with_details(&collection_details, &mut details)?; // Update account balances. - AccountBalance::::mutate((collection, &details.owner), |balance| { + AccountBalance::::mutate(collection, &details.owner, |balance| { balance.saturating_dec(); }); - AccountBalance::::mutate((collection, &dest), |balance| { + AccountBalance::::mutate(collection, &dest, |balance| { balance.saturating_inc(); }); diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index c7d826b5..bc8b67b6 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -404,14 +404,12 @@ pub mod pallet { /// Number of collection items that accounts own. #[pallet::storage] - pub type AccountBalance, I: 'static = ()> = StorageNMap< + pub type AccountBalance, I: 'static = ()> = StorageDoubleMap< _, - ( - // Collection Id. - NMapKey, - // Collection Owner Id. - NMapKey, - ), + Twox64Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, u32, ValueQuery, >; @@ -1333,8 +1331,7 @@ pub mod pallet { delegate, maybe_deadline, ), - None => - Self::do_approve_transfer_collection(maybe_check_origin, collection, delegate), + None => Self::do_approve_collection(maybe_check_origin, collection, delegate), } } @@ -1367,8 +1364,7 @@ pub mod pallet { match maybe_item { Some(item) => Self::do_cancel_approval(maybe_check_origin, collection, item, delegate), - None => - Self::do_cancel_approval_collection(maybe_check_origin, collection, delegate), + None => Self::do_cancel_collection(maybe_check_origin, collection, delegate), } } diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 10c95bd6..397a715c 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -164,7 +164,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42)]); assert_ok!(Nfts::force_create( @@ -174,7 +174,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); - assert_eq!(AccountBalance::::get((1, account(1))), 1); + assert_eq!(AccountBalance::::get(1, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } @@ -206,7 +206,7 @@ fn lifecycle_should_work() { account(10), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(10))), 1); + assert_eq!(AccountBalance::::get(0, account(10)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 6); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), @@ -215,10 +215,10 @@ fn lifecycle_should_work() { account(20), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(20))), 1); + assert_eq!(AccountBalance::::get(0, account(20)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 7); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -226,8 +226,8 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); - assert_eq!(AccountBalance::::get((0, account(1))), 0); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 0); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); @@ -245,7 +245,7 @@ fn lifecycle_should_work() { Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), Error::::CollectionNotEmpty ); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(account(1)), @@ -256,9 +256,9 @@ fn lifecycle_should_work() { bvec![0], )); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); - assert_eq!(AccountBalance::::get((0, account(10))), 0); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); - assert_eq!(AccountBalance::::get((0, account(10))), 0); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); let w = Nfts::get_destroy_witness(&0).unwrap(); @@ -266,7 +266,7 @@ fn lifecycle_should_work() { assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); @@ -316,7 +316,14 @@ fn destroy_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 1); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + None, + account(3), + None + )); assert_noop!( Nfts::destroy( RuntimeOrigin::signed(account(1)), @@ -335,7 +342,8 @@ fn destroy_should_work() { 0, Nfts::get_destroy_witness(&0).unwrap() )); - assert_eq!(AccountBalance::::get((0, account(1))), 0); + assert_eq!(AccountBalance::::iter_prefix(0).count(), 0); + assert_eq!(Allowances::::iter_prefix((0,)).count(), 0); assert!(!ItemConfigOf::::contains_key(0, 42)); assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); @@ -350,7 +358,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - assert_eq!(AccountBalance::::get((0, account(1))), 1); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -416,7 +424,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); - assert_eq!(AccountBalance::::get((0, account(2))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -455,7 +463,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); - assert_eq!(AccountBalance::::get((1, account(2))), 1); + assert_eq!(AccountBalance::::get(1, account(2)), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -494,8 +502,8 @@ fn transfer_should_work() { )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); - assert_eq!(AccountBalance::::get((0, account(2))), 0); - assert_eq!(AccountBalance::::get((0, account(3))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -510,9 +518,9 @@ fn transfer_should_work() { None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - assert_eq!(AccountBalance::::get((0, account(2))), 0); - assert_eq!(AccountBalance::::get((0, account(3))), 0); - assert_eq!(AccountBalance::::get((0, account(4))), 1); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 0); + assert_eq!(AccountBalance::::get(0, account(4)), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1766,7 +1774,7 @@ fn burn_works() { account(5), default_item_config() )); - assert_eq!(AccountBalance::::get((0, account(5))), 2); + assert_eq!(AccountBalance::::get(0, account(5)), 2); assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( @@ -1774,9 +1782,9 @@ fn burn_works() { Error::::NoPermission ); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); - assert_eq!(AccountBalance::::get((0, account(5))), 1); + assert_eq!(AccountBalance::::get(0, account(5)), 1); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); - assert_eq!(AccountBalance::::get((0, account(5))), 0); + assert_eq!(AccountBalance::::get(0, account(5)), 0); assert_eq!(Balances::reserved_balance(account(1)), 0); }); } @@ -2755,7 +2763,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 1); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -3123,8 +3131,8 @@ fn claim_swap_should_work() { default_item_config(), )); - assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1.clone()), collection_id, @@ -3215,8 +3223,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); - assert_eq!(AccountBalance::::get((collection_id, user_1.clone())), 2); - assert_eq!(AccountBalance::::get((collection_id, user_2.clone())), 3); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); From 091c6d368d03cbd30d0aae90fe7461272be17d5c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:04:01 +0700 Subject: [PATCH 51/79] refactor(nfts): toml lock file --- Cargo.lock | 20 ++++++++++---------- pallets/nfts/Cargo.toml | 18 ++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4ff50b7..0f67e615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7380,7 +7380,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "pallet-ismp", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "parity-scale-codec", "pop-chain-extension", "scale-info", @@ -8273,39 +8273,39 @@ dependencies = [ [[package]] name = "pallet-nfts" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" +version = "0.1.0" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", - "sp-std", ] [[package]] name = "pallet-nfts" -version = "31.0.0" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", - "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-keystore", "sp-runtime", + "sp-std", ] [[package]] @@ -11052,7 +11052,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", @@ -11197,7 +11197,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index b5639547..1fcfe9dd 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -1,32 +1,30 @@ [package] authors.workspace = true -description = "FRAME NFTs pallet (polkadot v1.15.0)" +description = "NFTs pallet (polkadot v1.15.0) for Pop Network Runtimes" edition.workspace = true -homepage = "https://substrate.io" -license = "Apache-2.0" +license.workspace = true name = "pallet-nfts" readme = "README.md" -repository.workspace = true -version = "31.0.0" +version = "0.1.0" [package.metadata.docs.rs] targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] -codec = { workspace = true } -enumflags2 = { workspace = true } +codec.workspace = true +enumflags2.workspace = true frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true frame-system.workspace = true -log = { workspace = true } +log.workspace = true scale-info = { features = [ "derive" ], workspace = true } sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true [dev-dependencies] -pallet-balances = { default-features = true, workspace = true } -sp-keystore = { default-features = true, workspace = true } +pallet-balances.workspace = true +sp-keystore.workspace = true [features] default = [ "std" ] From 856c0cab1601bb15c94c022d973cd7e99b3a4888 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:06:00 +0700 Subject: [PATCH 52/79] fix(pallet-api): propagate try-runtime feature --- pallets/api/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index 99598c01..1e2b4074 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -68,5 +68,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-nfts/try-runtime", "sp-runtime/try-runtime", ] From b2390e2512cd2e99142c21ed831ca5328020877c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:36:29 +0700 Subject: [PATCH 53/79] fix(api/nonfungibles): unit tests --- pallets/api/src/nonfungibles/mod.rs | 4 +- pallets/api/src/nonfungibles/tests.rs | 407 ++++++++++++++------------ 2 files changed, 225 insertions(+), 186 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index efef2386..9655bbea 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -72,7 +72,7 @@ pub mod pallet { #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - TotalSupply(u128), + TotalSupply(u32), BalanceOf(u32), Allowance(bool), OwnerOf(Option>), @@ -390,7 +390,7 @@ pub mod pallet { use Read::*; match value { TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default().into(), + NftsOf::::collection_items(collection).unwrap_or_default(), ), BalanceOf { collection, owner } => ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 54d85516..d757508a 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,184 +1,223 @@ -// TODO -// use codec::Encode; -// use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; -// use frame_system::pallet_prelude::BlockNumberFor; -// use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings}; - -// use super::types::*; -// use crate::{ -// mock::*, -// nonfungibles::{Event, Read::*}, -// }; - -// const ITEM: u32 = 1; - -// #[test] -// fn mint_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let collection = create_collection(owner.clone()); -// // Successfully mint a new collection item. -// assert_ok!(NonFungibles::mint(signed(owner.clone()), owner.clone(), collection, ITEM)); -// System::assert_last_event(Event::Mint { to: owner, collection, item: ITEM }.into()); -// }); -// } - -// #[test] -// fn burn_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully burn an existing new collection item. -// assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); -// System::assert_last_event(Event::Burn { collection, item }.into()); -// }); -// } - -// #[test] -// fn transfer() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let dest = account(BOB); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully burn an existing new collection item. -// assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); -// System::assert_last_event( -// Event::Transfer { collection, item, from: owner, to: dest }.into(), -// ); -// }); -// } - -// #[test] -// fn approve_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = create_collection_mint(owner.clone(), ITEM); -// // Successfully approve `spender` to transfer the collection item. -// assert_ok!(NonFungibles::approve(signed(owner.clone()), collection, item, spender.clone())); -// System::assert_last_event( -// Event::Approval { collection, item, owner, spender: spender.clone() }.into(), -// ); -// // Successfully transfer the item by the delegated account `spender`. -// assert_ok!(Nfts::transfer(signed(spender.clone()), collection, item, spender)); -// }); -// } - -// #[test] -// fn cancel_approval_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = -// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); -// // Successfully cancel the transfer approval of `spender` by `owner`. -// assert_ok!(NonFungibles::cancel_approval(signed(owner), collection, item, spender.clone())); -// // Failed to transfer the item by `spender` without permission. -// assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); -// }); -// } - -// #[test] -// fn owner_of_works() {} - -// #[test] -// fn collection_owner_works() { -// new_test_ext().execute_with(|| { -// let collection = create_collection(account(ALICE)); -// assert_eq!( -// NonFungibles::read_state(CollectionOwner(collection)), -// Nfts::collection_owner(collection).encode() -// ); -// }); -// } - -// #[test] -// fn total_supply_works() { -// new_test_ext().execute_with(|| { -// let (collection, _) = create_collection_mint(account(ALICE), ITEM); -// assert_eq!( -// NonFungibles::read_state(TotalSupply(collection)), -// (Nfts::items(&collection).count() as u8).encode() -// ); -// }); -// } - -// #[test] -// fn collection_works() { -// new_test_ext().execute_with(|| { -// let (collection, _) = create_collection_mint(account(ALICE), ITEM); -// assert_eq!( -// NonFungibles::read_state(Collection(collection)), -// pallet_nfts::Collection::::get(&collection).encode(), -// ); -// }); -// } - -// #[test] -// fn balance_of_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let (collection, _) = create_collection_mint(owner.clone(), ITEM); -// assert_eq!( -// NonFungibles::read_state(BalanceOf { collection, owner: owner.clone() }), -// (Nfts::owned_in_collection(&collection, &owner).count() as u8).encode() -// ); -// }); -// } - -// #[test] -// fn allowance_works() { -// new_test_ext().execute_with(|| { -// let owner = account(ALICE); -// let spender = account(BOB); -// let (collection, item) = -// create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); -// assert_eq!( -// NonFungibles::read_state(Allowance { spender: spender.clone(), collection, item }), -// super::Pallet::::allowance(collection, item, spender).encode() -// ); -// }); -// } - -// fn signed(account: AccountId) -> RuntimeOrigin { -// RuntimeOrigin::signed(account) -// } - -// fn create_collection_mint_and_approve( -// owner: AccountIdOf, -// item: ItemIdOf, -// spender: AccountIdOf, -// ) -> (u32, u32) { -// let (collection, item) = create_collection_mint(owner.clone(), item); -// assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, spender, None)); -// (collection, item) -// } - -// fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { -// let collection = create_collection(owner.clone()); -// assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); -// (collection, item) -// } - -// fn create_collection(owner: AccountIdOf) -> u32 { -// let next_id = next_collection_id(); -// assert_ok!(Nfts::create( -// signed(owner.clone()), -// owner.clone(), -// collection_config_with_all_settings_enabled() -// )); -// next_id -// } - -// fn next_collection_id() -> u32 { -// pallet_nfts::NextCollectionId::::get().unwrap_or_default() -// } - -// fn collection_config_with_all_settings_enabled( -// ) -> CollectionConfig, CollectionIdOf> { -// CollectionConfig { -// settings: CollectionSettings::all_enabled(), -// max_supply: None, -// mint_settings: MintSettings::default(), -// } -// } +use codec::Encode; +use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; + +use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; +use crate::{ + mock::*, + nonfungibles::{Event, Read::*}, + Read, +}; + +const ITEM: u32 = 1; + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let collection = create_collection(owner.clone()); + // Successfully mint a new collection item. + assert_ok!(NonFungibles::mint( + signed(owner.clone()), + owner.clone(), + collection, + ITEM, + None + )); + System::assert_last_event( + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), value: None } + .into(), + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: None, value: None }.into(), + ); + }); +} + +#[test] +fn transfer() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let dest = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully burn an existing new collection item. + assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), value: None } + .into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let operator = account(BOB); + let (collection, item) = create_collection_mint(owner.clone(), ITEM); + // Successfully approve `spender` to transfer the collection item. + assert_ok!(NonFungibles::approve( + signed(owner.clone()), + collection, + Some(item), + operator.clone(), + true + )); + System::assert_last_event( + Event::Approval { + collection, + item: Some(item), + owner, + operator: operator.clone(), + approved: true, + } + .into(), + ); + // Successfully transfer the item by the delegated account `spender`. + assert_ok!(Nfts::transfer(signed(operator.clone()), collection, item, operator)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let spender = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); + // Successfully cancel the transfer approval of `spender` by `owner`. + assert_ok!(NonFungibles::approve( + signed(owner), + collection, + Some(item), + spender.clone(), + false + )); + // Failed to transfer the item by `spender` without permission. + assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + }); +} + +#[test] +fn owner_of_works() {} + +#[test] +fn collection_owner_works() { + new_test_ext().execute_with(|| { + let collection = create_collection(account(ALICE)); + assert_eq!( + NonFungibles::read(CollectionOwner(collection)).encode(), + Nfts::collection_owner(collection).encode() + ); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + Nfts::collection_items(collection).unwrap_or_default().encode() + ); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let (collection, _) = create_collection_mint(account(ALICE), ITEM); + assert_eq!( + NonFungibles::read(Collection(collection)).encode(), + pallet_nfts::Collection::::get(&collection).encode(), + ); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let (collection, _) = create_collection_mint(owner.clone(), ITEM); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: owner.clone() }).encode(), + AccountBalance::::get(collection, owner).encode() + ); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let owner = account(ALICE); + let operator = account(BOB); + let (collection, item) = + create_collection_mint_and_approve(owner.clone(), ITEM, operator.clone()); + assert_eq!( + NonFungibles::read(Allowance { + collection, + item: Some(item), + owner: owner.clone(), + operator: operator.clone(), + }) + .encode(), + Nfts::check_allowance(&collection, &Some(item), &owner, &operator) + .is_ok() + .encode() + ); + }); +} + +fn signed(account: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account) +} + +fn create_collection_mint_and_approve( + owner: AccountIdOf, + item: ItemIdOf, + spender: AccountIdOf, +) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner.clone(), item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), spender, None)); + (collection, item) +} + +fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner.clone()); + assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); + (collection, item) +} + +fn create_collection(owner: AccountIdOf) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner.clone()), + owner.clone(), + collection_config_with_all_settings_enabled() + )); + next_id +} + +fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} From 0bc5f1126dca5d4e0dcd701fadaccb18994f644e Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:15:11 +0700 Subject: [PATCH 54/79] test(api/nonfungibles): encoding_read_result --- pallets/api/src/nonfungibles/mod.rs | 10 +-- pallets/api/src/nonfungibles/tests.rs | 109 ++++++++++++++++++++++++-- pallets/nfts/src/types.rs | 22 +++--- 3 files changed, 120 insertions(+), 21 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 9655bbea..eb88bfa6 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -137,8 +137,8 @@ pub mod pallet { from: Option>, /// The recipient of the transfer. `None` when burning. to: Option>, - /// The amount minted. - value: Option>, + /// The price of the collection item. + price: Option>, }, } @@ -167,7 +167,7 @@ pub mod pallet { item, from: None, to: Some(account), - value: mint_price, + price: mint_price, }); Ok(()) } @@ -186,7 +186,7 @@ pub mod pallet { item, from: Some(account), to: None, - value: None, + price: None, }); Ok(()) } @@ -206,7 +206,7 @@ pub mod pallet { item, from: Some(from), to: Some(to), - value: None, + price: None, }); Ok(()) } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index d757508a..063ceee3 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,23 +1,119 @@ use codec::Encode; -use frame_support::{assert_ok, traits::nonfungibles_v2::InspectEnumerable}; +use frame_support::assert_ok; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; use crate::{ mock::*, - nonfungibles::{Event, Read::*}, + nonfungibles::{Event, Read::*, ReadResult}, Read, }; const ITEM: u32 = 1; +mod encoding_read_result { + use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; + use sp_runtime::{BoundedBTreeMap, BoundedVec}; + + use super::*; + + #[test] + fn total_supply() { + let total_supply: u32 = 1_000_000; + assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); + } + + #[test] + fn balance_of() { + let balance: u32 = 100; + assert_eq!(ReadResult::BalanceOf::(balance).encode(), balance.encode()); + } + + #[test] + fn allowance() { + let allowance = false; + assert_eq!(ReadResult::Allowance::(allowance).encode(), allowance.encode()); + } + + #[test] + fn owner_of() { + let mut owner = Some(account(ALICE)); + assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); + owner = None; + assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); + } + + #[test] + fn get_attribute() { + let mut attribute = Some(BoundedVec::truncate_from("some attribute".as_bytes().to_vec())); + assert_eq!( + ReadResult::GetAttribute::(attribute.clone()).encode(), + attribute.encode() + ); + attribute = None; + assert_eq!( + ReadResult::GetAttribute::(attribute.clone()).encode(), + attribute.encode() + ); + } + + #[test] + fn collection_owner() { + let mut collection_owner = Some(account(ALICE)); + assert_eq!( + ReadResult::CollectionOwner::(collection_owner.clone()).encode(), + collection_owner.encode() + ); + collection_owner = None; + assert_eq!( + ReadResult::CollectionOwner::(collection_owner.clone()).encode(), + collection_owner.encode() + ); + } + + #[test] + fn collection() { + let mut collection_details = Some(CollectionDetails { + owner: account(ALICE), + owner_deposit: 0, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }); + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + collection_details = None; + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + } + + #[test] + fn item() { + let mut item_details = Some(ItemDetails { + owner: account(ALICE), + approvals: BoundedBTreeMap::default(), + deposit: ItemDeposit { amount: 0, account: account(BOB) }, + }); + assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); + item_details = None; + assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); + } +} + #[test] fn mint_works() { new_test_ext().execute_with(|| { let owner = account(ALICE); let collection = create_collection(owner.clone()); // Successfully mint a new collection item. + let balance_before_mint = AccountBalance::::get(collection.clone(), owner.clone()); + // assert_ok!(NonFungibles::mint( signed(owner.clone()), owner.clone(), @@ -25,8 +121,11 @@ fn mint_works() { ITEM, None )); + let balance_after_mint = AccountBalance::::get(collection.clone(), owner.clone()); + assert_eq!(balance_after_mint, 1); + assert_eq!(balance_after_mint - balance_before_mint, 1); System::assert_last_event( - Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), value: None } + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } .into(), ); }); @@ -40,7 +139,7 @@ fn burn_works() { // Successfully burn an existing new collection item. assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None, value: None }.into(), + Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), ); }); } @@ -54,7 +153,7 @@ fn transfer() { // Successfully burn an existing new collection item. assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), value: None } + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } .into(), ); }); diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index f08f1d09..061352c0 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -94,18 +94,18 @@ pub(super) type PreSignedAttributesOf = PreSignedAttributes< #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Collection's owner. - pub(super) owner: AccountId, + pub owner: AccountId, /// The total balance deposited by the owner for all the storage data associated with this /// collection. Used by `destroy`. - pub(super) owner_deposit: DepositBalance, + pub owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. - pub(super) items: u32, + pub items: u32, /// The total number of outstanding item metadata of this collection. - pub(super) item_metadatas: u32, + pub item_metadatas: u32, /// The total number of outstanding item configs of this collection. - pub(super) item_configs: u32, + pub item_configs: u32, /// The total number of attributes for this collection. - pub(super) attributes: u32, + pub attributes: u32, } /// Witness data for the destroy transactions. @@ -145,21 +145,21 @@ pub struct MintWitness { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ItemDetails { /// The owner of this item. - pub(super) owner: AccountId, + pub owner: AccountId, /// The approved transferrer of this item, if one is set. - pub(super) approvals: Approvals, + pub 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: Deposit, + pub 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, + pub account: AccountId, /// An amount that gets reserved. - pub(super) amount: DepositBalance, + pub amount: DepositBalance, } /// Information about the collection's metadata. From a2da82ebd83145a39d41d8ba8129f3b7ca332041 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:51:50 +0700 Subject: [PATCH 55/79] refactor(api/nonfungibles): pallet tests --- pallets/api/src/nonfungibles/tests.rs | 243 +++++++++++++++----------- 1 file changed, 143 insertions(+), 100 deletions(-) diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 063ceee3..05c82f60 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,7 +1,11 @@ use codec::Encode; -use frame_support::assert_ok; +use frame_support::{assert_noop, assert_ok}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AccountBalance, CollectionConfig, CollectionSettings, MintSettings}; +use pallet_nfts::{ + AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, + ItemDetails, MintSettings, +}; +use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; use crate::{ @@ -12,10 +16,9 @@ use crate::{ const ITEM: u32 = 1; -mod encoding_read_result { - use pallet_nfts::{CollectionDetails, ItemDeposit, ItemDetails}; - use sp_runtime::{BoundedBTreeMap, BoundedVec}; +type NftsError = pallet_nfts::Error; +mod encoding_read_result { use super::*; #[test] @@ -107,53 +110,72 @@ mod encoding_read_result { } #[test] -fn mint_works() { +fn transfer() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let collection = create_collection(owner.clone()); - // Successfully mint a new collection item. - let balance_before_mint = AccountBalance::::get(collection.clone(), owner.clone()); - // - assert_ok!(NonFungibles::mint( - signed(owner.clone()), - owner.clone(), - collection, - ITEM, - None - )); - let balance_after_mint = AccountBalance::::get(collection.clone(), owner.clone()); - assert_eq!(balance_after_mint, 1); - assert_eq!(balance_after_mint - balance_before_mint, 1); + let owner = ALICE; + let dest = BOB; + + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::transfer(origin, collection, item, account(dest)), + BadOrigin + ); + } + // Successfully burn an existing new collection item. + let balance_before_transfer = AccountBalance::::get(collection, &account(dest)); + assert_ok!(NonFungibles::transfer(signed(owner), collection, ITEM, account(dest))); + let balance_after_transfer = AccountBalance::::get(collection, &account(dest)); + assert_eq!(AccountBalance::::get(collection, &account(owner)), 0); + assert_eq!(balance_after_transfer - balance_before_transfer, 1); System::assert_last_event( - Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } - .into(), + Event::Transfer { + collection, + item, + from: Some(account(owner)), + to: Some(account(dest)), + price: None, + } + .into(), ); }); } #[test] -fn burn_works() { +fn mint_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::burn(signed(owner.clone()), collection, ITEM)); + let owner = ALICE; + let collection = nfts::create_collection(owner); + + // Successfully mint a new collection item. + let balance_before_mint = AccountBalance::::get(collection, account(owner)); + assert_ok!(NonFungibles::mint(signed(owner), account(owner), collection, ITEM, None)); + let balance_after_mint = AccountBalance::::get(collection, account(owner)); + assert_eq!(balance_after_mint, 1); + assert_eq!(balance_after_mint - balance_before_mint, 1); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), + Event::Transfer { + collection, + item: ITEM, + from: None, + to: Some(account(owner)), + price: None, + } + .into(), ); }); } #[test] -fn transfer() { +fn burn_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let dest = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); + let owner = ALICE; + // Successfully burn an existing new collection item. - assert_ok!(NonFungibles::transfer(signed(owner.clone()), collection, ITEM, dest.clone())); + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + assert_ok!(NonFungibles::burn(signed(owner), collection, ITEM)); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + Event::Transfer { collection, item, from: Some(account(owner)), to: None, price: None } .into(), ); }); @@ -162,59 +184,63 @@ fn transfer() { #[test] fn approve_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let operator = account(BOB); - let (collection, item) = create_collection_mint(owner.clone(), ITEM); - // Successfully approve `spender` to transfer the collection item. + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + // Successfully approve `oeprator` to transfer the collection item. assert_ok!(NonFungibles::approve( - signed(owner.clone()), + signed(owner), collection, Some(item), - operator.clone(), + account(operator), true )); System::assert_last_event( Event::Approval { collection, item: Some(item), - owner, - operator: operator.clone(), + owner: account(owner), + operator: account(operator), approved: true, } .into(), ); - // Successfully transfer the item by the delegated account `spender`. - assert_ok!(Nfts::transfer(signed(operator.clone()), collection, item, operator)); + // Successfully transfer the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, account(operator))); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let spender = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, spender.clone()); - // Successfully cancel the transfer approval of `spender` by `owner`. + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + // Successfully cancel the transfer approval of `operator` by `owner`. assert_ok!(NonFungibles::approve( signed(owner), collection, Some(item), - spender.clone(), + account(operator), false )); - // Failed to transfer the item by `spender` without permission. - assert!(Nfts::transfer(signed(spender.clone()), collection, item, spender).is_err()); + // Failed to transfer the item by `operator` without permission. + assert_noop!( + Nfts::transfer(signed(operator), collection, item, account(operator)), + NftsError::NoPermission + ); }); } #[test] -fn owner_of_works() {} +fn owner_of_works() { + unimplemented!() +} #[test] fn collection_owner_works() { new_test_ext().execute_with(|| { - let collection = create_collection(account(ALICE)); + let collection = nfts::create_collection(ALICE); assert_eq!( NonFungibles::read(CollectionOwner(collection)).encode(), Nfts::collection_owner(collection).encode() @@ -225,7 +251,7 @@ fn collection_owner_works() { #[test] fn total_supply_works() { new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); + let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( NonFungibles::read(TotalSupply(collection)).encode(), Nfts::collection_items(collection).unwrap_or_default().encode() @@ -236,7 +262,7 @@ fn total_supply_works() { #[test] fn collection_works() { new_test_ext().execute_with(|| { - let (collection, _) = create_collection_mint(account(ALICE), ITEM); + let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( NonFungibles::read(Collection(collection)).encode(), pallet_nfts::Collection::::get(&collection).encode(), @@ -247,11 +273,11 @@ fn collection_works() { #[test] fn balance_of_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let (collection, _) = create_collection_mint(owner.clone(), ITEM); + let owner = ALICE; + let (collection, _) = nfts::create_collection_mint(owner, ITEM); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: owner.clone() }).encode(), - AccountBalance::::get(collection, owner).encode() + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + AccountBalance::::get(collection, account(owner)).encode() ); }); } @@ -259,64 +285,81 @@ fn balance_of_works() { #[test] fn allowance_works() { new_test_ext().execute_with(|| { - let owner = account(ALICE); - let operator = account(BOB); - let (collection, item) = - create_collection_mint_and_approve(owner.clone(), ITEM, operator.clone()); + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); assert_eq!( NonFungibles::read(Allowance { collection, item: Some(item), - owner: owner.clone(), - operator: operator.clone(), + owner: account(owner), + operator: account(operator), }) .encode(), - Nfts::check_allowance(&collection, &Some(item), &owner, &operator) + Nfts::check_allowance(&collection, &Some(item), &account(owner), &account(operator)) .is_ok() .encode() ); }); } -fn signed(account: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account) +fn signed(account_id: u8) -> RuntimeOrigin { + RuntimeOrigin::signed(account(account_id)) } -fn create_collection_mint_and_approve( - owner: AccountIdOf, - item: ItemIdOf, - spender: AccountIdOf, -) -> (u32, u32) { - let (collection, item) = create_collection_mint(owner.clone(), item); - assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), spender, None)); - (collection, item) +fn root() -> RuntimeOrigin { + RuntimeOrigin::root() } -fn create_collection_mint(owner: AccountIdOf, item: ItemIdOf) -> (u32, u32) { - let collection = create_collection(owner.clone()); - assert_ok!(Nfts::mint(signed(owner.clone()), collection, item, owner, None)); - (collection, item) +fn none() -> RuntimeOrigin { + RuntimeOrigin::none() } -fn create_collection(owner: AccountIdOf) -> u32 { - let next_id = next_collection_id(); - assert_ok!(Nfts::create( - signed(owner.clone()), - owner.clone(), - collection_config_with_all_settings_enabled() - )); - next_id -} +mod nfts { + use super::*; -fn next_collection_id() -> u32 { - pallet_nfts::NextCollectionId::::get().unwrap_or_default() -} + pub(super) fn create_collection_mint_and_approve( + owner: u8, + item: ItemIdOf, + operator: u8, + ) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner, item); + assert_ok!(Nfts::approve_transfer( + signed(owner), + collection, + Some(item), + account(operator), + None + )); + (collection, item) + } + + pub(super) fn create_collection_mint(owner: u8, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner); + assert_ok!(Nfts::mint(signed(owner), collection, item, account(owner), None)); + (collection, item) + } + + pub(super) fn create_collection(owner: u8) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner), + account(owner), + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } -fn collection_config_with_all_settings_enabled( -) -> CollectionConfig, CollectionIdOf> { - CollectionConfig { - settings: CollectionSettings::all_enabled(), - max_supply: None, - mint_settings: MintSettings::default(), + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } } } From 9d1496253be7e6e3d03c02194fb462f08db5794b Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:30:10 +0700 Subject: [PATCH 56/79] test(nonfungibles): add tests and remove collection owner read --- pallets/api/src/nonfungibles/mod.rs | 70 ++++--- pallets/api/src/nonfungibles/tests.rs | 267 +++++++++++++++++++++++--- pallets/api/src/nonfungibles/types.rs | 2 + 3 files changed, 286 insertions(+), 53 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index eb88bfa6..7377048f 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -12,7 +12,7 @@ mod types; #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::Incrementable}; use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, @@ -21,7 +21,8 @@ pub mod pallet { use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, + NftsOf, NftsWeightInfoOf, }; use super::*; @@ -32,54 +33,62 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// Number of items existing in a concrete collection. - #[codec(index = 2)] + /// Total item supply of a collection. + #[codec(index = 0)] TotalSupply(CollectionIdOf), - /// Returns the total number of items in the collection owned by the account. - #[codec(index = 3)] + /// Account balance for a specified collection. + #[codec(index = 1)] BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, - /// Whether a spender is allowed to transfer an item or items from owner. - #[codec(index = 6)] + /// Allowance for an operator approved by an owner, for a specified collection or item. + #[codec(index = 2)] Allowance { collection: CollectionIdOf, owner: AccountIdOf, operator: AccountIdOf, item: Option>, }, - /// Returns the owner of an item. - #[codec(index = 0)] + /// Owner of a specified collection item. + #[codec(index = 3)] OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Returns the attribute of `item` for the given `key`. - #[codec(index = 6)] + /// Attribute value of a collection item. + #[codec(index = 4)] GetAttribute { collection: CollectionIdOf, item: Option>, namespace: AttributeNamespaceOf, key: BoundedVec, }, - /// Returns the owner of a collection. - #[codec(index = 1)] - CollectionOwner(CollectionIdOf), - /// Returns the details of a collection. - #[codec(index = 4)] + /// Details of a collection. + #[codec(index = 6)] Collection(CollectionIdOf), - /// Returns the details of an item. - #[codec(index = 5)] + /// Details of a collection item. + #[codec(index = 7)] Item { collection: CollectionIdOf, item: ItemIdOf }, + /// Next collection ID. + #[codec(index = 8)] + NextCollectionId, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { + /// Total item supply of a collection. TotalSupply(u32), + /// Account balance for a specified collection. BalanceOf(u32), + /// Allowance for an operator approved by an owner, for a specified collection or item. Allowance(bool), + /// Owner of a specified collection owner. OwnerOf(Option>), + /// Attribute value of a collection item. GetAttribute(Option>), - CollectionOwner(Option>), + /// Details of a collection. Collection(Option>), + /// Details of a collection item. Item(Option>), + /// Next collection ID. + NextCollectionId(Option>), } impl ReadResult { @@ -88,13 +97,13 @@ pub mod pallet { use ReadResult::*; match self { OwnerOf(result) => result.encode(), - CollectionOwner(result) => result.encode(), TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), Collection(result) => result.encode(), Item(result) => result.encode(), Allowance(result) => result.encode(), GetAttribute(result) => result.encode(), + NextCollectionId(result) => result.encode(), } } } @@ -140,6 +149,15 @@ pub mod pallet { /// The price of the collection item. price: Option>, }, + /// Event emitted when a collection is created. + Created { + /// The collection identifier. + id: CollectionIdOf, + /// The creator of the collection. + creator: AccountIdOf, + /// The administrator of the collection. + admin: AccountIdOf, + }, } #[pallet::call] @@ -248,6 +266,10 @@ pub mod pallet { admin: AccountIdOf, config: CreateCollectionConfigFor, ) -> DispatchResult { + let id = NextCollectionIdOf::::get() + .or(T::CollectionId::initial_value()) + .ok_or(pallet_nfts::Error::::UnknownCollection)?; + let creator = ensure_signed(origin.clone())?; let collection_config = CollectionConfig { settings: CollectionSettings::all_enabled(), max_supply: config.max_supply, @@ -259,6 +281,7 @@ pub mod pallet { }, }; NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), collection_config)?; + Self::deposit_event(Event::Created { id, admin, creator }); Ok(()) } @@ -403,12 +426,13 @@ pub mod pallet { pallet_nfts::Attribute::::get((collection, item, namespace, key)) .map(|attribute| attribute.0), ), - CollectionOwner(collection) => - ReadResult::CollectionOwner(NftsOf::::collection_owner(collection)), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), Item { collection, item } => ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + NextCollectionId => ReadResult::NextCollectionId( + NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), + ), } } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 05c82f60..e89ba0bd 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,5 +1,5 @@ use codec::Encode; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, @@ -7,7 +7,7 @@ use pallet_nfts::{ }; use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; -use super::types::{AccountIdOf, CollectionIdOf, ItemIdOf}; +use super::types::{CollectionIdOf, ItemIdOf}; use crate::{ mock::*, nonfungibles::{Event, Read::*, ReadResult}, @@ -61,20 +61,6 @@ mod encoding_read_result { ); } - #[test] - fn collection_owner() { - let mut collection_owner = Some(account(ALICE)); - assert_eq!( - ReadResult::CollectionOwner::(collection_owner.clone()).encode(), - collection_owner.encode() - ); - collection_owner = None; - assert_eq!( - ReadResult::CollectionOwner::(collection_owner.clone()).encode(), - collection_owner.encode() - ); - } - #[test] fn collection() { let mut collection_details = Some(CollectionDetails { @@ -107,6 +93,20 @@ mod encoding_read_result { item_details = None; assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); } + + #[test] + fn next_collection_id_works() { + let mut next_collection_id = Some(0); + assert_eq!( + ReadResult::NextCollectionId::(next_collection_id).encode(), + next_collection_id.encode() + ); + next_collection_id = None; + assert_eq!( + ReadResult::NextCollectionId::(next_collection_id).encode(), + next_collection_id.encode() + ); + } } #[test] @@ -232,33 +232,212 @@ fn cancel_approval_works() { }); } +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, 10)); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + }); + assert_noop!( + Nfts::mint(signed(owner), collection, 42, account(owner), None), + NftsError::MaxSupplyReached + ); + }); +} + #[test] fn owner_of_works() { - unimplemented!() + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }).encode(), + Nfts::owner(collection, item).encode() + ); + }); +} + +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + result.encode() + ); + // Successfully get an existing attribute. + result = Some(value.clone()); + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + value, + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() + ); + }); } #[test] -fn collection_owner_works() { +fn clear_attribute_works() { new_test_ext().execute_with(|| { - let collection = nfts::create_collection(ALICE); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + BoundedVec::truncate_from("some value".as_bytes().to_vec()) + )); + // Successfully clear an attribute. + assert_ok!(Nfts::clear_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + )); assert_eq!( - NonFungibles::read(CollectionOwner(collection)).encode(), - Nfts::collection_owner(collection).encode() + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() ); }); } #[test] -fn total_supply_works() { +fn approve_item_attribute_works() { new_test_ext().execute_with(|| { - let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + )); + result = Some(value); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: pallet_nfts::AttributeNamespace::Account(account(BOB)), + key: attribute + }) + .encode(), + result.encode() + ); + }); +} + +#[test] +fn cancel_item_attribute_approval_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + )); + assert_ok!(Nfts::cancel_item_attributes_approval( + signed(ALICE), + collection, + item, + account(BOB), + pallet_nfts::CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert_noop!( + Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(account(BOB)), + attribute.clone(), + value.clone() + ), + NftsError::NoPermission + ); + }); +} + +#[test] +fn next_collection_id_works() { + new_test_ext().execute_with(|| { + let _ = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); assert_eq!( - NonFungibles::read(TotalSupply(collection)).encode(), - Nfts::collection_items(collection).unwrap_or_default().encode() + NonFungibles::read(NextCollectionId).encode(), + pallet_nfts::NextCollectionId::::get() + .or(CollectionIdOf::::initial_value()) + .encode(), ); }); } +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_eq!(NonFungibles::read(TotalSupply(collection)).encode(), (i + 1).encode()); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + Nfts::collection_items(collection).unwrap_or_default().encode() + ); + }); + }); +} + #[test] fn collection_works() { new_test_ext().execute_with(|| { @@ -271,23 +450,51 @@ fn collection_works() { } #[test] -fn balance_of_works() { +fn item_works() { new_test_ext().execute_with(|| { - let owner = ALICE; - let (collection, _) = nfts::create_collection_mint(owner, ITEM); + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), - AccountBalance::::get(collection, account(owner)).encode() + NonFungibles::read(Item { collection, item }).encode(), + pallet_nfts::Item::::get(&collection, &item).encode(), ); }); } +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + (i + 1).encode() + ); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + AccountBalance::::get(collection, account(owner)).encode() + ); + }); + }); +} + #[test] fn allowance_works() { new_test_ext().execute_with(|| { let owner = ALICE; let operator = BOB; let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + assert_eq!( + NonFungibles::read(Allowance { + collection, + item: Some(item), + owner: account(owner), + operator: account(operator), + }) + .encode(), + true.encode() + ); assert_eq!( NonFungibles::read(Allowance { collection, diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index 8c53ffd3..f57ee697 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -7,12 +7,14 @@ use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; +pub(super) type NftsErrorOf = pallet_nfts::Error; pub(super) type NftsWeightInfoOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. pub(super) type AccountIdOf = ::AccountId; pub(super) type BalanceOf = <>::Currency as Currency< ::AccountId, >>::Balance; +pub(super) type NextCollectionIdOf = pallet_nfts::NextCollectionId; pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; pub(super) type ItemIdOf = From d0dd77c94c1579f8dfa65dcd6a0e76520d7f6f59 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:17:07 +0700 Subject: [PATCH 57/79] fix(runtime): remove PartialEq, add Encode --- pallets/api/src/fungibles/mod.rs | 4 +- pallets/api/src/fungibles/tests.rs | 85 ++++++---- pallets/api/src/nonfungibles/mod.rs | 135 +++++++-------- pallets/api/src/nonfungibles/tests.rs | 15 +- pallets/api/src/nonfungibles/types.rs | 3 +- pallets/nfts/src/benchmarking.rs | 12 +- pop-api/Cargo.toml | 3 +- pop-api/src/lib.rs | 2 + pop-api/src/v0/mod.rs | 4 + pop-api/src/v0/nonfungibles/errors.rs | 33 ++++ pop-api/src/v0/nonfungibles/events.rs | 45 +++++ pop-api/src/v0/nonfungibles/mod.rs | 98 +++++++++++ pop-api/src/v0/nonfungibles/traits.rs | 87 ++++++++++ pop-api/src/v0/nonfungibles/types.rs | 41 +++++ runtime/devnet/src/config/api/mod.rs | 176 +++++++++++++++++--- runtime/devnet/src/config/api/versioning.rs | 37 ++-- runtime/devnet/src/lib.rs | 14 +- 17 files changed, 640 insertions(+), 154 deletions(-) create mode 100644 pop-api/src/v0/nonfungibles/errors.rs create mode 100644 pop-api/src/v0/nonfungibles/events.rs create mode 100644 pop-api/src/v0/nonfungibles/mod.rs create mode 100644 pop-api/src/v0/nonfungibles/traits.rs create mode 100644 pop-api/src/v0/nonfungibles/types.rs diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index bc36936c..50e5a218 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -43,7 +43,7 @@ pub mod pallet { /// State reads for the fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -84,7 +84,7 @@ pub mod pallet { /// Results of state reads for the fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total token supply for a specified token. TotalSupply(BalanceOf), diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 6e181a6f..a71c8394 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -30,47 +30,47 @@ mod encoding_read_result { #[test] fn total_supply() { let total_supply = 1_000_000 * UNIT; - assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); + assert_eq!(ReadResult::::TotalSupply(total_supply).encode(), total_supply.encode()); } #[test] fn balance_of() { let balance = 100 * UNIT; - assert_eq!(ReadResult::BalanceOf::(balance).encode(), balance.encode()); + assert_eq!(ReadResult::::BalanceOf(balance).encode(), balance.encode()); } #[test] fn allowance() { let allowance = 100 * UNIT; - assert_eq!(ReadResult::Allowance::(allowance).encode(), allowance.encode()); + assert_eq!(ReadResult::::Allowance(allowance).encode(), allowance.encode()); } #[test] fn token_name() { let mut name = Some("some name".as_bytes().to_vec()); - assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); name = None; - assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); } #[test] fn token_symbol() { let mut symbol = Some("some symbol".as_bytes().to_vec()); - assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); symbol = None; - assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); } #[test] fn token_decimals() { let decimals = 42; - assert_eq!(ReadResult::TokenDecimals::(decimals).encode(), decimals.encode()); + assert_eq!(ReadResult::::TokenDecimals(decimals).encode(), decimals.encode()); } #[test] fn token_exists() { let exists = true; - assert_eq!(ReadResult::TokenExists::(exists).encode(), exists.encode()); + assert_eq!(ReadResult::::TokenExists(exists).encode(), exists.encode()); } } @@ -513,11 +513,14 @@ fn total_supply_works() { new_test_ext().execute_with(|| { let total_supply = INIT_AMOUNT; assert_eq!( - Fungibles::read(TotalSupply(TOKEN)), - ReadResult::TotalSupply(Default::default()) + Fungibles::read(TotalSupply(TOKEN)).encode(), + ReadResult::::TotalSupply(Default::default()).encode() ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, total_supply); - assert_eq!(Fungibles::read(TotalSupply(TOKEN)), ReadResult::TotalSupply(total_supply)); + assert_eq!( + Fungibles::read(TotalSupply(TOKEN)).encode(), + ReadResult::::TotalSupply(total_supply).encode() + ); assert_eq!( Fungibles::read(TotalSupply(TOKEN)).encode(), Assets::total_supply(TOKEN).encode(), @@ -530,13 +533,13 @@ fn balance_of_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), - ReadResult::BalanceOf(Default::default()) + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + ReadResult::::BalanceOf(Default::default()).encode() ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, value); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }), - ReadResult::BalanceOf(value) + Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), + ReadResult::::BalanceOf(value).encode() ); assert_eq!( Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), @@ -554,8 +557,9 @@ fn allowance_works() { token: TOKEN, owner: account(ALICE), spender: account(BOB) - }), - ReadResult::Allowance(Default::default()) + }) + .encode(), + ReadResult::::Allowance(Default::default()).encode() ); assets::create_mint_and_approve(ALICE, TOKEN, ALICE, value * 2, BOB, value); assert_eq!( @@ -563,8 +567,9 @@ fn allowance_works() { token: TOKEN, owner: account(ALICE), spender: account(BOB) - }), - ReadResult::Allowance(value) + }) + .encode(), + ReadResult::::Allowance(value).encode() ); assert_eq!( Fungibles::read(Allowance { @@ -584,13 +589,31 @@ fn token_metadata_works() { let name: Vec = vec![11, 12, 13]; let symbol: Vec = vec![21, 22, 23]; let decimals: u8 = 69; - assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(None)); - assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(None)); - assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(0)); + assert_eq!( + Fungibles::read(TokenName(TOKEN)).encode(), + ReadResult::::TokenName(None).encode() + ); + assert_eq!( + Fungibles::read(TokenSymbol(TOKEN)).encode(), + ReadResult::::TokenSymbol(None).encode() + ); + assert_eq!( + Fungibles::read(TokenDecimals(TOKEN)).encode(), + ReadResult::::TokenDecimals(0).encode() + ); assets::create_and_set_metadata(ALICE, TOKEN, name.clone(), symbol.clone(), decimals); - assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(Some(name))); - assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(Some(symbol))); - assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(decimals)); + assert_eq!( + Fungibles::read(TokenName(TOKEN)).encode(), + ReadResult::::TokenName(Some(name)).encode() + ); + assert_eq!( + Fungibles::read(TokenSymbol(TOKEN)).encode(), + ReadResult::::TokenSymbol(Some(symbol)).encode() + ); + assert_eq!( + Fungibles::read(TokenDecimals(TOKEN)).encode(), + ReadResult::::TokenDecimals(decimals).encode() + ); assert_eq!(Fungibles::read(TokenName(TOKEN)).encode(), Some(Assets::name(TOKEN)).encode()); assert_eq!( Fungibles::read(TokenSymbol(TOKEN)).encode(), @@ -606,9 +629,15 @@ fn token_metadata_works() { #[test] fn token_exists_works() { new_test_ext().execute_with(|| { - assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(false)); + assert_eq!( + Fungibles::read(TokenExists(TOKEN)).encode(), + ReadResult::::TokenExists(false).encode() + ); assert_ok!(Assets::create(signed(ALICE), TOKEN, account(ALICE), 1)); - assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(true)); + assert_eq!( + Fungibles::read(TokenExists(TOKEN)).encode(), + ReadResult::::TokenExists(true).encode() + ); assert_eq!( Fungibles::read(TokenExists(TOKEN)).encode(), Assets::asset_exists(TOKEN).encode(), diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 7377048f..4939b7ee 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -5,10 +5,11 @@ pub use pallet::*; use pallet_nfts::WeightInfo; use sp_runtime::traits::StaticLookup; +pub use types::*; #[cfg(test)] mod tests; -mod types; +pub mod types; #[frame_support::pallet] pub mod pallet { @@ -29,7 +30,7 @@ pub mod pallet { /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -48,10 +49,10 @@ pub mod pallet { item: Option>, }, /// Owner of a specified collection item. - #[codec(index = 3)] + #[codec(index = 5)] OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, - /// Attribute value of a collection item. - #[codec(index = 4)] + /// Attribute value of a collection item. (Error: bounded collection is not partial) + #[codec(index = 6)] GetAttribute { collection: CollectionIdOf, item: Option>, @@ -59,19 +60,19 @@ pub mod pallet { key: BoundedVec, }, /// Details of a collection. - #[codec(index = 6)] + #[codec(index = 9)] Collection(CollectionIdOf), /// Details of a collection item. - #[codec(index = 7)] + #[codec(index = 10)] Item { collection: CollectionIdOf, item: ItemIdOf }, /// Next collection ID. - #[codec(index = 8)] + #[codec(index = 11)] NextCollectionId, } /// Results of state reads for the non-fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total item supply of a collection. TotalSupply(u32), @@ -162,54 +163,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(NftsWeightInfoOf::::mint())] - pub fn mint( - origin: OriginFor, - to: AccountIdOf, - collection: CollectionIdOf, - item: ItemIdOf, - mint_price: Option>, - ) -> DispatchResult { - let account = ensure_signed(origin.clone())?; - let witness_data = MintWitness { mint_price, owned_item: Some(item) }; - NftsOf::::mint( - origin, - collection, - item, - T::Lookup::unlookup(to.clone()), - Some(witness_data), - )?; - Self::deposit_event(Event::Transfer { - collection, - item, - from: None, - to: Some(account), - price: mint_price, - }); - Ok(()) - } - - #[pallet::call_index(1)] - #[pallet::weight(NftsWeightInfoOf::::burn())] - pub fn burn( - origin: OriginFor, - collection: CollectionIdOf, - item: ItemIdOf, - ) -> DispatchResult { - let account = ensure_signed(origin.clone())?; - NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Transfer { - collection, - item, - from: Some(account), - to: None, - price: None, - }); - Ok(()) - } - - #[pallet::call_index(2)] + #[pallet::call_index(3)] #[pallet::weight(NftsWeightInfoOf::::transfer())] pub fn transfer( origin: OriginFor, @@ -229,7 +183,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(3)] + #[pallet::call_index(4)] #[pallet::weight(NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::cancel_approval())] pub fn approve( origin: OriginFor, @@ -259,7 +213,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(4)] + #[pallet::call_index(7)] #[pallet::weight(NftsWeightInfoOf::::create())] pub fn create( origin: OriginFor, @@ -285,7 +239,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(5)] + #[pallet::call_index(8)] #[pallet::weight(NftsWeightInfoOf::::destroy( witness.item_metadatas, witness.item_configs, @@ -299,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(6)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -312,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(7)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -324,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(8)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -335,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(9)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -345,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(10)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -361,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(11)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -379,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(12)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -388,6 +342,53 @@ pub mod pallet { ) -> DispatchResult { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } + + #[pallet::call_index(19)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + mint_price: Option>, + ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + let witness_data = MintWitness { mint_price, owned_item: Some(item) }; + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness_data), + )?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: None, + to: Some(account), + price: mint_price, + }); + Ok(()) + } + + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(account), + to: None, + price: None, + }); + Ok(()) + } } impl crate::Read for Pallet { diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index e89ba0bd..ad575469 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -264,7 +264,7 @@ fn get_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; // No attribute set. @@ -301,13 +301,18 @@ fn get_attribute_works() { }); } +#[test] +fn set_metadata_works() { + unimplemented!() +} + #[test] fn clear_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); - let mut result: Option::ValueLimit>> = None; + let result: Option::ValueLimit>> = None; assert_ok!(Nfts::set_attribute( signed(ALICE), collection, @@ -342,7 +347,7 @@ fn approve_item_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. @@ -374,9 +379,9 @@ fn cancel_item_attribute_approval_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - let mut result: Option::ValueLimit>> = None; + let result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); assert_ok!(Nfts::set_attribute( diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index f57ee697..be6d47e6 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,13 +1,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; +pub use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; use scale_info::TypeInfo; use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; -pub(super) type NftsErrorOf = pallet_nfts::Error; pub(super) type NftsWeightInfoOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. pub(super) type AccountIdOf = ::AccountId; diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index 8fa87557..ab66da26 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -578,9 +578,9 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let deadline = BlockNumberFor::::max_value(); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + assert_last_event::(Event::TransferApproved { collection, item: Some(item), owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { @@ -590,10 +590,10 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection, item: Some(item), owner: caller, delegate }.into()); } clear_all_transfer_approvals { @@ -603,7 +603,7 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 872a129d..4942db48 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -24,4 +24,5 @@ path = "src/lib.rs" default = [ "std" ] fungibles = [ ] messaging = [ ] -std = [ "ink/std", "pop-primitives/std" ] +nonfungibles = [ ] +std = [ "ink/std", "pop-primitives/std", "sp-io/std" ] diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 057aa3c4..1dc93915 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -73,6 +73,8 @@ mod constants { pub(crate) const FUNGIBLES: u8 = 150; pub(crate) const MESSAGING: u8 = 151; pub(crate) const INCENTIVES: u8 = 152; + pub(crate) const NONFUNGIBLES: u8 = 151; + pub(crate) const NFTS: u8 = 50; } // Helper method to build a dispatch call or a call to read state. diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index ecad085c..e0ca2b0d 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -14,6 +14,10 @@ pub mod incentives; #[cfg(feature = "messaging")] pub mod messaging; +/// APIs for nonfungible tokens. +#[cfg(feature = "nonfungibles")] +pub mod nonfungibles; + pub(crate) const V0: u8 = 0; impl From for Error { diff --git a/pop-api/src/v0/nonfungibles/errors.rs b/pop-api/src/v0/nonfungibles/errors.rs new file mode 100644 index 00000000..f7e2324c --- /dev/null +++ b/pop-api/src/v0/nonfungibles/errors.rs @@ -0,0 +1,33 @@ +//! A set of errors for use in smart contracts that interact with the nonfungibles api. This +//! includes errors compliant to standards. + +use super::*; + +/// The PSP34 error. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Psp34Error { + /// Custom error type for cases if writer of traits added own restrictions + Custom(String), + /// Returned if owner approves self + SelfApprove, + /// Returned if the caller doesn't have allowance for transferring. + NotApproved, + /// Returned if the owner already own the token. + TokenExists, + /// Returned if the token doesn't exist + TokenNotExists, + /// Returned if safe transfer check fails + SafeTransferCheckFailed(String), +} + +impl From for Psp34Error { + /// Converts a `StatusCode` to a `PSP22Error`. + fn from(value: StatusCode) -> Self { + let encoded = value.0.to_le_bytes(); + match encoded { + // TODO: Handle conversion. + _ => Psp34Error::Custom(value.0.to_string()), + } + } +} diff --git a/pop-api/src/v0/nonfungibles/events.rs b/pop-api/src/v0/nonfungibles/events.rs new file mode 100644 index 00000000..e11b2f31 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/events.rs @@ -0,0 +1,45 @@ +//! A set of events for use in smart contracts interacting with the nonfungibles API. +//! +//! The `Transfer` and `Approval` events conform to the PSP-34 standard. +//! +//! These events are not emitted by the API itself but can be used in your contracts to +//! track token operations. Be mindful of the costs associated with emitting events. +//! +//! For more details, refer to [ink! events](https://use.ink/basics/events). + +use super::*; + +/// Event emitted when a token transfer occurs. +#[ink::event] +pub struct Transfer { + /// The source of the transfer. `None` when minting. + from: Option, + /// The recipient of the transfer. `None` when burning. + to: Option, + /// The item transferred (or minted/burned). + item: ItemId, +} + +/// Event emitted when a token approve occurs. +#[ink::event] +pub struct Approval { + /// The owner providing the allowance. + owner: AccountId, + /// The beneficiary of the allowance. + operator: AccountId, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option, + /// Whether allowance is set or removed. + approved: bool, +} + +/// Event emitted when an attribute is set for a token. +#[ink::event] +pub struct AttributeSet { + /// The item which attribute is set. + item: ItemId, + /// The key for the attribute. + key: Vec, + /// The data for the attribute. + data: Vec, +} diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs new file mode 100644 index 00000000..601dd99e --- /dev/null +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -0,0 +1,98 @@ +//! The `nonfungibles` module provides an API for interacting and managing nonfungible tokens. +//! +//! The API includes the following interfaces: +//! 1. PSP-34 +//! 2. PSP-34 Metadata +//! 3. PSP-22 Mintable & Burnable + +use constants::*; +pub use errors::*; +pub use events::*; +use ink::prelude::vec::Vec; +pub use traits::*; +pub use types::*; + +use crate::{ + primitives::{AccountId, Balance, TokenId}, + ChainExtensionMethodApi, Result, StatusCode, +}; + +pub mod errors; +pub mod events; +pub mod traits; +pub mod types; + +/// Returns the total item supply for a specified collection. +/// +/// # Parameters +/// - `collection` - The collection. +#[inline] +pub fn total_supply(collection: CollectionId) -> Result { + build_read_state(TOTAL_SUPPLY) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(token)) +} + +/// Returns the account balance for a specified `token` and `owner`. Returns `0` if +/// the account is non-existent. +/// +/// # Parameters +/// - `token` - The token. +/// - `owner` - The account whose balance is being queried. +#[inline] +pub fn balance_of(token: TokenId, owner: AccountId) -> Result { + build_read_state(BALANCE_OF) + .input::<(TokenId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(token, owner)) +} + +mod constants { + /// 1. PSP-34 + pub(super) const TOTAL_SUPPLY: u8 = 0; + pub(super) const BALANCE_OF: u8 = 1; + pub(super) const ALLOWANCE: u8 = 2; + pub(super) const TRANSFER: u8 = 3; + pub(super) const APPROVE: u8 = 4; + pub(super) const OWNER_OF: u8 = 5; + + /// 2. PSP-34 Metadata + pub(super) const GET_ATTRIBUTE: u8 = 6; + + /// 3. Management + pub(super) const CREATE: u8 = 7; + pub(super) const DESTROY: u8 = 8; + pub(super) const COLLECTION: u8 = 9; + pub(super) const ITEM: u8 = 10; + pub(super) const NEXT_COLLECTION_ID: u8 = 11; + pub(super) const SET_ATTRIBUTE: u8 = 12; + pub(super) const CLEAR_ATTRIBUTE: u8 = 13; + pub(super) const SET_METADATA: u8 = 14; + pub(super) const CLEAR_METADATA: u8 = 15; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; + pub(super) const SET_MAX_SUPPLY: u8 = 18; + + /// 4. PSP-34 Mintable & Burnable + pub(super) const MINT: u8 = 19; + pub(super) const BURN: u8 = 20; +} + +// Helper method to build a dispatch call. +// +// Parameters: +// - 'dispatchable': The index of the dispatchable function within the module. +fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { + crate::v0::build_dispatch(FUNGIBLES, dispatchable) +} + +// Helper method to build a call to read state. +// +// Parameters: +// - 'state_query': The index of the runtime state query. +fn build_read_state(state_query: u8) -> ChainExtensionMethodApi { + crate::v0::build_read_state(FUNGIBLES, state_query) +} diff --git a/pop-api/src/v0/nonfungibles/traits.rs b/pop-api/src/v0/nonfungibles/traits.rs new file mode 100644 index 00000000..616767a3 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/traits.rs @@ -0,0 +1,87 @@ +//! Traits that can be used by contracts. Including standard compliant traits. + +use core::result::Result; + +use ink::prelude::string::String; + +use super::*; + +#[ink::trait_definition] +pub trait Psp34 { + /// Returns the collection `Id`. + #[ink(message, selector = 0xffa27a5f)] + fn collection_id(&self) -> ItemId; + + // Returns the current total supply of the NFT. + #[ink(message, selector = 0x628413fe)] + fn total_supply(&self) -> u128; + + /// Returns the amount of items the owner has within a collection. + /// + /// # Parameters + /// - `owner` - The account whose balance is being queried. + #[ink(message, selector = 0xcde7e55f)] + fn balance_of(&self, owner: AccountId) -> u32; + + /// Returns whether the operator is approved by the owner to withdraw `item`. If `item` is + /// `None`, it returns whether the operator is approved to withdraw all owner's items for the + /// given collection. + /// + /// # Parameters + /// * `owner` - The account that owns the item(s). + /// * `operator` - the account that is allowed to withdraw the item(s). + /// * `item` - The item. If `None`, it is regarding all owner's items in collection. + #[ink(message, selector = 0x4790f55a)] + fn allowance(&self, owner: AccountId, operator: AccountId, id: Option) -> bool; + + /// Transfers an owned or approved item to the specified recipient. + /// + /// # Parameters + /// * `to` - The recipient account. + /// * `item` - The item. + /// - `data` - Additional data in unspecified format. + #[ink(message, selector = 0x3128d61b)] + fn transfer(&mut self, to: AccountId, id: ItemId, data: Vec) -> Result<(), Psp34Error>; + + /// Approves operator to withdraw item(s) from the contract's account. + /// + /// # Parameters + /// * `operator` - The account that is allowed to withdraw the item. + /// * `item` - Optional item. `None` means all items owned in the specified collection. + /// * `approved` - Whether the operator is given or removed the right to withdraw the item(s). + #[ink(message, selector = 0x1932a8b0)] + fn approve( + &mut self, + operator: AccountId, + id: Option, + approved: bool, + ) -> Result<(), Psp34Error>; + + /// Returns the owner of an item within a specified collection, if any. + /// + /// # Parameters + /// * `item` - The item. + #[ink(message, selector = 0x1168624d)] + fn owner_of(&self, id: ItemId) -> Option; +} + +#[ink::trait_definition] +pub trait Psp34Metadata { + /// Returns the attribute of `item` for the given `key`. + /// + /// # Parameters + /// * `item` - The item. If `None` the attributes for the collection are queried. + /// * `namespace` - The attribute's namespace. + /// * `key` - The key of the attribute. + #[ink(message, selector = 0xf19d48d1)] + fn get_attribute(&self, id: ItemId, key: Vec) -> Option>; +} + +#[ink::trait_definition] +pub trait Psp34Enumerable { + #[ink(message, selector = 0x3bcfb511)] + fn owners_token_by_index(&self, owner: AccountId, index: u128) -> Result; + + #[ink(message, selector = 0xcd0340d0)] + fn token_by_index(&self, index: u128) -> Result; +} diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs new file mode 100644 index 00000000..0cae037f --- /dev/null +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -0,0 +1,41 @@ +use super::*; +use crate::primitives::AccountId; + +pub type ItemId = u32; +pub type Collection = u32; + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CreateCollectionConfig { + pub max_supply: Option, + pub mint_type: MintType, + pub price: Option, + pub start_block: Option, + pub end_block: Option, +} + +/// Attribute namespaces for non-fungible tokens. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum AttributeNamespace { + /// An attribute set by collection's owner. + #[codec(index = 1)] + CollectionOwner, + /// An attribute set by item's owner. + #[codec(index = 2)] + ItemOwner, + /// An attribute set by a pre-approved account. + #[codec(index = 3)] + Account(AccountId), +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +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), +} diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 911c6e19..9e92febf 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use codec::Decode; +use codec::{Decode, Encode}; use cumulus_primitives_core::Weight; use frame_support::traits::{ConstU32, Contains}; pub(crate) use pallet_api::Extension; @@ -13,7 +13,7 @@ use versioning::*; use crate::{ config::{assets::TrustBackedAssetsInstance, xcm::LocalOriginToLocation}, fungibles, messaging, Balances, Ismp, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, - RuntimeHoldReason, TransactionByteFee, + RuntimeHoldReason, TransactionByteFee, fungibles, nonfungibles, }; mod versioning; @@ -28,7 +28,7 @@ type DecodesAs = pallet_api::extension::DecodesAs< /// A query of runtime state. #[derive(Decode, Debug)] -#[cfg_attr(test, derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] #[repr(u8)] pub enum RuntimeRead { /// Fungible token queries. @@ -37,6 +37,9 @@ pub enum RuntimeRead { /// Messaging state queries. #[codec(index = 151)] Messaging(messaging::Read), + // Non-fungible token queries. + #[codec(index = 152)] + NonFungibles(nonfungibles::Read), } impl Readable for RuntimeRead { @@ -49,6 +52,7 @@ impl Readable for RuntimeRead { match self { RuntimeRead::Fungibles(key) => fungibles::Pallet::weight(key), RuntimeRead::Messaging(key) => messaging::Pallet::weight(key), + RuntimeRead::NonFungibles(key) => nonfungibles::Pallet::weight(key), } } @@ -57,17 +61,21 @@ impl Readable for RuntimeRead { match self { RuntimeRead::Fungibles(key) => RuntimeResult::Fungibles(fungibles::Pallet::read(key)), RuntimeRead::Messaging(key) => RuntimeResult::Messaging(messaging::Pallet::read(key)), + RuntimeRead::NonFungibles(key) => + RuntimeResult::NonFungibles(nonfungibles::Pallet::read(key)), } } } /// The result of a runtime state read. #[derive(Debug)] -#[cfg_attr(feature = "std", derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] pub enum RuntimeResult { /// Fungible token read results. Fungibles(fungibles::ReadResult), - // Messaging state read results. + /// Non-fungible token read results. + NonFungibles(nonfungibles::ReadResult), + /// Messaging state read results. Messaging(messaging::ReadResult), } @@ -77,6 +85,7 @@ impl RuntimeResult { match self { RuntimeResult::Fungibles(result) => result.encode(), RuntimeResult::Messaging(result) => result.encode(), + RuntimeResult::NonFungibles(result) => result.encode(), } } } @@ -108,6 +117,10 @@ impl fungibles::Config for Runtime { type WeightInfo = fungibles::weights::SubstrateWeight; } +impl nonfungibles::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + #[derive(Default)] pub struct Config; impl pallet_api::extension::Config for Config { @@ -163,6 +176,7 @@ impl> Contains f fn contains(c: &RuntimeCall) -> bool { use fungibles::Call::*; use messaging::Call::*; + use nonfungibles::Call::*; use pallet_incentives::Call::*; T::BaseCallFilter::contains(c) && matches!( @@ -183,15 +197,27 @@ impl> Contains f remove { .. } ) | RuntimeCall::Incentives( register_contract { .. } | claim_rewards { .. } | deposit_funds { .. } + ) | RuntimeCall::NonFungibles( + transfer { .. } | + approve { .. } | create { .. } | + destroy { .. } | set_metadata { .. } | + clear_metadata { .. } | + set_attribute { .. } | + clear_attribute { .. } | + approve_item_attributes { .. } | + cancel_item_attributes_approval { .. } | + mint { .. } | burn { .. } | + set_max_supply { .. }, ) ) - } + }; } impl Contains for Filter { fn contains(r: &RuntimeRead) -> bool { use fungibles::Read::*; use messaging::Read::*; + use nonfungibles::Read::*; matches!( r, RuntimeRead::Fungibles( @@ -201,7 +227,13 @@ impl Contains for Filter { TokenName(..) | TokenSymbol(..) | TokenDecimals(..) | TokenExists(..) - ) | RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..)) + ) | RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..)) | RuntimeRead::NonFungibles( + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + OwnerOf { .. } | GetAttribute { .. } | + Collection { .. } | Item { .. } | + NextCollectionId + ) ) } } @@ -209,9 +241,8 @@ impl Contains for Filter { #[cfg(test)] mod tests { use codec::Encode; - use pallet_api::fungibles::Call::*; - use sp_core::crypto::AccountId32; - use RuntimeCall::{Balances, Fungibles}; + use sp_core::{bounded_vec, crypto::AccountId32}; + use RuntimeCall::{Balances, Fungibles, NonFungibles}; use super::*; @@ -222,6 +253,10 @@ mod tests { let value = 1_000; let result = fungibles::ReadResult::::TotalSupply(value); assert_eq!(RuntimeResult::Fungibles(result).encode(), value.encode()); + + let value = 1_000; + let result = nonfungibles::ReadResult::::TotalSupply(value); + assert_eq!(RuntimeResult::NonFungibles(result).encode(), value.encode()); } #[test] @@ -229,7 +264,7 @@ mod tests { use pallet_balances::{AdjustmentDirection, Call::*}; use sp_runtime::MultiAddress; - const CALLS: [RuntimeCall; 4] = [ + for call in vec![ Balances(force_adjust_total_issuance { direction: AdjustmentDirection::Increase, delta: 0, @@ -241,16 +276,18 @@ mod tests { value: 0, }), Balances(force_unreserve { who: MultiAddress::Address32([0u8; 32]), amount: 0 }), - ]; - - for call in CALLS { - assert!(!Filter::::contains(&call)) + ] + .iter() + { + assert!(!Filter::::contains(call)) } } #[test] fn filter_allows_fungibles_calls() { - const CALLS: [RuntimeCall; 11] = [ + use pallet_api::fungibles::Call::*; + + for call in vec![ Fungibles(transfer { token: 0, to: ACCOUNT, value: 0 }), Fungibles(transfer_from { token: 0, from: ACCOUNT, to: ACCOUNT, value: 0 }), Fungibles(approve { token: 0, spender: ACCOUNT, value: 0 }), @@ -262,17 +299,80 @@ mod tests { Fungibles(clear_metadata { token: 0 }), Fungibles(mint { token: 0, account: ACCOUNT, value: 0 }), Fungibles(burn { token: 0, account: ACCOUNT, value: 0 }), - ]; + ] + .iter() + { + assert!(Filter::::contains(call)) + } + } - for call in CALLS { - assert!(Filter::::contains(&call)) + #[test] + fn filter_allows_nonfungibles_calls() { + use pallet_api::nonfungibles::{ + types::{CreateCollectionConfig, MintType}, + Call::*, + }; + use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness}; + + for call in vec![ + NonFungibles(transfer { collection: 0, item: 0, to: ACCOUNT }), + NonFungibles(approve { + collection: 0, + item: Some(0), + operator: ACCOUNT, + approved: false, + }), + NonFungibles(create { + admin: ACCOUNT, + config: CreateCollectionConfig { + max_supply: Some(0), + mint_type: MintType::Public, + price: None, + start_block: Some(0), + end_block: None, + }, + }), + NonFungibles(destroy { + collection: 0, + witness: DestroyWitness { attributes: 0, item_configs: 0, item_metadatas: 0 }, + }), + NonFungibles(set_attribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::Pallet, + key: bounded_vec![], + value: bounded_vec![], + }), + NonFungibles(clear_attribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::Pallet, + key: bounded_vec![], + }), + NonFungibles(set_metadata { collection: 0, item: 0, data: bounded_vec![] }), + NonFungibles(clear_metadata { collection: 0, item: 0 }), + NonFungibles(approve_item_attributes { collection: 0, item: 0, delegate: ACCOUNT }), + NonFungibles(cancel_item_attributes_approval { + collection: 0, + item: 0, + delegate: ACCOUNT, + witness: CancelAttributesApprovalWitness { account_attributes: 0 }, + }), + NonFungibles(set_max_supply { collection: 0, max_supply: 0 }), + NonFungibles(mint { to: ACCOUNT, collection: 0, item: 0, mint_price: None }), + NonFungibles(burn { collection: 0, item: 0 }), + ] + .iter() + { + assert!(Filter::::contains(call)) } } #[test] fn filter_allows_fungibles_reads() { use super::{fungibles::Read::*, RuntimeRead::*}; - const READS: [RuntimeRead; 7] = [ + + for read in vec![ Fungibles(TotalSupply(1)), Fungibles(BalanceOf { token: 1, owner: ACCOUNT }), Fungibles(Allowance { token: 1, owner: ACCOUNT, spender: ACCOUNT }), @@ -280,10 +380,40 @@ mod tests { Fungibles(TokenSymbol(1)), Fungibles(TokenDecimals(10)), Fungibles(TokenExists(1)), - ]; + ] + .iter() + { + assert!(Filter::::contains(read)) + } + } - for read in READS { - assert!(Filter::::contains(&read)) + #[test] + fn filter_allows_nonfungibles_reads() { + use super::{nonfungibles::Read::*, RuntimeRead::*}; + + for read in vec![ + NonFungibles(TotalSupply(1)), + NonFungibles(BalanceOf { collection: 1, owner: ACCOUNT }), + NonFungibles(Allowance { + collection: 1, + item: None, + owner: ACCOUNT, + operator: ACCOUNT, + }), + NonFungibles(OwnerOf { collection: 1, item: 1 }), + // NonFungibles(GetAttribute { + // collection: 0, + // item: Some(0), + // namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + // key: bounded_vec![], + // }), + NonFungibles(Collection(1)), + NonFungibles(Item { collection: 1, item: 1 }), + NonFungibles(NextCollectionId), + ] + .iter() + { + assert!(Filter::::contains(read)) } } } diff --git a/runtime/devnet/src/config/api/versioning.rs b/runtime/devnet/src/config/api/versioning.rs index 76e59e6a..0e36646d 100644 --- a/runtime/devnet/src/config/api/versioning.rs +++ b/runtime/devnet/src/config/api/versioning.rs @@ -1,3 +1,4 @@ +use codec::Encode; use sp_runtime::ModuleError; use super::*; @@ -40,7 +41,7 @@ impl From for RuntimeRead { /// Versioned runtime state read results. #[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Clone))] +#[cfg_attr(test, derive(Encode, Clone))] pub enum VersionedRuntimeResult { /// Version zero of runtime read results. V0(RuntimeResult), @@ -118,9 +119,9 @@ impl From for V0Error { // Note: message not used let ModuleError { index, error, message: _message } = error; // Map `pallet-contracts::Error::DecodingFailed` to `Error::DecodingFailed` - if index as usize == - ::index() && - error == DECODING_FAILED_ERROR + if index as usize + == ::index() + && error == DECODING_FAILED_ERROR { Error::DecodingFailed } else { @@ -175,13 +176,19 @@ mod tests { fn from_versioned_runtime_call_to_runtime_call_works() { let call = RuntimeCall::System(Call::remark_with_event { remark: "pop".as_bytes().to_vec() }); - assert_eq!(RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())), call); + assert_eq!( + RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())).encode(), + call.encode() + ); } #[test] fn from_versioned_runtime_read_to_runtime_read_works() { let read = RuntimeRead::Fungibles(fungibles::Read::::TotalSupply(42)); - assert_eq!(RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())), read); + assert_eq!( + RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())).encode(), + read.encode() + ); } #[test] @@ -189,21 +196,21 @@ mod tests { let result = RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)); let v0 = 0; assert_eq!( - VersionedRuntimeResult::try_from((result.clone(), v0)), - Ok(VersionedRuntimeResult::V0(result.clone())) + VersionedRuntimeResult::try_from((result.clone(), v0)).unwrap().encode(), + VersionedRuntimeResult::V0(result.clone()).encode() ); } #[test] fn versioned_runtime_result_fails() { // Unknown version 1. - assert_eq!( - VersionedRuntimeResult::try_from(( - RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), - 1 - )), - Err(pallet_revive::Error::::DecodingFailed.into()) - ); + let err = VersionedRuntimeResult::try_from(( + RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), + 1, + )) + .unwrap_err(); + let expected_err: DispatchError = pallet_contracts::Error::::DecodingFailed.into(); + assert_eq!(err.encode(), expected_err.encode()); } #[test] diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index e0579078..f9fb341a 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -41,7 +41,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -use pallet_api::{fungibles, messaging}; +use pallet_api::{fungibles, messaging, nonfungibles}; use pallet_balances::Call as BalancesCall; use pallet_ismp::mmr::{Leaf, Proof, ProofKeys}; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; @@ -249,10 +249,10 @@ impl Contains for FilteredCalls { matches!( c, RuntimeCall::Balances( - force_adjust_total_issuance { .. } | - force_set_balance { .. } | - force_transfer { .. } | - force_unreserve { .. } + force_adjust_total_issuance { .. } + | force_set_balance { .. } + | force_transfer { .. } + | force_unreserve { .. } ) ) } @@ -697,6 +697,9 @@ mod runtime { #[runtime::pallet_index(152)] pub type Incentives = pallet_incentives::Pallet; + #[runtime::pallet_index(153)] + pub type NonFungibles = nonfungibles::Pallet; + // Revive #[runtime::pallet_index(255)] pub type Revive = pallet_revive::Pallet; @@ -707,6 +710,7 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] + [nonfungibles, NonFungibles], [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 5bd7735b462db1527078e8d3889c203ee7f787cc Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:15:10 +0700 Subject: [PATCH 58/79] feat(nonfungibles): pop-api --- pop-api/src/v0/nonfungibles/mod.rs | 236 ++++++++++++++++++++++++--- pop-api/src/v0/nonfungibles/types.rs | 48 +++++- 2 files changed, 260 insertions(+), 24 deletions(-) diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 601dd99e..292285bc 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -13,7 +13,8 @@ pub use traits::*; pub use types::*; use crate::{ - primitives::{AccountId, Balance, TokenId}, + constants::NONFUNGIBLES, + primitives::{AccountId, BlockNumber}, ChainExtensionMethodApi, Result, StatusCode, }; @@ -22,32 +23,225 @@ pub mod events; pub mod traits; pub mod types; -/// Returns the total item supply for a specified collection. -/// -/// # Parameters -/// - `collection` - The collection. #[inline] -pub fn total_supply(collection: CollectionId) -> Result { +pub fn total_supply(collection: CollectionId) -> Result { build_read_state(TOTAL_SUPPLY) - .input::() - .output::, true>() + .input::() + .output::, true>() .handle_error_code::() - .call(&(token)) + .call(&(collection)) } -/// Returns the account balance for a specified `token` and `owner`. Returns `0` if -/// the account is non-existent. -/// -/// # Parameters -/// - `token` - The token. -/// - `owner` - The account whose balance is being queried. #[inline] -pub fn balance_of(token: TokenId, owner: AccountId) -> Result { +pub fn balance_of(collection: CollectionId, owner: AccountId) -> Result { build_read_state(BALANCE_OF) - .input::<(TokenId, AccountId)>() - .output::, true>() + .input::<(CollectionId, AccountId)>() + .output::, true>() .handle_error_code::() - .call(&(token, owner)) + .call(&(collection, owner)) +} + +#[inline] +pub fn allowance( + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, +) -> Result { + build_read_state(ALLOWANCE) + .input::<(CollectionId, AccountId, AccountId, Option)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, owner, operator, item)) +} + +#[inline] +pub fn transfer(collection: CollectionId, item: ItemId, to: AccountId) -> Result<()> { + build_dispatch(TRANSFER) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, to)) +} + +#[inline] +pub fn approve( + collection: CollectionId, + item: ItemId, + operator: AccountId, + approved: bool, +) -> Result<()> { + build_read_state(APPROVE) + .input::<(CollectionId, ItemId, AccountId, bool)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, operator, approved)) +} + +#[inline] +pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { + build_read_state(OWNER_OF) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn get_attribute( + collection: CollectionId, + item: AccountId, + namespace: AttributeNamespace, + key: Vec, +) -> Result> { + build_read_state(GET_ATTRIBUTE) + .input::<(CollectionId, AccountId, AttributeNamespace, Vec)>() + .output::>, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key)) +} + +#[inline] +pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { + build_read_state(CREATE) + .input::<(AccountId, CreateCollectionConfig)>() + .output::, true>() + .handle_error_code::() + .call(&(admin, config)) +} + +#[inline] +pub fn destroy(collection: CollectionId, witness: DestroyWitness) -> Result<()> { + build_read_state(DESTROY) + .input::<(CollectionId, DestroyWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, witness)) +} + +#[inline] +pub fn collection(collection: CollectionId) -> Result> { + build_read_state(COLLECTION) + .input::() + .output::>, true>() + .handle_error_code::() + .call(&(collection)) +} + +// TODO: ItemDetails. +#[inline] +pub fn item(collection: CollectionId, item: ItemId) -> Result { + build_read_state(ITEM) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn set_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<()> { + build_read_state(SET_ATTRIBUTE) + .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key, value)) +} + +#[inline] +pub fn clear_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<()> { + build_read_state(CLEAR_ATTRIBUTE) + .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key)) +} + +#[inline] +pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Result<()> { + build_read_state(SET_METADATA) + .input::<(CollectionId, ItemId, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, data)) +} + +#[inline] +pub fn clear_metadata(collection: CollectionId, item: ItemId) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn approve_item_attributes( + collection: CollectionId, + item: ItemId, + delegate: AccountId, +) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate)) +} + +#[inline] +pub fn cancel_item_attributes_approval( + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, +) -> Result<()> { + build_read_state(CLEAR_METADATA) + .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate, witness)) +} + +#[inline] +pub fn set_max_supply(collection: CollectionId, max_supply: u32) -> Result<()> { + build_read_state(SET_MAX_SUPPLY) + .input::<(CollectionId, u32)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, max_supply)) +} + +#[inline] +pub fn mint( + to: AccountId, + collection: CollectionId, + item: ItemId, + mint_price: Option, +) -> Result<()> { + build_read_state(MINT) + .input::<(AccountId, CollectionId, ItemId, Option)>() + .output::, true>() + .handle_error_code::() + .call(&(to, collection, item, mint_price)) +} + +#[inline] +pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { + build_read_state(BURN) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) } mod constants { @@ -86,7 +280,7 @@ mod constants { // Parameters: // - 'dispatchable': The index of the dispatchable function within the module. fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { - crate::v0::build_dispatch(FUNGIBLES, dispatchable) + crate::v0::build_dispatch(NONFUNGIBLES, dispatchable) } // Helper method to build a call to read state. @@ -94,5 +288,5 @@ fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { // Parameters: // - 'state_query': The index of the runtime state query. fn build_read_state(state_query: u8) -> ChainExtensionMethodApi { - crate::v0::build_read_state(FUNGIBLES, state_query) + crate::v0::build_read_state(NONFUNGIBLES, state_query) } diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs index 0cae037f..2357f1ab 100644 --- a/pop-api/src/v0/nonfungibles/types.rs +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -2,14 +2,34 @@ use super::*; use crate::primitives::AccountId; pub type ItemId = u32; -pub type Collection = u32; +pub type CollectionId = u32; +pub type Balance = u32; +/// Information about a collection. #[derive(Debug, PartialEq, Eq)] #[ink::scale_derive(Encode, Decode, TypeInfo)] -pub struct CreateCollectionConfig { +pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: Balance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CreateCollectionConfig { pub max_supply: Option, pub mint_type: MintType, - pub price: Option, + pub price: Option, pub start_block: Option, pub end_block: Option, } @@ -39,3 +59,25 @@ pub enum MintType { /// Only holders of items in specified collection could mint new items. HolderOf(CollectionId), } + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, +} + +/// Witness data for the destroy transactions. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct DestroyWitness { + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + #[codec(compact)] + pub item_configs: u32, + /// The total number of attributes for this collection. + #[codec(compact)] + pub attributes: u32, +} From 763e27d0ba39878e8a1e171ee65bac471f2c95c6 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:42:23 +0700 Subject: [PATCH 59/79] test(nonfungibles): pallet unit testing --- pallets/api/src/nonfungibles/mod.rs | 39 +++++++++--------- pallets/api/src/nonfungibles/tests.rs | 59 ++++++++++++++++----------- pallets/nfts/src/types.rs | 4 +- pop-api/src/v0/nonfungibles/mod.rs | 27 ++++-------- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 4939b7ee..1c437324 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -17,8 +17,9 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, - MintSettings, MintWitness, + ItemMetadata, ItemMetadataOf, MintSettings, MintWitness, }; + use sp_runtime::BoundedVec; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, @@ -62,12 +63,11 @@ pub mod pallet { /// Details of a collection. #[codec(index = 9)] Collection(CollectionIdOf), - /// Details of a collection item. - #[codec(index = 10)] - Item { collection: CollectionIdOf, item: ItemIdOf }, /// Next collection ID. - #[codec(index = 11)] + #[codec(index = 10)] NextCollectionId, + #[codec(index = 11)] + ItemMetadata { collection: CollectionIdOf, item: ItemIdOf }, } /// Results of state reads for the non-fungibles API. @@ -86,10 +86,10 @@ pub mod pallet { GetAttribute(Option>), /// Details of a collection. Collection(Option>), - /// Details of a collection item. - Item(Option>), /// Next collection ID. NextCollectionId(Option>), + /// Collection item metadata. + ItemMetadata(Option>), } impl ReadResult { @@ -101,10 +101,10 @@ pub mod pallet { TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), Collection(result) => result.encode(), - Item(result) => result.encode(), Allowance(result) => result.encode(), GetAttribute(result) => result.encode(), NextCollectionId(result) => result.encode(), + ItemMetadata(result) => result.encode(), } } } @@ -253,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(12)] + #[pallet::call_index(11)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -266,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(13)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -278,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(14)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -289,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(15)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -299,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(16)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -315,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(17)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -333,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(18)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -343,7 +343,7 @@ pub mod pallet { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } - #[pallet::call_index(19)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( origin: OriginFor, @@ -371,7 +371,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(20)] + #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( origin: OriginFor, @@ -429,8 +429,9 @@ pub mod pallet { ), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), - Item { collection, item } => - ReadResult::Item(pallet_nfts::Item::::get(collection, item)), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data), + ), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), ), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index ad575469..7556e0ec 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -82,18 +82,6 @@ mod encoding_read_result { ); } - #[test] - fn item() { - let mut item_details = Some(ItemDetails { - owner: account(ALICE), - approvals: BoundedBTreeMap::default(), - deposit: ItemDeposit { amount: 0, account: account(BOB) }, - }); - assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); - item_details = None; - assert_eq!(ReadResult::Item::(item_details.clone()).encode(), item_details.encode()); - } - #[test] fn next_collection_id_works() { let mut next_collection_id = Some(0); @@ -107,6 +95,14 @@ mod encoding_read_result { next_collection_id.encode() ); } + + #[test] + fn item_metadata_works() { + let mut data = Some(BoundedVec::truncate_from("some metadata".as_bytes().to_vec())); + assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); + data = None; + assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); + } } #[test] @@ -303,7 +299,33 @@ fn get_attribute_works() { #[test] fn set_metadata_works() { - unimplemented!() + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + let value = BoundedVec::truncate_from("some metadata".as_bytes().to_vec()); + assert_ok!(NonFungibles::set_metadata(signed(ALICE), collection, item, value.clone())); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + Some(value).encode() + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_ok!(NonFungibles::set_metadata( + signed(ALICE), + collection, + item, + BoundedVec::truncate_from("some metadata".as_bytes().to_vec()) + )); + assert_ok!(NonFungibles::clear_metadata(signed(ALICE), collection, item)); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + ReadResult::::ItemMetadata(None).encode() + ); + }); } #[test] @@ -454,17 +476,6 @@ fn collection_works() { }); } -#[test] -fn item_works() { - new_test_ext().execute_with(|| { - let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); - assert_eq!( - NonFungibles::read(Item { collection, item }).encode(), - pallet_nfts::Item::::get(&collection, &item).encode(), - ); - }); -} - #[test] fn balance_of_works() { new_test_ext().execute_with(|| { diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 061352c0..8a5153c3 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -184,11 +184,11 @@ pub struct ItemMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. - pub(super) deposit: Deposit, + pub deposit: Deposit, /// General information concerning this item. Limited in length by `StringLimit`. This will /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. - pub(super) data: BoundedVec, + pub data: BoundedVec, } /// Information about the tip. diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 292285bc..afed937b 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -128,16 +128,6 @@ pub fn collection(collection: CollectionId) -> Result> .call(&(collection)) } -// TODO: ItemDetails. -#[inline] -pub fn item(collection: CollectionId, item: ItemId) -> Result { - build_read_state(ITEM) - .input::<(CollectionId, ItemId)>() - .output::, true>() - .handle_error_code::() - .call(&(collection, item)) -} - #[inline] pub fn set_attribute( collection: CollectionId, @@ -260,15 +250,14 @@ mod constants { pub(super) const CREATE: u8 = 7; pub(super) const DESTROY: u8 = 8; pub(super) const COLLECTION: u8 = 9; - pub(super) const ITEM: u8 = 10; - pub(super) const NEXT_COLLECTION_ID: u8 = 11; - pub(super) const SET_ATTRIBUTE: u8 = 12; - pub(super) const CLEAR_ATTRIBUTE: u8 = 13; - pub(super) const SET_METADATA: u8 = 14; - pub(super) const CLEAR_METADATA: u8 = 15; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; - pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; - pub(super) const SET_MAX_SUPPLY: u8 = 18; + pub(super) const NEXT_COLLECTION_ID: u8 = 10; + pub(super) const SET_ATTRIBUTE: u8 = 11; + pub(super) const CLEAR_ATTRIBUTE: u8 = 12; + pub(super) const SET_METADATA: u8 = 13; + pub(super) const CLEAR_METADATA: u8 = 14; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 16; + pub(super) const SET_MAX_SUPPLY: u8 = 17; /// 4. PSP-34 Mintable & Burnable pub(super) const MINT: u8 = 19; From 887804b5e7a73705584ff2472e1ae91d9d3d5e03 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:44:42 +0700 Subject: [PATCH 60/79] fix: [nonfungibles, NonFungibles] --- runtime/devnet/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index f9fb341a..a50c28d0 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -710,7 +710,7 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] - [nonfungibles, NonFungibles], + [nonfungibles, NonFungibles] [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 4366c0fdc40d49242e70f8ef87e25c06b28bd7d0 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:17:09 +0700 Subject: [PATCH 61/79] fix: base call filter --- pop-api/integration-tests/src/nonfungibles/mod.rs | 0 runtime/devnet/src/config/api/mod.rs | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 pop-api/integration-tests/src/nonfungibles/mod.rs diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 9e92febf..7c983e52 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -231,8 +231,7 @@ impl Contains for Filter { TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | OwnerOf { .. } | GetAttribute { .. } | - Collection { .. } | Item { .. } | - NextCollectionId + Collection { .. } | NextCollectionId | ItemMetadata { .. } ) ) } From c8b37982456973c49775397c921f73ddf0a478f2 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:59:04 +0700 Subject: [PATCH 62/79] feat(nonfungibles): pop-api integration tests utils --- pop-api/integration-tests/Cargo.toml | 3 +- pop-api/integration-tests/src/lib.rs | 1 + .../integration-tests/src/nonfungibles/mod.rs | 12 + .../src/nonfungibles/utils.rs | 233 ++++++++++++++++++ pop-api/src/v0/nonfungibles/mod.rs | 8 +- 5 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 pop-api/integration-tests/src/nonfungibles/utils.rs diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 0e9caf1b..cd2e6678 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -23,6 +23,7 @@ pallet-timestamp = { version = "35.0.0", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", "messaging", + "nonfungibles", ] } pop-primitives = { path = "../../primitives", default-features = false } pop-runtime-devnet = { path = "../../runtime/devnet", default-features = false } @@ -36,7 +37,7 @@ staging-xcm = { version = "=14.1.0", default-features = false } xcm-executor = { version = "15.0.0", package = "staging-xcm-executor", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "frame-support/std", "frame-system/std", diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index 1ffdd849..8ed5c197 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -22,6 +22,7 @@ mod environment; mod fungibles; mod incentives; mod messaging; +mod nonfungibles; type Balance = u128; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index e69de29b..1361014b 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -0,0 +1,12 @@ +use pop_api::nonfungibles::{ + events::{Approval, AttributeSet, Transfer}, + types::*, +}; +use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*}; +use utils::*; + +use super::*; + +mod utils; + +const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs new file mode 100644 index 00000000..024709fa --- /dev/null +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -0,0 +1,233 @@ +use super::*; + +fn do_bare_call(function: &str, addr: &AccountId32, params: Vec) -> ExecReturnValue { + let function = function_selector(function); + let params = [function, params].concat(); + bare_call(addr.clone(), params, 0).expect("should work") +} + +// TODO - issue #263 - why result.data[1..] +pub(super) fn decoded(result: ExecReturnValue) -> Result { + ::decode(&mut &result.data[1..]).map_err(|_| result) +} + +pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { + let result = do_bare_call("total_supply", addr, collection.encode()); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn balance_of( + addr: &AccountId32, + collection: CollectionId, + owner: AccountId32, +) -> Result { + let params = [collection.encode(), owner.encode()].concat(); + let result = do_bare_call("balance_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn allowance( + addr: &AccountId32, + collection: CollectionId, + owner: AccountId32, + operator: AccountId32, + item: Option, +) -> Result { + let params = [collection.encode(), owner.encode(), operator.encode(), item.encode()].concat(); + let result = do_bare_call("allowance", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn transfer( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + to: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), to.encode()].concat(); + let result = do_bare_call("transfer", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + operator: AccountId32, + approved: bool, +) -> Result<(), Error> { + let params = + [collection.encode(), item.encode(), operator.encode(), approved.encode()].concat(); + let result = do_bare_call("approve", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn owner_of( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("owner_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn get_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result, Error> { + let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); + let result = do_bare_call("get_attribute", &addr, params); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn create( + addr: &AccountId32, + admin: AccountId32, + config: CreateCollectionConfig, +) -> Result<(), Error> { + let params = [admin.encode(), config.encode()].concat(); + let result = do_bare_call("create", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn destroy( + addr: &AccountId32, + collection: CollectionId, + witness: DestroyWitness, +) -> Result<(), Error> { + let params = [collection.encode(), witness.encode()].concat(); + let result = do_bare_call("destroy", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn collection( + addr: &AccountId32, + collection: CollectionId, +) -> Result, Error> { + let result = do_bare_call("collection", &addr, collection.encode()); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<(), Error> { + let params = + [collection.encode(), item.encode(), namespace.encode(), key.encode(), value.encode()] + .concat(); + let result = do_bare_call("set_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_attribute( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); + let result = do_bare_call("clear_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + data: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), data.encode()].concat(); + let result = do_bare_call("set_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("clear_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve_item_attributes( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode()].concat(); + let result = do_bare_call("approve_item_attributes", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn cancel_item_attributes_approval( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, + witness: CancelAttributesApprovalWitness, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode(), witness.encode()].concat(); + let result = do_bare_call("cancel_item_attributes_approval", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_max_supply( + addr: &AccountId32, + collection: CollectionId, + max_supply: u32, +) -> Result<(), Error> { + let params = [collection.encode(), max_supply.encode()].concat(); + let result = do_bare_call("set_max_supply", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +/// Get the last event from pallet contracts. +pub(super) fn last_contract_event() -> Vec { + let events = System::read_events_for_pallet::>(); + let contract_events = events + .iter() + .filter_map(|event| match event { + pallet_contracts::Event::::ContractEmitted { data, .. } => + Some(data.as_slice()), + _ => None, + }) + .collect::>(); + contract_events.last().unwrap().to_vec() +} + +/// Decodes a byte slice into an `AccountId` as defined in `primitives`. +/// +/// This is used to resolve type mismatches between the `AccountId` in the integration tests and the +/// contract environment. +pub fn account_id_from_slice(s: &[u8; 32]) -> pop_api::primitives::AccountId { + pop_api::primitives::AccountId::decode(&mut &s[..]).expect("Should be decoded to AccountId") +} diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index afed937b..8c7083ba 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -90,12 +90,12 @@ pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { #[inline] pub fn get_attribute( collection: CollectionId, - item: AccountId, + item: ItemId, namespace: AttributeNamespace, key: Vec, ) -> Result> { build_read_state(GET_ATTRIBUTE) - .input::<(CollectionId, AccountId, AttributeNamespace, Vec)>() + .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() .output::>, true>() .handle_error_code::() .call(&(collection, item, namespace, key)) @@ -256,8 +256,8 @@ mod constants { pub(super) const SET_METADATA: u8 = 13; pub(super) const CLEAR_METADATA: u8 = 14; pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; - pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 16; - pub(super) const SET_MAX_SUPPLY: u8 = 17; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; + pub(super) const SET_MAX_SUPPLY: u8 = 18; /// 4. PSP-34 Mintable & Burnable pub(super) const MINT: u8 = 19; From 4d5451ebb0f16d6d480d2e606087fddbcb1604e5 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 22 Oct 2024 19:00:03 +0700 Subject: [PATCH 63/79] fix: benchmark listing --- runtime/devnet/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index a50c28d0..e0d8618d 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -710,7 +710,6 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] - [nonfungibles, NonFungibles] [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 4441ad4231bd0d99fbeea66b21f8115fa202dfae Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:32:19 +0700 Subject: [PATCH 64/79] fix: test --- runtime/devnet/src/config/api/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 7c983e52..0a32243c 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -400,14 +400,13 @@ mod tests { operator: ACCOUNT, }), NonFungibles(OwnerOf { collection: 1, item: 1 }), - // NonFungibles(GetAttribute { - // collection: 0, - // item: Some(0), - // namespace: pallet_nfts::AttributeNamespace::CollectionOwner, - // key: bounded_vec![], - // }), + NonFungibles(GetAttribute { + collection: 0, + item: Some(0), + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: bounded_vec![], + }), NonFungibles(Collection(1)), - NonFungibles(Item { collection: 1, item: 1 }), NonFungibles(NextCollectionId), ] .iter() From ea91eb6a865d01ecf9a279f617abfd69a2c43ce6 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:26:48 +0700 Subject: [PATCH 65/79] feat: add integration test contract --- pallets/api/src/nonfungibles/mod.rs | 22 +- pallets/api/src/nonfungibles/tests.rs | 9 +- .../contracts/nonfungibles/Cargo.toml | 20 ++ .../contracts/nonfungibles/lib.rs | 254 ++++++++++++++++++ pop-api/src/v0/nonfungibles/events.rs | 25 +- pop-api/src/v0/nonfungibles/mod.rs | 36 ++- 6 files changed, 329 insertions(+), 37 deletions(-) create mode 100644 pop-api/integration-tests/contracts/nonfungibles/Cargo.toml create mode 100644 pop-api/integration-tests/contracts/nonfungibles/lib.rs diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 1c437324..9161ff6c 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -75,7 +75,7 @@ pub mod pallet { #[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { /// Total item supply of a collection. - TotalSupply(u32), + TotalSupply(u128), /// Account balance for a specified collection. BalanceOf(u32), /// Allowance for an operator approved by an owner, for a specified collection or item. @@ -253,7 +253,7 @@ pub mod pallet { NftsOf::::destroy(origin, collection, witness) } - #[pallet::call_index(11)] + #[pallet::call_index(12)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -266,7 +266,7 @@ pub mod pallet { NftsOf::::set_attribute(origin, collection, item, namespace, key, value) } - #[pallet::call_index(12)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -278,7 +278,7 @@ pub mod pallet { NftsOf::::clear_attribute(origin, collection, item, namespace, key) } - #[pallet::call_index(13)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -289,7 +289,7 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } - #[pallet::call_index(14)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -299,7 +299,7 @@ pub mod pallet { NftsOf::::clear_metadata(origin, collection, item) } - #[pallet::call_index(15)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -315,7 +315,7 @@ pub mod pallet { ) } - #[pallet::call_index(16)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -333,7 +333,7 @@ pub mod pallet { ) } - #[pallet::call_index(17)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -343,7 +343,7 @@ pub mod pallet { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } - #[pallet::call_index(18)] + #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( origin: OriginFor, @@ -371,7 +371,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(19)] + #[pallet::call_index(20)] #[pallet::weight(NftsWeightInfoOf::::burn())] pub fn burn( origin: OriginFor, @@ -414,7 +414,7 @@ pub mod pallet { use Read::*; match value { TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default(), + NftsOf::::collection_items(collection).unwrap_or_default() as u128, ), BalanceOf { collection, owner } => ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 7556e0ec..dbeaaef4 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -23,7 +23,7 @@ mod encoding_read_result { #[test] fn total_supply() { - let total_supply: u32 = 1_000_000; + let total_supply: u128 = 1_000_000; assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); } @@ -456,10 +456,13 @@ fn total_supply_works() { let collection = nfts::create_collection(owner); (0..10).into_iter().for_each(|i| { assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); - assert_eq!(NonFungibles::read(TotalSupply(collection)).encode(), (i + 1).encode()); assert_eq!( NonFungibles::read(TotalSupply(collection)).encode(), - Nfts::collection_items(collection).unwrap_or_default().encode() + ((i + 1) as u128).encode() + ); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() ); }); }); diff --git a/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml new file mode 100644 index 00000000..47b105fb --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "nonfungibles" +version = "0.1.0" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +pop-api = { path = "../../../../pop-api", default-features = false, features = [ "nonfungibles" ] } + +[lib] +path = "lib.rs" + +[features] +default = [ "std" ] +e2e-tests = [ ] +ink-as-dependency = [ ] +std = [ + "ink/std", + "pop-api/std", +] diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs new file mode 100644 index 00000000..cfbd2bf0 --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -0,0 +1,254 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +/// 1. PSP-34 +/// 2. PSP-34 Metadata +/// 3. Management +/// 4. PSP-34 Mintable & Burnable +use ink::prelude::vec::Vec; +use pop_api::{ + nonfungibles::{ + self as api, + events::{Approval, AttributeSet, Transfer}, + AttributeNamespace, CancelAttributesApprovalWitness, CollectionDetails, CollectionId, + CreateCollectionConfig, DestroyWitness, ItemId, + }, + StatusCode, +}; + +pub type Result = core::result::Result; + +#[ink::contract] +mod nonfungibles { + use super::*; + + #[ink(storage)] + #[derive(Default)] + pub struct NonFungibles; + + impl NonFungibles { + #[ink(constructor, payable)] + pub fn new() -> Self { + ink::env::debug_println!("PopApiNonFungiblesExample::new"); + Default::default() + } + + /// 1. PSP-34 Interface: + /// - total_supply + /// - balance_of + /// - allowance + /// - transfer + /// - approve + /// - owner_of + + #[ink(message)] + pub fn total_supply(&self, collection: CollectionId) -> Result { + api::total_supply(collection) + } + + #[ink(message)] + pub fn balance_of(&self, collection: CollectionId, owner: AccountId) -> Result { + api::balance_of(collection, owner) + } + + #[ink(message)] + pub fn allowance( + &self, + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, + ) -> Result { + api::allowance(collection, owner, operator, item) + } + + #[ink(message)] + pub fn transfer( + &mut self, + collection: CollectionId, + item: ItemId, + to: AccountId, + ) -> Result<()> { + api::transfer(collection, item, to)?; + self.env().emit_event(Transfer { + from: Some(self.env().account_id()), + to: Some(to), + item, + }); + Ok(()) + } + + #[ink(message)] + pub fn approve( + &mut self, + collection: CollectionId, + item: Option, + operator: AccountId, + approved: bool, + ) -> Result<()> { + api::approve(collection, item, operator, approved)?; + self.env().emit_event(Approval { + owner: self.env().account_id(), + operator, + item, + approved, + }); + Ok(()) + } + + #[ink(message)] + pub fn owner_of( + &self, + collection: CollectionId, + item: ItemId, + ) -> Result> { + api::owner_of(collection, item) + } + + /// 2. PSP-34 Metadata Interface: + /// - get_attribute + + #[ink(message)] + pub fn get_attribute( + &self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result>> { + api::get_attribute(collection, item, namespace, key) + } + + /// 3. Asset Management: + /// - create + /// - destroy + /// - collection + /// - set_attribute + /// - clear_attribute + /// - set_metadata + /// - clear_metadata + /// - approve_item_attributes + /// - cancel_item_attributes_approval + /// - set_max_supply + /// - item_metadata + + #[ink(message)] + pub fn create(&mut self, admin: AccountId, config: CreateCollectionConfig) -> Result<()> { + api::create(admin, config) + } + + #[ink(message)] + pub fn destroy(&mut self, collection: CollectionId, witness: DestroyWitness) -> Result<()> { + api::destroy(collection, witness) + } + + #[ink(message)] + pub fn collection(&self, collection: CollectionId) -> Result> { + api::collection(collection) + } + + #[ink(message)] + pub fn set_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, + ) -> Result<()> { + api::set_attribute(collection, item, namespace, key.clone(), value.clone())?; + self.env().emit_event(AttributeSet { item, key, data: value }); + Ok(()) + } + + #[ink(message)] + pub fn clear_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result<()> { + api::clear_attribute(collection, item, namespace, key) + } + + #[ink(message)] + pub fn set_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + data: Vec, + ) -> Result<()> { + api::set_metadata(collection, item, data) + } + + #[ink(message)] + pub fn clear_metadata(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::clear_metadata(collection, item) + } + + #[ink(message)] + pub fn approve_item_attributes( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + ) -> Result<()> { + api::approve_item_attributes(collection, item, delegate) + } + + #[ink(message)] + pub fn cancel_item_attributes_approval( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, + ) -> Result<()> { + api::cancel_item_attributes_approval(collection, item, delegate, witness) + } + + #[ink(message)] + pub fn set_max_supply(&mut self, collection: CollectionId, max_supply: u32) -> Result<()> { + api::set_max_supply(collection, max_supply) + } + + #[ink(message)] + pub fn item_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + ) -> Result>> { + api::item_metadata(collection, item) + } + + /// 4. PSP-22 Mintable & Burnable Interface: + /// - mint + /// - burn + + #[ink(message)] + pub fn mint( + &mut self, + to: AccountId, + collection: CollectionId, + item: ItemId, + mint_price: Option, + ) -> Result<()> { + api::mint(to, collection, item, mint_price) + } + + #[ink(message)] + pub fn burn(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::burn(collection, item) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + PopApiNonFungiblesExample::new(); + } + } +} diff --git a/pop-api/src/v0/nonfungibles/events.rs b/pop-api/src/v0/nonfungibles/events.rs index e11b2f31..3c409868 100644 --- a/pop-api/src/v0/nonfungibles/events.rs +++ b/pop-api/src/v0/nonfungibles/events.rs @@ -13,33 +13,38 @@ use super::*; #[ink::event] pub struct Transfer { /// The source of the transfer. `None` when minting. - from: Option, + #[ink(topic)] + pub from: Option, /// The recipient of the transfer. `None` when burning. - to: Option, + #[ink(topic)] + pub to: Option, /// The item transferred (or minted/burned). - item: ItemId, + pub item: ItemId, } /// Event emitted when a token approve occurs. #[ink::event] pub struct Approval { /// The owner providing the allowance. - owner: AccountId, + #[ink(topic)] + pub owner: AccountId, /// The beneficiary of the allowance. - operator: AccountId, + #[ink(topic)] + pub operator: AccountId, /// The item which is (dis)approved. `None` for all owner's items. - item: Option, + pub item: Option, /// Whether allowance is set or removed. - approved: bool, + pub approved: bool, } /// Event emitted when an attribute is set for a token. #[ink::event] pub struct AttributeSet { /// The item which attribute is set. - item: ItemId, + #[ink(topic)] + pub item: ItemId, /// The key for the attribute. - key: Vec, + pub key: Vec, /// The data for the attribute. - data: Vec, + pub data: Vec, } diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 8c7083ba..af50b766 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -24,10 +24,10 @@ pub mod traits; pub mod types; #[inline] -pub fn total_supply(collection: CollectionId) -> Result { +pub fn total_supply(collection: CollectionId) -> Result { build_read_state(TOTAL_SUPPLY) .input::() - .output::, true>() + .output::, true>() .handle_error_code::() .call(&(collection)) } @@ -67,22 +67,22 @@ pub fn transfer(collection: CollectionId, item: ItemId, to: AccountId) -> Result #[inline] pub fn approve( collection: CollectionId, - item: ItemId, + item: Option, operator: AccountId, approved: bool, ) -> Result<()> { build_read_state(APPROVE) - .input::<(CollectionId, ItemId, AccountId, bool)>() + .input::<(CollectionId, Option, AccountId, bool)>() .output::, true>() .handle_error_code::() .call(&(collection, item, operator, approved)) } #[inline] -pub fn owner_of(collection: CollectionId, item: ItemId) -> Result { +pub fn owner_of(collection: CollectionId, item: ItemId) -> Result> { build_read_state(OWNER_OF) .input::<(CollectionId, ItemId)>() - .output::, true>() + .output::>, true>() .handle_error_code::() .call(&(collection, item)) } @@ -93,10 +93,10 @@ pub fn get_attribute( item: ItemId, namespace: AttributeNamespace, key: Vec, -) -> Result> { +) -> Result>> { build_read_state(GET_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() - .output::>, true>() + .output::>>, true>() .handle_error_code::() .call(&(collection, item, namespace, key)) } @@ -128,6 +128,15 @@ pub fn collection(collection: CollectionId) -> Result> .call(&(collection)) } +#[inline] +pub fn item_metadata(collection: CollectionId, item: ItemId) -> Result>> { + build_read_state(BURN) + .input::<(CollectionId, ItemId)>() + .output::>>, true>() + .handle_error_code::() + .call(&(collection, item)) +} + #[inline] pub fn set_attribute( collection: CollectionId, @@ -251,11 +260,12 @@ mod constants { pub(super) const DESTROY: u8 = 8; pub(super) const COLLECTION: u8 = 9; pub(super) const NEXT_COLLECTION_ID: u8 = 10; - pub(super) const SET_ATTRIBUTE: u8 = 11; - pub(super) const CLEAR_ATTRIBUTE: u8 = 12; - pub(super) const SET_METADATA: u8 = 13; - pub(super) const CLEAR_METADATA: u8 = 14; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 15; + pub(super) const ITEM_METADATA: u8 = 11; + pub(super) const SET_ATTRIBUTE: u8 = 12; + pub(super) const CLEAR_ATTRIBUTE: u8 = 13; + pub(super) const SET_METADATA: u8 = 14; + pub(super) const CLEAR_METADATA: u8 = 15; + pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; pub(super) const SET_MAX_SUPPLY: u8 = 18; From 5d432945b2aab6bf72356a2d47dd9fdc818aa6be Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:48:49 +0700 Subject: [PATCH 66/79] test(nonfungibles): integration tests skeleton --- pop-api/integration-tests/Cargo.toml | 2 + .../contracts/nonfungibles/lib.rs | 20 ++-- pop-api/integration-tests/src/lib.rs | 2 +- .../integration-tests/src/nonfungibles/mod.rs | 62 ++++++++++- .../src/nonfungibles/utils.rs | 100 +++++++++++++++++- pop-api/src/v0/nonfungibles/errors.rs | 2 + pop-api/src/v0/nonfungibles/mod.rs | 8 ++ pop-api/src/v0/nonfungibles/types.rs | 2 +- 8 files changed, 178 insertions(+), 20 deletions(-) diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index cd2e6678..0f793136 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -20,6 +20,7 @@ pallet-incentives = { path = "../../pallets/incentives", default-features = fals pallet-ismp = { git = "https://github.com/r0gue-io/ismp", branch = "polkadot-v1.14.0", default-features = false } pallet-revive = { path = "../../pallets/revive", default-features = false } pallet-timestamp = { version = "35.0.0", default-features = false } +pallet-nfts = { path = "../../pallets/nfts", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", "messaging", @@ -46,6 +47,7 @@ std = [ "pallet-incentives/std", "pallet-revive/std", "pallet-timestamp/std", + "pallet-nfts/std", "pop-api/std", "pop-primitives/std", "pop-runtime-devnet/std", diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs index cfbd2bf0..5cdc85f6 100644 --- a/pop-api/integration-tests/contracts/nonfungibles/lib.rs +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -132,8 +132,14 @@ mod nonfungibles { /// - item_metadata #[ink(message)] - pub fn create(&mut self, admin: AccountId, config: CreateCollectionConfig) -> Result<()> { - api::create(admin, config) + pub fn create( + &mut self, + admin: AccountId, + config: CreateCollectionConfig, + ) -> Result { + let next_collection_id = api::next_collection_id(); + api::create(admin, config)?; + next_collection_id } #[ink(message)] @@ -241,14 +247,4 @@ mod nonfungibles { api::burn(collection, item) } } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - PopApiNonFungiblesExample::new(); - } - } } diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index 8ed5c197..6205850e 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -9,7 +9,7 @@ use frame_support::{ }; use pallet_revive::{AddressMapper, Code, CollectEvents, ExecReturnValue}; use pop_runtime_devnet::{ - config::ismp::Router, Assets, Messaging, Revive, Runtime, RuntimeOrigin, System, UNIT, + config::ismp::Router, Assets, Messaging, Nfts, Revive, Runtime, RuntimeOrigin, System, UNIT, }; use scale::{Decode, Encode}; use sp_runtime::{ diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 1361014b..e91ce1e6 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -1,6 +1,10 @@ -use pop_api::nonfungibles::{ - events::{Approval, AttributeSet, Transfer}, - types::*, +use pallet_nfts::CollectionConfig; +use pop_api::{ + nonfungibles::{ + events::{Approval, AttributeSet, Transfer}, + types::*, + }, + primitives::BlockNumber, }; use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*}; use utils::*; @@ -9,4 +13,54 @@ use super::*; mod utils; -const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; +const COLLECTION_ID: CollectionId = 0; +const ITEM_ID: ItemId = 1; +const CONTRACT: &str = "contracts/nonfungibles/target/ink/nonfungibles.wasm"; + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(None)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&addr, &ALICE, &ALICE, COLLECTION_ID); + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(Some(100))); + }); +} + +// Testing a contract that creates a token in the constructor. +#[test] +fn instantiate_and_create_fungible_works() { + new_test_ext().execute_with(|| { + // let contract = + // "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; + // // Token already exists. + // assets::create(&ALICE, 0, 1); + // assert_eq!( + // instantiate_and_create_fungible(contract, 0, 1), + // Err(Module { index: 52, error: [5, 0] }) + // ); + // // Successfully create a token when instantiating the contract. + // let result_with_address = instantiate_and_create_fungible(contract, TOKEN_ID, 1); + // let instantiator = result_with_address.clone().ok(); + // assert_ok!(result_with_address); + // assert_eq!(&Assets::owner(TOKEN_ID), &instantiator); + // assert!(Assets::asset_exists(TOKEN_ID)); + // // Successfully emit event. + // let instantiator = account_id_from_slice(instantiator.unwrap().as_ref()); + // let expected = + // Created { id: TOKEN_ID, creator: instantiator.clone(), admin: instantiator }.encode(); + // assert_eq!(last_contract_event(), expected.as_slice()); + }); +} diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 024709fa..d52e3067 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -11,9 +11,12 @@ pub(super) fn decoded(result: ExecReturnValue) -> Result::decode(&mut &result.data[1..]).map_err(|_| result) } -pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { +pub(super) fn total_supply( + addr: &AccountId32, + collection: CollectionId, +) -> Result, Error> { let result = do_bare_call("total_supply", addr, collection.encode()); - decoded::>(result.clone()) + decoded::, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } @@ -210,6 +213,99 @@ pub(super) fn set_max_supply( .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } +pub(super) fn item_metadata( + addr: &AccountId32, + collection: CollectionId, + item: ItemId, +) -> Result>, Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("item_metadata", &addr, params); + decoded::>, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) mod nfts { + use super::*; + + pub(crate) fn create_collection_and_mint_to( + owner: &AccountId32, + admin: &AccountId32, + to: &AccountId32, + item: ItemId, + ) -> (CollectionId, ItemId) { + let collection = create_collection(owner, admin); + mint(owner, to, collection, item); + (collection, item) + } + + pub(crate) fn create_collection(owner: &AccountId32, admin: &AccountId32) -> CollectionId { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(owner.clone()), + owner.clone().into(), + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } + + pub(crate) fn mint( + owner: &AccountId32, + to: &AccountId32, + collection: CollectionId, + item: ItemId, + ) -> ItemId { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner.clone()), + collection, + item, + owner.clone().into(), + None + )); + item + } + + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig { + CollectionConfig { + settings: pallet_nfts::CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: pallet_nfts::MintSettings::default(), + } + } +} + +pub(super) fn instantiate_and_create_nonfungible( + contract: &str, + admin: AccountId32, + config: CreateCollectionConfig, +) -> Result { + let function = function_selector("new"); + let input = [function, admin.encode(), config.encode()].concat(); + let wasm_binary = std::fs::read(contract).expect("could not read .wasm file"); + let result = Contracts::bare_instantiate( + ALICE, + INIT_VALUE, + GAS_LIMIT, + None, + Code::Upload(wasm_binary), + input, + vec![], + DEBUG_OUTPUT, + CollectEvents::Skip, + ) + .result + .expect("should work"); + let address = result.account_id; + let result = result.result; + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|_| address) +} + /// Get the last event from pallet contracts. pub(super) fn last_contract_event() -> Vec { let events = System::read_events_for_pallet::>(); diff --git a/pop-api/src/v0/nonfungibles/errors.rs b/pop-api/src/v0/nonfungibles/errors.rs index f7e2324c..c955bf60 100644 --- a/pop-api/src/v0/nonfungibles/errors.rs +++ b/pop-api/src/v0/nonfungibles/errors.rs @@ -1,6 +1,8 @@ //! A set of errors for use in smart contracts that interact with the nonfungibles api. This //! includes errors compliant to standards. +use ink::prelude::string::{String, ToString}; + use super::*; /// The PSP34 error. diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index af50b766..7052e5f5 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -243,6 +243,14 @@ pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { .call(&(collection, item)) } +#[inline] +pub fn next_collection_id() -> Result { + build_read_state(NEXT_COLLECTION_ID) + .output::, true>() + .handle_error_code::() + .call(&()) +} + mod constants { /// 1. PSP-34 pub(super) const TOTAL_SUPPLY: u8 = 0; diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs index 2357f1ab..3b0174b9 100644 --- a/pop-api/src/v0/nonfungibles/types.rs +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -3,7 +3,7 @@ use crate::primitives::AccountId; pub type ItemId = u32; pub type CollectionId = u32; -pub type Balance = u32; +pub(super) type Balance = u32; /// Information about a collection. #[derive(Debug, PartialEq, Eq)] From a6da1ffb7acf644f25729d7f406c7b158df5beff Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:02:32 +0700 Subject: [PATCH 67/79] fix(nonfungibles): integration tests --- pop-api/integration-tests/src/nonfungibles/mod.rs | 10 +++++----- pop-api/integration-tests/src/nonfungibles/utils.rs | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index e91ce1e6..ced0add8 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -25,17 +25,17 @@ fn total_supply_works() { // No tokens in circulation. assert_eq!( total_supply(&addr, COLLECTION_ID), - Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) ); - assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(None)); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); // Tokens in circulation. - nfts::create_collection_and_mint_to(&addr, &ALICE, &ALICE, COLLECTION_ID); + nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); assert_eq!( total_supply(&addr, COLLECTION_ID), - Ok(Nfts::collection_items(COLLECTION_ID).map(|value| value as u128)) + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) ); - assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(Some(100))); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index d52e3067..035180d9 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -11,12 +11,9 @@ pub(super) fn decoded(result: ExecReturnValue) -> Result::decode(&mut &result.data[1..]).map_err(|_| result) } -pub(super) fn total_supply( - addr: &AccountId32, - collection: CollectionId, -) -> Result, Error> { +pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { let result = do_bare_call("total_supply", addr, collection.encode()); - decoded::, Error>>(result.clone()) + decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } From 34f84e2bf409e31d10dd183ef7fd2a79259d2ee6 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:09:44 +0700 Subject: [PATCH 68/79] test(nonfungibles): add balance_of_works --- .../integration-tests/src/nonfungibles/mod.rs | 22 +++++++++++++++++++ .../src/nonfungibles/utils.rs | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index ced0add8..73765e52 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -39,6 +39,28 @@ fn total_supply_works() { }); } +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); + }); +} + // Testing a contract that creates a token in the constructor. #[test] fn instantiate_and_create_fungible_works() { diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 035180d9..c306c191 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -265,6 +265,10 @@ pub(super) mod nfts { item } + pub(crate) fn balance_of(collection: CollectionId, owner: AccountId32) -> u32 { + pallet_nfts::AccountBalance::::get(collection, owner) + } + pub(super) fn collection_config_with_all_settings_enabled( ) -> CollectionConfig { CollectionConfig { From f976c20a9b0f4ee0a2b65a8b3a7feb31df9f50b1 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:35:46 +0700 Subject: [PATCH 69/79] test(nonfungibles): add more integration tests --- .../integration-tests/src/nonfungibles/mod.rs | 128 +++++++++++++++--- .../src/nonfungibles/utils.rs | 28 +++- pop-api/src/v0/nonfungibles/mod.rs | 24 ++-- 3 files changed, 142 insertions(+), 38 deletions(-) diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 73765e52..8f26a6ab 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -1,3 +1,4 @@ +use frame_support::BoundedVec; use pallet_nfts::CollectionConfig; use pop_api::{ nonfungibles::{ @@ -61,28 +62,113 @@ fn balance_of_works() { }); } -// Testing a contract that creates a token in the constructor. #[test] -fn instantiate_and_create_fungible_works() { +fn allowance_works() { new_test_ext().execute_with(|| { - // let contract = - // "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; - // // Token already exists. - // assets::create(&ALICE, 0, 1); - // assert_eq!( - // instantiate_and_create_fungible(contract, 0, 1), - // Err(Module { index: 52, error: [5, 0] }) - // ); - // // Successfully create a token when instantiating the contract. - // let result_with_address = instantiate_and_create_fungible(contract, TOKEN_ID, 1); - // let instantiator = result_with_address.clone().ok(); - // assert_ok!(result_with_address); - // assert_eq!(&Assets::owner(TOKEN_ID), &instantiator); - // assert!(Assets::asset_exists(TOKEN_ID)); - // // Successfully emit event. - // let instantiator = account_id_from_slice(instantiator.unwrap().as_ref()); - // let expected = - // Created { id: TOKEN_ID, creator: instantiator.clone(), admin: instantiator }.encode(); - // assert_eq!(last_contract_event(), expected.as_slice()); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + // No tokens in circulation. + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), + Ok(!Nfts::check_allowance(&COLLECTION_ID, &None, &addr, &ALICE).is_err()), + ); + assert_eq!(allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), Ok(false)); + + let (collection, item) = + nfts::create_collection_mint_and_approve(&addr, &addr, ITEM_ID, &addr, &ALICE); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), + Ok(Nfts::check_allowance(&COLLECTION_ID, &Some(item), &addr.clone(), &ALICE).is_ok()), + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), + Ok(true) + ); + }); +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let before_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_ok!(transfer(&addr, collection, item, ALICE)); + let after_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_eq!(after_transfer_balance - before_transfer_balance, 1); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(approve(&addr, collection, Some(item), ALICE, true)); + assert!(Nfts::check_allowance(&collection, &Some(item), &addr.clone(), &ALICE).is_ok(),); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(ALICE), collection, item, BOB.into())); + }); +} + +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + }); +} + +// TODO +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(addr.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + get_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + ), + Ok("some value".as_bytes().to_vec()) + ); + }); +} + +// TODO +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + }); +} + +// TODO +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index c306c191..ba174c39 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -56,7 +56,7 @@ pub(super) fn transfer( pub(super) fn approve( addr: &AccountId32, collection: CollectionId, - item: ItemId, + item: Option, operator: AccountId32, approved: bool, ) -> Result<(), Error> { @@ -231,7 +231,25 @@ pub(super) mod nfts { item: ItemId, ) -> (CollectionId, ItemId) { let collection = create_collection(owner, admin); - mint(owner, to, collection, item); + mint(collection, item, owner, to); + (collection, item) + } + + pub(crate) fn create_collection_mint_and_approve( + owner: &AccountId32, + admin: &AccountId32, + item: ItemId, + to: &AccountId32, + operator: &AccountId32, + ) -> (u32, u32) { + let (collection, item) = create_collection_and_mint_to(&owner.clone(), admin, to, item); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(to.clone()), + collection, + Some(item), + operator.clone().into(), + None + )); (collection, item) } @@ -250,16 +268,16 @@ pub(super) mod nfts { } pub(crate) fn mint( - owner: &AccountId32, - to: &AccountId32, collection: CollectionId, item: ItemId, + owner: &AccountId32, + to: &AccountId32, ) -> ItemId { assert_ok!(Nfts::mint( RuntimeOrigin::signed(owner.clone()), collection, item, - owner.clone().into(), + to.clone().into(), None )); item diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 7052e5f5..7018685c 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -71,7 +71,7 @@ pub fn approve( operator: AccountId, approved: bool, ) -> Result<()> { - build_read_state(APPROVE) + build_dispatch(APPROVE) .input::<(CollectionId, Option, AccountId, bool)>() .output::, true>() .handle_error_code::() @@ -103,7 +103,7 @@ pub fn get_attribute( #[inline] pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { - build_read_state(CREATE) + build_dispatch(CREATE) .input::<(AccountId, CreateCollectionConfig)>() .output::, true>() .handle_error_code::() @@ -112,7 +112,7 @@ pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { #[inline] pub fn destroy(collection: CollectionId, witness: DestroyWitness) -> Result<()> { - build_read_state(DESTROY) + build_dispatch(DESTROY) .input::<(CollectionId, DestroyWitness)>() .output::, true>() .handle_error_code::() @@ -145,7 +145,7 @@ pub fn set_attribute( key: Vec, value: Vec, ) -> Result<()> { - build_read_state(SET_ATTRIBUTE) + build_dispatch(SET_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() .output::, true>() .handle_error_code::() @@ -159,7 +159,7 @@ pub fn clear_attribute( namespace: AttributeNamespace, key: Vec, ) -> Result<()> { - build_read_state(CLEAR_ATTRIBUTE) + build_dispatch(CLEAR_ATTRIBUTE) .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() .output::, true>() .handle_error_code::() @@ -168,7 +168,7 @@ pub fn clear_attribute( #[inline] pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Result<()> { - build_read_state(SET_METADATA) + build_dispatch(SET_METADATA) .input::<(CollectionId, ItemId, Vec)>() .output::, true>() .handle_error_code::() @@ -177,7 +177,7 @@ pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Re #[inline] pub fn clear_metadata(collection: CollectionId, item: ItemId) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId)>() .output::, true>() .handle_error_code::() @@ -190,7 +190,7 @@ pub fn approve_item_attributes( item: ItemId, delegate: AccountId, ) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId, AccountId)>() .output::, true>() .handle_error_code::() @@ -204,7 +204,7 @@ pub fn cancel_item_attributes_approval( delegate: AccountId, witness: CancelAttributesApprovalWitness, ) -> Result<()> { - build_read_state(CLEAR_METADATA) + build_dispatch(CLEAR_METADATA) .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() .output::, true>() .handle_error_code::() @@ -213,7 +213,7 @@ pub fn cancel_item_attributes_approval( #[inline] pub fn set_max_supply(collection: CollectionId, max_supply: u32) -> Result<()> { - build_read_state(SET_MAX_SUPPLY) + build_dispatch(SET_MAX_SUPPLY) .input::<(CollectionId, u32)>() .output::, true>() .handle_error_code::() @@ -227,7 +227,7 @@ pub fn mint( item: ItemId, mint_price: Option, ) -> Result<()> { - build_read_state(MINT) + build_dispatch(MINT) .input::<(AccountId, CollectionId, ItemId, Option)>() .output::, true>() .handle_error_code::() @@ -236,7 +236,7 @@ pub fn mint( #[inline] pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { - build_read_state(BURN) + build_dispatch(BURN) .input::<(CollectionId, ItemId)>() .output::, true>() .handle_error_code::() From 2abb4ff13a980d99d3e4b5e867110e9dea4b9ead Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:30:38 +0700 Subject: [PATCH 70/79] test(nonfungibles): add more tests --- pallets/api/src/nonfungibles/mod.rs | 10 +- pallets/api/src/nonfungibles/tests.rs | 51 +++- pop-api/integration-tests/src/lib.rs | 2 +- .../integration-tests/src/nonfungibles/mod.rs | 237 +++++++++++++++++- .../src/nonfungibles/utils.rs | 31 ++- pop-api/src/v0/nonfungibles/mod.rs | 14 +- 6 files changed, 305 insertions(+), 40 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 9161ff6c..0c7985aa 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -17,14 +17,14 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, - ItemMetadata, ItemMetadataOf, MintSettings, MintWitness, + ItemMetadataOf, MintSettings, MintWitness, }; use sp_runtime::BoundedVec; use sp_std::vec::Vec; use types::{ AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - CreateCollectionConfigFor, ItemDetailsFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, - NftsOf, NftsWeightInfoOf, + CreateCollectionConfigFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, NftsOf, + NftsWeightInfoOf, }; use super::*; @@ -56,7 +56,7 @@ pub mod pallet { #[codec(index = 6)] GetAttribute { collection: CollectionIdOf, - item: Option>, + item: ItemIdOf, namespace: AttributeNamespaceOf, key: BoundedVec, }, @@ -424,7 +424,7 @@ pub mod pallet { OwnerOf { collection, item } => ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( - pallet_nfts::Attribute::::get((collection, item, namespace, key)) + pallet_nfts::Attribute::::get((collection, Some(item), namespace, key)) .map(|attribute| attribute.0), ), Collection(collection) => diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index dbeaaef4..c119188a 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -2,15 +2,15 @@ use codec::Encode; use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ - AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, ItemDeposit, + AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, DestroyWitness, ItemDetails, MintSettings, }; -use sp_runtime::{BoundedBTreeMap, BoundedVec, DispatchError::BadOrigin}; +use sp_runtime::{BoundedVec, DispatchError::BadOrigin}; use super::types::{CollectionIdOf, ItemIdOf}; use crate::{ mock::*, - nonfungibles::{Event, Read::*, ReadResult}, + nonfungibles::{CreateCollectionConfig, Event, Read::*, ReadResult}, Read, }; @@ -259,7 +259,6 @@ fn owner_of_works() { fn get_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); - assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); let mut result: Option::ValueLimit>> = None; @@ -267,7 +266,7 @@ fn get_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute.clone() }) @@ -287,7 +286,7 @@ fn get_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute }) @@ -333,7 +332,7 @@ fn clear_attribute_works() { new_test_ext().execute_with(|| { let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); - let mut attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let result: Option::ValueLimit>> = None; assert_ok!(Nfts::set_attribute( signed(ALICE), @@ -354,7 +353,7 @@ fn clear_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: attribute }) @@ -386,7 +385,7 @@ fn approve_item_attribute_works() { assert_eq!( NonFungibles::read(GetAttribute { collection, - item: Some(item), + item, namespace: pallet_nfts::AttributeNamespace::Account(account(BOB)), key: attribute }) @@ -403,7 +402,6 @@ fn cancel_item_attribute_approval_works() { assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - let result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); assert_ok!(Nfts::set_attribute( @@ -468,6 +466,39 @@ fn total_supply_works() { }); } +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let next_collection_id = pallet_nfts::NextCollectionId::::get().unwrap_or_default(); + assert_ok!(NonFungibles::create( + signed(owner), + account(owner), + CreateCollectionConfig { + max_supply: None, + mint_type: pallet_nfts::MintType::Public, + price: None, + start_block: None, + end_block: None, + }, + )); + assert_eq!(Nfts::collection_owner(next_collection_id), Some(account(owner))); + }); +} + +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let collection = nfts::create_collection(ALICE); + assert_ok!(NonFungibles::destroy( + signed(ALICE), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(Nfts::collection_owner(collection), None); + }); +} + #[test] fn collection_works() { new_test_ext().execute_with(|| { diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index 6205850e..efb4e132 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -30,7 +30,7 @@ const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); const BOB: AccountId32 = AccountId32::new([2_u8; 32]); const DEBUG_OUTPUT: pallet_revive::DebugInfo = pallet_revive::DebugInfo::UnsafeDebug; const FERDIE: AccountId32 = AccountId32::new([3_u8; 32]); -const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +const GAS_LIMIT: Weight = Weight::from_parts(500_000_000_000, 3 * 1024 * 1024); const INIT_AMOUNT: Balance = 100_000_000 * UNIT; const INIT_VALUE: Balance = 100 * UNIT; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 8f26a6ab..299ac04a 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -116,19 +116,17 @@ fn approve_works() { fn owner_of_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); }); } -// TODO #[test] fn get_attribute_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(addr.clone()), @@ -146,29 +144,248 @@ fn get_attribute_works() { AttributeNamespace::CollectionOwner, "some attribute".as_bytes().to_vec(), ), - Ok("some value".as_bytes().to_vec()) + Ok(Some("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn set_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + + assert_ok!(set_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + "some value".as_bytes().to_vec(), + )); + + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn clear_attribute_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(addr.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(clear_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec() + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + None + ); + }); +} + +#[test] +fn approve_item_attributes_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(approve_item_attributes(&addr.clone(), collection, item, ALICE)); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn cancel_item_attributes_approval_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(addr.clone()), + collection, + item, + ALICE.into() + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(cancel_item_attributes_approval( + &addr.clone(), + collection, + item, + ALICE, + CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + ) + .is_err()); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(set_metadata(&addr.clone(), collection, item, vec![])); + assert_eq!( + pallet_nfts::ItemMetadataOf::::get(collection, item) + .map(|metadata| metadata.data), + Some(MetadataData::default()) + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + assert_ok!(Nfts::set_metadata( + RuntimeOrigin::signed(addr.clone()), + collection, + item, + MetadataData::default() + )); + assert_ok!(clear_metadata(&addr.clone(), collection, item)); + assert_eq!( + pallet_nfts::ItemMetadataOf::::get(collection, item) + .map(|metadata| metadata.data), + None ); }); } -// TODO #[test] fn create_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); - assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + let collection = nfts::next_collection_id(); + assert_ok!(create( + &addr.clone(), + addr.clone(), + CreateCollectionConfig { + max_supply: Some(100), + mint_type: MintType::Public, + price: None, + start_block: None, + end_block: None, + } + )); + assert_eq!( + pallet_nfts::Collection::::get(collection), + Some(pallet_nfts::CollectionDetails { + owner: addr.clone(), + owner_deposit: 100000000000, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }) + ); }); } -// TODO #[test] fn destroy_works() { new_test_ext().execute_with(|| { let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); - assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + let collection = nfts::create_collection(&addr, &addr); + assert_ok!(destroy( + &addr.clone(), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(pallet_nfts::Collection::::get(collection), None); + }); +} + +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let value = 10; + + let collection = nfts::create_collection(&addr, &addr); + assert_ok!(set_max_supply(&addr.clone(), collection, value)); + + (0..value).into_iter().for_each(|i| { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(addr.clone()), + collection, + i, + ALICE.into(), + None + )); + }); + assert!(Nfts::mint( + RuntimeOrigin::signed(addr.clone()), + collection, + value + 1, + ALICE.into(), + None + ) + .is_err()); }); } diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index ba174c39..bcec2bd5 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -1,5 +1,9 @@ use super::*; +pub(super) type AttributeKey = BoundedVec::KeyLimit>; +pub(super) type AttributeValue = BoundedVec::ValueLimit>; +pub(super) type MetadataData = BoundedVec::StringLimit>; + fn do_bare_call(function: &str, addr: &AccountId32, params: Vec) -> ExecReturnValue { let function = function_selector(function); let params = [function, params].concat(); @@ -84,11 +88,18 @@ pub(super) fn get_attribute( item: ItemId, namespace: AttributeNamespace, key: Vec, -) -> Result, Error> { - let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); +) -> Result>, Error> { + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + ] + .concat(); let result = do_bare_call("get_attribute", &addr, params); - decoded::, Error>>(result.clone()) + decoded::, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) } pub(super) fn create( @@ -130,9 +141,14 @@ pub(super) fn set_attribute( key: Vec, value: Vec, ) -> Result<(), Error> { - let params = - [collection.encode(), item.encode(), namespace.encode(), key.encode(), value.encode()] - .concat(); + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + AttributeValue::truncate_from(value).encode(), + ] + .concat(); let result = do_bare_call("set_attribute", &addr, params); decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) @@ -219,6 +235,7 @@ pub(super) fn item_metadata( let result = do_bare_call("item_metadata", &addr, params); decoded::>, Error>>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) } pub(super) mod nfts { @@ -263,7 +280,7 @@ pub(super) mod nfts { next_id } - pub(super) fn next_collection_id() -> u32 { + pub(crate) fn next_collection_id() -> u32 { pallet_nfts::NextCollectionId::::get().unwrap_or_default() } diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 7018685c..27e6d551 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -146,10 +146,10 @@ pub fn set_attribute( value: Vec, ) -> Result<()> { build_dispatch(SET_ATTRIBUTE) - .input::<(CollectionId, ItemId, AttributeNamespace, Vec, Vec)>() + .input::<(CollectionId, Option, AttributeNamespace, Vec, Vec)>() .output::, true>() .handle_error_code::() - .call(&(collection, item, namespace, key, value)) + .call(&(collection, Some(item), namespace, key, value)) } #[inline] @@ -160,10 +160,10 @@ pub fn clear_attribute( key: Vec, ) -> Result<()> { build_dispatch(CLEAR_ATTRIBUTE) - .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() + .input::<(CollectionId, Option, AttributeNamespace, Vec)>() .output::, true>() .handle_error_code::() - .call(&(collection, item, namespace, key)) + .call(&(collection, Some(item), namespace, key)) } #[inline] @@ -190,7 +190,7 @@ pub fn approve_item_attributes( item: ItemId, delegate: AccountId, ) -> Result<()> { - build_dispatch(CLEAR_METADATA) + build_dispatch(APPROVE_ITEM_ATTRIBUTES) .input::<(CollectionId, ItemId, AccountId)>() .output::, true>() .handle_error_code::() @@ -204,7 +204,7 @@ pub fn cancel_item_attributes_approval( delegate: AccountId, witness: CancelAttributesApprovalWitness, ) -> Result<()> { - build_dispatch(CLEAR_METADATA) + build_dispatch(CANCEL_ITEM_ATTRIBUTES_APPROVAL) .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() .output::, true>() .handle_error_code::() @@ -273,7 +273,7 @@ mod constants { pub(super) const CLEAR_ATTRIBUTE: u8 = 13; pub(super) const SET_METADATA: u8 = 14; pub(super) const CLEAR_METADATA: u8 = 15; - pub(super) const APPROVE_ITEM_ATTRIBUTE: u8 = 16; + pub(super) const APPROVE_ITEM_ATTRIBUTES: u8 = 16; pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; pub(super) const SET_MAX_SUPPLY: u8 = 18; From fe4ef45d575fdcf0b1dc8f42ae853d3a7dbd6b6c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:20:50 +0700 Subject: [PATCH 71/79] fix: devnet api config --- runtime/devnet/src/config/api/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 0a32243c..8d59c13d 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -402,7 +402,7 @@ mod tests { NonFungibles(OwnerOf { collection: 1, item: 1 }), NonFungibles(GetAttribute { collection: 0, - item: Some(0), + item: 0, namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: bounded_vec![], }), From ebbba96a63dbaddd842f253f9e5f92023821a7df Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:04:52 +0700 Subject: [PATCH 72/79] chore: update weight pallet-nfts file --- pallets/nfts/src/weights.rs | 684 +++++++++++++++++++----------------- runtime/devnet/src/lib.rs | 1 + 2 files changed, 355 insertions(+), 330 deletions(-) diff --git a/pallets/nfts/src/weights.rs b/pallets/nfts/src/weights.rs index c5fb60a2..a6ec8215 100644 --- a/pallets/nfts/src/weights.rs +++ b/pallets/nfts/src/weights.rs @@ -1,30 +1,14 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. //! Autogenerated weights for `pallet_nfts` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 +//! DATE: 2024-10-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `R0GUE`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// ./target/release/pop-node // benchmark // pallet // --chain=dev @@ -34,12 +18,11 @@ // --no-storage-info // --no-median-slopes // --no-min-squares -// --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/nfts/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --output=./pallets/nfts/src/weights.rs +// --template=./scripts/pallet-weights-template.hbs +// --extrinsic= #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -107,10 +90,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -126,10 +109,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -141,6 +124,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -152,15 +137,19 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1005_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(1005_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -174,18 +163,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -195,18 +186,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -218,22 +211,24 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -245,6 +240,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -253,12 +250,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -269,12 +266,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -286,10 +283,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -299,10 +296,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -312,10 +309,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -329,10 +326,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -342,10 +339,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -355,10 +352,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -368,10 +365,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -381,10 +378,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -400,10 +397,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,10 +410,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -430,30 +427,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -461,12 +458,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -485,10 +482,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -502,10 +499,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,10 +516,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -536,10 +533,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -549,10 +546,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -560,10 +557,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -571,10 +568,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -582,10 +579,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -595,10 +592,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -608,10 +605,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -625,10 +622,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -644,28 +641,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -673,10 +672,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -686,10 +685,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -705,18 +704,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -726,6 +727,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -739,22 +742,22 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -766,12 +769,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -794,10 +797,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -813,10 +816,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -828,6 +831,8 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -839,15 +844,19 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1005_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(RocksDbWeight::get().writes(1005_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -861,18 +870,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -882,18 +893,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -905,22 +918,24 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -932,6 +947,8 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -940,12 +957,12 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -956,12 +973,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -973,10 +990,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -986,10 +1003,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -999,10 +1016,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1016,10 +1033,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1029,10 +1046,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1042,10 +1059,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1055,10 +1072,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1068,10 +1085,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1087,10 +1104,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1100,10 +1117,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1117,30 +1134,30 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1148,12 +1165,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1172,10 +1189,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1189,10 +1206,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1206,10 +1223,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1223,10 +1240,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1236,10 +1253,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1247,10 +1264,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1258,10 +1275,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1269,10 +1286,10 @@ impl WeightInfo for () { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1282,10 +1299,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1295,10 +1312,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1312,10 +1329,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1331,28 +1348,30 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -1360,10 +1379,10 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1373,10 +1392,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1392,18 +1411,20 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -1413,6 +1434,8 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1426,22 +1449,22 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -1453,12 +1476,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1466,3 +1489,4 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } } + diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index e0d8618d..aff7a56b 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -714,6 +714,7 @@ mod benches { [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] [pallet_message_queue, MessageQueue] + [pallet_nfts, Nfts] [pallet_sudo, Sudo] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] From f303fe956409cce8059b669d8cab3e62b72d15ef Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:52:52 +0700 Subject: [PATCH 73/79] chore: rebase --- Cargo.toml | 2 +- pallets/api/Cargo.toml | 6 +- pallets/api/src/messaging/mod.rs | 2 +- pallets/api/src/nonfungibles/tests.rs | 5 +- pop-api/Cargo.toml | 2 +- pop-api/integration-tests/Cargo.toml | 6 +- .../contracts/nonfungibles/Cargo.toml | 6 +- .../integration-tests/src/nonfungibles/mod.rs | 133 +++++++++++------- .../src/nonfungibles/utils.rs | 75 ++++------ pop-api/src/lib.rs | 3 +- pop-api/src/v0/nonfungibles/mod.rs | 2 +- pop-api/src/v0/nonfungibles/traits.rs | 2 - runtime/devnet/src/config/api/mod.rs | 102 +++++++++----- runtime/devnet/src/config/api/versioning.rs | 8 +- runtime/devnet/src/lib.rs | 10 +- 15 files changed, 205 insertions(+), 159 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d643786f..af200875 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,8 +64,8 @@ substrate-wasm-builder = "23.0.0" # Local pallet-api = { path = "pallets/api", default-features = false } -pallet-nfts = { path = "pallets/nfts", default-features = false } pallet-incentives = { path = "pallets/incentives", default-features = false } +pallet-nfts = { path = "pallets/nfts", default-features = false } pop-chain-extension = { path = "./extension", default-features = false } pop-primitives = { path = "./primitives", default-features = false } pop-runtime-common = { path = "runtime/common", default-features = false } diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index 1e2b4074..80d6a364 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -7,7 +7,7 @@ name = "pallet-api" version = "0.1.0" [package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] +targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] codec.workspace = true @@ -22,8 +22,8 @@ frame-benchmarking.workspace = true frame-support.workspace = true frame-system.workspace = true pallet-assets.workspace = true -sp-core.workspace = true pallet-nfts.workspace = true +sp-core.workspace = true sp-runtime.workspace = true sp-std.workspace = true @@ -38,7 +38,7 @@ pallet-balances.workspace = true sp-io.workspace = true [features] -default = ["std"] +default = [ "std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/pallets/api/src/messaging/mod.rs b/pallets/api/src/messaging/mod.rs index 7f340bd6..77b9dd17 100644 --- a/pallets/api/src/messaging/mod.rs +++ b/pallets/api/src/messaging/mod.rs @@ -322,7 +322,7 @@ pub enum Read { } #[derive(Debug)] -#[cfg_attr(feature = "std", derive(PartialEq, Clone))] +#[cfg_attr(feature = "std", derive(Encode, Clone))] pub enum ReadResult { Poll(Option), Get(Option>), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index c119188a..a9545f0c 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -3,7 +3,7 @@ use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, DestroyWitness, - ItemDetails, MintSettings, + MintSettings, }; use sp_runtime::{BoundedVec, DispatchError::BadOrigin}; @@ -370,7 +370,6 @@ fn approve_item_attribute_works() { assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - let mut result: Option::ValueLimit>> = None; // Successfully approve delegate to set attributes. assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); assert_ok!(Nfts::set_attribute( @@ -381,7 +380,7 @@ fn approve_item_attribute_works() { attribute.clone(), value.clone() )); - result = Some(value); + let result: Option::ValueLimit>> = Some(value); assert_eq!( NonFungibles::read(GetAttribute { collection, diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 4942db48..48932cf0 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -25,4 +25,4 @@ default = [ "std" ] fungibles = [ ] messaging = [ ] nonfungibles = [ ] -std = [ "ink/std", "pop-primitives/std", "sp-io/std" ] +std = [ "ink/std", "pop-primitives/std" ] diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 0f793136..31b99926 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -18,9 +18,9 @@ pallet-assets = { version = "37.0.0", default-features = false } pallet-balances = { version = "37.0.0", default-features = false } pallet-incentives = { path = "../../pallets/incentives", default-features = false } pallet-ismp = { git = "https://github.com/r0gue-io/ismp", branch = "polkadot-v1.14.0", default-features = false } +pallet-nfts = { path = "../../pallets/nfts", default-features = false } pallet-revive = { path = "../../pallets/revive", default-features = false } pallet-timestamp = { version = "35.0.0", default-features = false } -pallet-nfts = { path = "../../pallets/nfts", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", "messaging", @@ -38,16 +38,16 @@ staging-xcm = { version = "=14.1.0", default-features = false } xcm-executor = { version = "15.0.0", package = "staging-xcm-executor", default-features = false } [features] -default = ["std"] +default = [ "std" ] std = [ "frame-support/std", "frame-system/std", "pallet-assets/std", "pallet-balances/std", "pallet-incentives/std", + "pallet-nfts/std", "pallet-revive/std", "pallet-timestamp/std", - "pallet-nfts/std", "pop-api/std", "pop-primitives/std", "pop-runtime-devnet/std", diff --git a/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml index 47b105fb..ce8c07d7 100644 --- a/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml +++ b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml @@ -1,11 +1,13 @@ [package] +authors = [ "R0GUE " ] edition = "2021" name = "nonfungibles" version = "0.1.0" [dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../../pop-api", default-features = false, features = [ "nonfungibles" ] } +ink = { git = "https://github.com/r0gue-io/ink", branch = "sub0", default-features = false } +polkavm-derive = "0.11.0" +pop-api = { path = "../../..", default-features = false, features = [ "nonfungibles" ] } [lib] path = "lib.rs" diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 299ac04a..5e34428c 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -16,12 +16,13 @@ mod utils; const COLLECTION_ID: CollectionId = 0; const ITEM_ID: ItemId = 1; -const CONTRACT: &str = "contracts/nonfungibles/target/ink/nonfungibles.wasm"; +const CONTRACT: &str = "contracts/nonfungibles/target/ink/nonfungibles.riscv"; #[test] fn total_supply_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); // No tokens in circulation. assert_eq!( @@ -31,7 +32,7 @@ fn total_supply_works() { assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); // Tokens in circulation. - nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); assert_eq!( total_supply(&addr, COLLECTION_ID), Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) @@ -43,7 +44,8 @@ fn total_supply_works() { #[test] fn balance_of_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); // No tokens in circulation. assert_eq!( @@ -53,7 +55,7 @@ fn balance_of_works() { assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); // Tokens in circulation. - nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); assert_eq!( balance_of(&addr, COLLECTION_ID, ALICE), Ok(nfts::balance_of(COLLECTION_ID, ALICE)), @@ -65,22 +67,32 @@ fn balance_of_works() { #[test] fn allowance_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); // No tokens in circulation. assert_eq!( - allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), - Ok(!Nfts::check_allowance(&COLLECTION_ID, &None, &addr, &ALICE).is_err()), + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, None), + Ok(!Nfts::check_allowance(&COLLECTION_ID, &None, &account_id, &ALICE).is_err()), + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, None), + Ok(false) ); - assert_eq!(allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, None), Ok(false)); - let (collection, item) = - nfts::create_collection_mint_and_approve(&addr, &addr, ITEM_ID, &addr, &ALICE); + let (_, item) = nfts::create_collection_mint_and_approve( + &account_id, + &account_id, + ITEM_ID, + &account_id, + &ALICE, + ); assert_eq!( - allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), - Ok(Nfts::check_allowance(&COLLECTION_ID, &Some(item), &addr.clone(), &ALICE).is_ok()), + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, Some(item)), + Ok(Nfts::check_allowance(&COLLECTION_ID, &Some(item), &account_id.clone(), &ALICE) + .is_ok()), ); assert_eq!( - allowance(&addr.clone(), COLLECTION_ID, addr.clone(), ALICE, Some(item)), + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, Some(item)), Ok(true) ); }); @@ -89,9 +101,11 @@ fn allowance_works() { #[test] fn transfer_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); let before_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); assert_ok!(transfer(&addr, collection, item, ALICE)); let after_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); @@ -102,11 +116,15 @@ fn transfer_works() { #[test] fn approve_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(approve(&addr, collection, Some(item), ALICE, true)); - assert!(Nfts::check_allowance(&collection, &Some(item), &addr.clone(), &ALICE).is_ok(),); + assert!( + Nfts::check_allowance(&collection, &Some(item), &account_id.clone(), &ALICE).is_ok(), + ); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(ALICE), collection, item, BOB.into())); }); @@ -115,8 +133,10 @@ fn approve_works() { #[test] fn owner_of_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &ALICE, ITEM_ID); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); }); } @@ -124,12 +144,14 @@ fn owner_of_works() { #[test] fn get_attribute_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, Some(item), pallet_nfts::AttributeNamespace::CollectionOwner, @@ -152,9 +174,11 @@ fn get_attribute_works() { #[test] fn set_attribute_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(set_attribute( &addr.clone(), @@ -181,11 +205,13 @@ fn set_attribute_works() { #[test] fn clear_attribute_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, Some(item), pallet_nfts::AttributeNamespace::CollectionOwner, @@ -215,9 +241,11 @@ fn clear_attribute_works() { #[test] fn approve_item_attributes_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(approve_item_attributes(&addr.clone(), collection, item, ALICE)); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(ALICE), @@ -243,11 +271,13 @@ fn approve_item_attributes_works() { #[test] fn cancel_item_attributes_approval_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(Nfts::approve_item_attributes( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, item, ALICE.into() @@ -282,9 +312,11 @@ fn cancel_item_attributes_approval_works() { #[test] fn set_metadata_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(set_metadata(&addr.clone(), collection, item, vec![])); assert_eq!( pallet_nfts::ItemMetadataOf::::get(collection, item) @@ -297,11 +329,13 @@ fn set_metadata_works() { #[test] fn clear_metadata_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let (collection, item) = nfts::create_collection_and_mint_to(&addr, &addr, &addr, ITEM_ID); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(Nfts::set_metadata( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, item, MetadataData::default() @@ -318,12 +352,13 @@ fn clear_metadata_works() { #[test] fn create_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); let collection = nfts::next_collection_id(); assert_ok!(create( &addr.clone(), - addr.clone(), + account_id.clone(), CreateCollectionConfig { max_supply: Some(100), mint_type: MintType::Public, @@ -335,7 +370,7 @@ fn create_works() { assert_eq!( pallet_nfts::Collection::::get(collection), Some(pallet_nfts::CollectionDetails { - owner: addr.clone(), + owner: account_id.clone(), owner_deposit: 100000000000, items: 0, item_metadatas: 0, @@ -349,9 +384,10 @@ fn create_works() { #[test] fn destroy_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); - let collection = nfts::create_collection(&addr, &addr); + let collection = nfts::create_collection(&account_id, &account_id); assert_ok!(destroy( &addr.clone(), collection, @@ -364,15 +400,16 @@ fn destroy_works() { #[test] fn set_max_supply_works() { new_test_ext().execute_with(|| { - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); let value = 10; - let collection = nfts::create_collection(&addr, &addr); + let collection = nfts::create_collection(&account_id, &account_id); assert_ok!(set_max_supply(&addr.clone(), collection, value)); (0..value).into_iter().for_each(|i| { assert_ok!(Nfts::mint( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, i, ALICE.into(), @@ -380,7 +417,7 @@ fn set_max_supply_works() { )); }); assert!(Nfts::mint( - RuntimeOrigin::signed(addr.clone()), + RuntimeOrigin::signed(account_id.clone()), collection, value + 1, ALICE.into(), diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index bcec2bd5..0a581636 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -1,10 +1,12 @@ use super::*; +use crate::sp_core::H160; +use pallet_revive::AddressMapper; pub(super) type AttributeKey = BoundedVec::KeyLimit>; pub(super) type AttributeValue = BoundedVec::ValueLimit>; pub(super) type MetadataData = BoundedVec::StringLimit>; -fn do_bare_call(function: &str, addr: &AccountId32, params: Vec) -> ExecReturnValue { +fn do_bare_call(function: &str, addr: &H160, params: Vec) -> ExecReturnValue { let function = function_selector(function); let params = [function, params].concat(); bare_call(addr.clone(), params, 0).expect("should work") @@ -15,14 +17,14 @@ pub(super) fn decoded(result: ExecReturnValue) -> Result::decode(&mut &result.data[1..]).map_err(|_| result) } -pub(super) fn total_supply(addr: &AccountId32, collection: CollectionId) -> Result { +pub(super) fn total_supply(addr: &H160, collection: CollectionId) -> Result { let result = do_bare_call("total_supply", addr, collection.encode()); decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } pub(super) fn balance_of( - addr: &AccountId32, + addr: &H160, collection: CollectionId, owner: AccountId32, ) -> Result { @@ -33,7 +35,7 @@ pub(super) fn balance_of( } pub(super) fn allowance( - addr: &AccountId32, + addr: &H160, collection: CollectionId, owner: AccountId32, operator: AccountId32, @@ -46,7 +48,7 @@ pub(super) fn allowance( } pub(super) fn transfer( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, to: AccountId32, @@ -58,7 +60,7 @@ pub(super) fn transfer( } pub(super) fn approve( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: Option, operator: AccountId32, @@ -72,7 +74,7 @@ pub(super) fn approve( } pub(super) fn owner_of( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, ) -> Result { @@ -83,7 +85,7 @@ pub(super) fn owner_of( } pub(super) fn get_attribute( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -103,7 +105,7 @@ pub(super) fn get_attribute( } pub(super) fn create( - addr: &AccountId32, + addr: &H160, admin: AccountId32, config: CreateCollectionConfig, ) -> Result<(), Error> { @@ -114,7 +116,7 @@ pub(super) fn create( } pub(super) fn destroy( - addr: &AccountId32, + addr: &H160, collection: CollectionId, witness: DestroyWitness, ) -> Result<(), Error> { @@ -125,7 +127,7 @@ pub(super) fn destroy( } pub(super) fn collection( - addr: &AccountId32, + addr: &H160, collection: CollectionId, ) -> Result, Error> { let result = do_bare_call("collection", &addr, collection.encode()); @@ -134,7 +136,7 @@ pub(super) fn collection( } pub(super) fn set_attribute( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -155,7 +157,7 @@ pub(super) fn set_attribute( } pub(super) fn clear_attribute( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -168,7 +170,7 @@ pub(super) fn clear_attribute( } pub(super) fn set_metadata( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, data: Vec, @@ -180,7 +182,7 @@ pub(super) fn set_metadata( } pub(super) fn clear_metadata( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, ) -> Result<(), Error> { @@ -191,7 +193,7 @@ pub(super) fn clear_metadata( } pub(super) fn approve_item_attributes( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, delegate: AccountId32, @@ -203,7 +205,7 @@ pub(super) fn approve_item_attributes( } pub(super) fn cancel_item_attributes_approval( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, delegate: AccountId32, @@ -216,7 +218,7 @@ pub(super) fn cancel_item_attributes_approval( } pub(super) fn set_max_supply( - addr: &AccountId32, + addr: &H160, collection: CollectionId, max_supply: u32, ) -> Result<(), Error> { @@ -227,7 +229,7 @@ pub(super) fn set_max_supply( } pub(super) fn item_metadata( - addr: &AccountId32, + addr: &H160, collection: CollectionId, item: ItemId, ) -> Result>, Error> { @@ -274,7 +276,7 @@ pub(super) mod nfts { let next_id = next_collection_id(); assert_ok!(Nfts::create( RuntimeOrigin::signed(owner.clone()), - owner.clone().into(), + admin.clone().into(), collection_config_with_all_settings_enabled() )); next_id @@ -314,42 +316,13 @@ pub(super) mod nfts { } } -pub(super) fn instantiate_and_create_nonfungible( - contract: &str, - admin: AccountId32, - config: CreateCollectionConfig, -) -> Result { - let function = function_selector("new"); - let input = [function, admin.encode(), config.encode()].concat(); - let wasm_binary = std::fs::read(contract).expect("could not read .wasm file"); - let result = Contracts::bare_instantiate( - ALICE, - INIT_VALUE, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - input, - vec![], - DEBUG_OUTPUT, - CollectEvents::Skip, - ) - .result - .expect("should work"); - let address = result.account_id; - let result = result.result; - decoded::>(result.clone()) - .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) - .map(|_| address) -} - /// Get the last event from pallet contracts. pub(super) fn last_contract_event() -> Vec { - let events = System::read_events_for_pallet::>(); + let events = System::read_events_for_pallet::>(); let contract_events = events .iter() .filter_map(|event| match event { - pallet_contracts::Event::::ContractEmitted { data, .. } => - Some(data.as_slice()), + pallet_revive::Event::::ContractEmitted { data, .. } => Some(data.as_slice()), _ => None, }) .collect::>(); diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 1dc93915..88a3880b 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -73,8 +73,7 @@ mod constants { pub(crate) const FUNGIBLES: u8 = 150; pub(crate) const MESSAGING: u8 = 151; pub(crate) const INCENTIVES: u8 = 152; - pub(crate) const NONFUNGIBLES: u8 = 151; - pub(crate) const NFTS: u8 = 50; + pub(crate) const NONFUNGIBLES: u8 = 154; } // Helper method to build a dispatch call or a call to read state. diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index 27e6d551..ed2405df 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -130,7 +130,7 @@ pub fn collection(collection: CollectionId) -> Result> #[inline] pub fn item_metadata(collection: CollectionId, item: ItemId) -> Result>> { - build_read_state(BURN) + build_read_state(ITEM_METADATA) .input::<(CollectionId, ItemId)>() .output::>>, true>() .handle_error_code::() diff --git a/pop-api/src/v0/nonfungibles/traits.rs b/pop-api/src/v0/nonfungibles/traits.rs index 616767a3..c9e3ad04 100644 --- a/pop-api/src/v0/nonfungibles/traits.rs +++ b/pop-api/src/v0/nonfungibles/traits.rs @@ -2,8 +2,6 @@ use core::result::Result; -use ink::prelude::string::String; - use super::*; #[ink::trait_definition] diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 8d59c13d..10bcbc1e 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -12,8 +12,8 @@ use versioning::*; use crate::{ config::{assets::TrustBackedAssetsInstance, xcm::LocalOriginToLocation}, - fungibles, messaging, Balances, Ismp, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, - RuntimeHoldReason, TransactionByteFee, fungibles, nonfungibles, + fungibles, messaging, nonfungibles, Balances, Ismp, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeHoldReason, TransactionByteFee, }; mod versioning; @@ -38,7 +38,7 @@ pub enum RuntimeRead { #[codec(index = 151)] Messaging(messaging::Read), // Non-fungible token queries. - #[codec(index = 152)] + #[codec(index = 154)] NonFungibles(nonfungibles::Read), } @@ -174,11 +174,8 @@ pub struct Filter(PhantomData); impl> Contains for Filter { fn contains(c: &RuntimeCall) -> bool { - use fungibles::Call::*; - use messaging::Call::*; - use nonfungibles::Call::*; - use pallet_incentives::Call::*; - T::BaseCallFilter::contains(c) && + let contain_fungibles: bool = { + use fungibles::Call::*; matches!( c, RuntimeCall::Fungibles( @@ -189,15 +186,16 @@ impl> Contains f create { .. } | set_metadata { .. } | start_destroy { .. } | clear_metadata { .. } | - mint { .. } | burn { .. } - ) | RuntimeCall::Messaging( - send { .. } | - ismp_get { .. } | ismp_post { .. } | - xcm_new_query { .. } | - remove { .. } - ) | RuntimeCall::Incentives( - register_contract { .. } | claim_rewards { .. } | deposit_funds { .. } - ) | RuntimeCall::NonFungibles( + mint { .. } | burn { .. }, + ) + ) + }; + + let contain_nonfungibles: bool = { + use nonfungibles::Call::*; + matches!( + c, + RuntimeCall::NonFungibles( transfer { .. } | approve { .. } | create { .. } | destroy { .. } | set_metadata { .. } | @@ -211,29 +209,69 @@ impl> Contains f ) ) }; + + let contain_messaging: bool = { + use messaging::Call::*; + matches!( + c, + RuntimeCall::Messaging( + send { .. } | + ismp_get { .. } | ismp_post { .. } | + xcm_new_query { .. } | + remove { .. }, + ) + ) + }; + + let contain_incentives: bool = { + use pallet_incentives::Call::*; + matches!( + c, + RuntimeCall::Incentives( + register_contract { .. } | claim_rewards { .. } | deposit_funds { .. }, + ) + ) + }; + + T::BaseCallFilter::contains(c) && + contain_fungibles | contain_nonfungibles | contain_messaging | contain_incentives + } } impl Contains for Filter { fn contains(r: &RuntimeRead) -> bool { - use fungibles::Read::*; - use messaging::Read::*; - use nonfungibles::Read::*; - matches!( - r, - RuntimeRead::Fungibles( - TotalSupply(..) | - BalanceOf { .. } | - Allowance { .. } | - TokenName(..) | TokenSymbol(..) | - TokenDecimals(..) | - TokenExists(..) - ) | RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..)) | RuntimeRead::NonFungibles( + let contain_fungibles: bool = { + use fungibles::Read::*; + matches!( + r, + RuntimeRead::Fungibles( + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + TokenName(..) | TokenSymbol(..) | + TokenDecimals(..) | TokenExists(..), + ) + ) + }; + let contain_nonfungibles: bool = { + use nonfungibles::Read::*; + matches!( + r, + RuntimeRead::NonFungibles( TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | OwnerOf { .. } | GetAttribute { .. } | - Collection { .. } | NextCollectionId | ItemMetadata { .. } + Collection { .. } | NextCollectionId | + ItemMetadata { .. }, ) - ) + ) + }; + + let contain_messaging: bool = { + use messaging::Read::*; + matches!(r, RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..))) + }; + + contain_fungibles | contain_nonfungibles | contain_messaging } } diff --git a/runtime/devnet/src/config/api/versioning.rs b/runtime/devnet/src/config/api/versioning.rs index 0e36646d..1cb10d9d 100644 --- a/runtime/devnet/src/config/api/versioning.rs +++ b/runtime/devnet/src/config/api/versioning.rs @@ -119,9 +119,9 @@ impl From for V0Error { // Note: message not used let ModuleError { index, error, message: _message } = error; // Map `pallet-contracts::Error::DecodingFailed` to `Error::DecodingFailed` - if index as usize - == ::index() - && error == DECODING_FAILED_ERROR + if index as usize == + ::index() && + error == DECODING_FAILED_ERROR { Error::DecodingFailed } else { @@ -209,7 +209,7 @@ mod tests { 1, )) .unwrap_err(); - let expected_err: DispatchError = pallet_contracts::Error::::DecodingFailed.into(); + let expected_err: DispatchError = pallet_revive::Error::::DecodingFailed.into(); assert_eq!(err.encode(), expected_err.encode()); } diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index aff7a56b..498ffda2 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -249,10 +249,10 @@ impl Contains for FilteredCalls { matches!( c, RuntimeCall::Balances( - force_adjust_total_issuance { .. } - | force_set_balance { .. } - | force_transfer { .. } - | force_unreserve { .. } + force_adjust_total_issuance { .. } | + force_set_balance { .. } | + force_transfer { .. } | + force_unreserve { .. } ) ) } @@ -697,7 +697,7 @@ mod runtime { #[runtime::pallet_index(152)] pub type Incentives = pallet_incentives::Pallet; - #[runtime::pallet_index(153)] + #[runtime::pallet_index(154)] pub type NonFungibles = nonfungibles::Pallet; // Revive From a9f4ceb896ad137462a33de5722b28329f18c76d Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:05:19 +0700 Subject: [PATCH 74/79] fix: formatting --- runtime/devnet/src/config/api/mod.rs | 72 ++++++++++----------- runtime/devnet/src/config/api/versioning.rs | 6 +- runtime/devnet/src/lib.rs | 8 +-- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 405527b7..10bcbc1e 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -61,9 +61,8 @@ impl Readable for RuntimeRead { match self { RuntimeRead::Fungibles(key) => RuntimeResult::Fungibles(fungibles::Pallet::read(key)), RuntimeRead::Messaging(key) => RuntimeResult::Messaging(messaging::Pallet::read(key)), - RuntimeRead::NonFungibles(key) => { - RuntimeResult::NonFungibles(nonfungibles::Pallet::read(key)) - }, + RuntimeRead::NonFungibles(key) => + RuntimeResult::NonFungibles(nonfungibles::Pallet::read(key)), } } } @@ -180,14 +179,14 @@ impl> Contains f matches!( c, RuntimeCall::Fungibles( - transfer { .. } - | transfer_from { .. } - | approve { .. } | increase_allowance { .. } - | decrease_allowance { .. } - | create { .. } | set_metadata { .. } - | start_destroy { .. } - | clear_metadata { .. } - | mint { .. } | burn { .. }, + transfer { .. } | + transfer_from { .. } | + approve { .. } | increase_allowance { .. } | + decrease_allowance { .. } | + create { .. } | set_metadata { .. } | + start_destroy { .. } | + clear_metadata { .. } | + mint { .. } | burn { .. }, ) ) }; @@ -197,16 +196,16 @@ impl> Contains f matches!( c, RuntimeCall::NonFungibles( - transfer { .. } - | approve { .. } | create { .. } - | destroy { .. } | set_metadata { .. } - | clear_metadata { .. } - | set_attribute { .. } - | clear_attribute { .. } - | approve_item_attributes { .. } - | cancel_item_attributes_approval { .. } - | mint { .. } | burn { .. } - | set_max_supply { .. }, + transfer { .. } | + approve { .. } | create { .. } | + destroy { .. } | set_metadata { .. } | + clear_metadata { .. } | + set_attribute { .. } | + clear_attribute { .. } | + approve_item_attributes { .. } | + cancel_item_attributes_approval { .. } | + mint { .. } | burn { .. } | + set_max_supply { .. }, ) ) }; @@ -216,10 +215,10 @@ impl> Contains f matches!( c, RuntimeCall::Messaging( - send { .. } - | ismp_get { .. } | ismp_post { .. } - | xcm_new_query { .. } - | remove { .. }, + send { .. } | + ismp_get { .. } | ismp_post { .. } | + xcm_new_query { .. } | + remove { .. }, ) ) }; @@ -234,8 +233,8 @@ impl> Contains f ) }; - T::BaseCallFilter::contains(c) - && contain_fungibles | contain_nonfungibles | contain_messaging | contain_incentives + T::BaseCallFilter::contains(c) && + contain_fungibles | contain_nonfungibles | contain_messaging | contain_incentives } } @@ -246,11 +245,10 @@ impl Contains for Filter { matches!( r, RuntimeRead::Fungibles( - TotalSupply(..) - | BalanceOf { .. } | Allowance { .. } - | TokenName(..) | TokenSymbol(..) - | TokenDecimals(..) - | TokenExists(..), + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + TokenName(..) | TokenSymbol(..) | + TokenDecimals(..) | TokenExists(..), ) ) }; @@ -259,11 +257,11 @@ impl Contains for Filter { matches!( r, RuntimeRead::NonFungibles( - TotalSupply(..) - | BalanceOf { .. } | Allowance { .. } - | OwnerOf { .. } | GetAttribute { .. } - | Collection { .. } - | NextCollectionId | ItemMetadata { .. }, + TotalSupply(..) | + BalanceOf { .. } | Allowance { .. } | + OwnerOf { .. } | GetAttribute { .. } | + Collection { .. } | NextCollectionId | + ItemMetadata { .. }, ) ) }; diff --git a/runtime/devnet/src/config/api/versioning.rs b/runtime/devnet/src/config/api/versioning.rs index 87821ead..1cb10d9d 100644 --- a/runtime/devnet/src/config/api/versioning.rs +++ b/runtime/devnet/src/config/api/versioning.rs @@ -119,9 +119,9 @@ impl From for V0Error { // Note: message not used let ModuleError { index, error, message: _message } = error; // Map `pallet-contracts::Error::DecodingFailed` to `Error::DecodingFailed` - if index as usize - == ::index() - && error == DECODING_FAILED_ERROR + if index as usize == + ::index() && + error == DECODING_FAILED_ERROR { Error::DecodingFailed } else { diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index 716987fb..498ffda2 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -249,10 +249,10 @@ impl Contains for FilteredCalls { matches!( c, RuntimeCall::Balances( - force_adjust_total_issuance { .. } - | force_set_balance { .. } - | force_transfer { .. } - | force_unreserve { .. } + force_adjust_total_issuance { .. } | + force_set_balance { .. } | + force_transfer { .. } | + force_unreserve { .. } ) ) } From 159d6bc0dd146d3cc0186996ef09b8edb59a8188 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:05:40 +0700 Subject: [PATCH 75/79] fix: taplo --- pallets/api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index 13ba2dc5..80d6a364 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -30,8 +30,8 @@ sp-std.workspace = true # Cross chain ismp.workspace = true pallet-ismp.workspace = true -xcm-executor.workspace = true xcm.workspace = true +xcm-executor.workspace = true [dev-dependencies] pallet-balances.workspace = true From a5a1c8c480e671e69c2f0069548ab01e8362bedf Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:10:21 +0700 Subject: [PATCH 76/79] fix: remove duplicate import --- pop-api/src/v0/mod.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index eae8f579..50b41e78 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -13,11 +13,6 @@ pub mod incentives; /// APIs for messaging. #[cfg(feature = "messaging")] pub mod messaging; - -/// APIs for nonfungible tokens. -#[cfg(feature = "nonfungibles")] -pub mod nonfungibles; - /// APIs for nonfungible tokens. #[cfg(feature = "nonfungibles")] pub mod nonfungibles; From 0ae4535a846d6ac764bba9c5f6eb24a0249aced7 Mon Sep 17 00:00:00 2001 From: Tin Chung <56880684+chungquantin@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:41:29 +0700 Subject: [PATCH 77/79] chore: nonfungibles rebase (#360) --- .../src/nonfungibles/utils.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 789edd26..0a581636 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -85,7 +85,7 @@ pub(super) fn owner_of( } pub(super) fn get_attribute( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -105,7 +105,7 @@ addr: &H160, } pub(super) fn create( -addr: &H160, + addr: &H160, admin: AccountId32, config: CreateCollectionConfig, ) -> Result<(), Error> { @@ -116,7 +116,7 @@ addr: &H160, } pub(super) fn destroy( -addr: &H160, + addr: &H160, collection: CollectionId, witness: DestroyWitness, ) -> Result<(), Error> { @@ -127,7 +127,7 @@ addr: &H160, } pub(super) fn collection( -addr: &H160, + addr: &H160, collection: CollectionId, ) -> Result, Error> { let result = do_bare_call("collection", &addr, collection.encode()); @@ -136,7 +136,7 @@ addr: &H160, } pub(super) fn set_attribute( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -157,7 +157,7 @@ addr: &H160, } pub(super) fn clear_attribute( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, namespace: AttributeNamespace, @@ -170,7 +170,7 @@ addr: &H160, } pub(super) fn set_metadata( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, data: Vec, @@ -182,7 +182,7 @@ addr: &H160, } pub(super) fn clear_metadata( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, ) -> Result<(), Error> { @@ -193,7 +193,7 @@ addr: &H160, } pub(super) fn approve_item_attributes( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, delegate: AccountId32, @@ -205,7 +205,7 @@ addr: &H160, } pub(super) fn cancel_item_attributes_approval( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, delegate: AccountId32, @@ -218,7 +218,7 @@ addr: &H160, } pub(super) fn set_max_supply( -addr: &H160, + addr: &H160, collection: CollectionId, max_supply: u32, ) -> Result<(), Error> { @@ -229,7 +229,7 @@ addr: &H160, } pub(super) fn item_metadata( -addr: &H160, + addr: &H160, collection: CollectionId, item: ItemId, ) -> Result>, Error> { From b87948845a2675909b12726014437fb975cf74f2 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:13:55 +0700 Subject: [PATCH 78/79] fix: formatting --- Cargo.lock | 20 +------------------ pallets/nfts/Cargo.toml | 2 +- pallets/nfts/src/benchmarking.rs | 2 +- pallets/nfts/src/common_functions.rs | 2 +- pallets/nfts/src/features/approvals.rs | 2 +- .../src/features/create_delete_collection.rs | 2 +- .../nfts/src/features/create_delete_item.rs | 2 +- pallets/nfts/src/lib.rs | 2 +- pallets/nfts/src/tests.rs | 2 +- pallets/nfts/src/types.rs | 2 +- 10 files changed, 10 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fc0574a..37f7ee69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -8308,24 +8308,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-nfts" -version = "31.0.0" -dependencies = [ - "enumflags2", - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", -] - [[package]] name = "pallet-nfts-runtime-api" version = "22.0.0" diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index ae7c1425..1fcfe9dd 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -50,4 +50,4 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime", -] \ No newline at end of file +] diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index af090fc0..ab66da26 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -879,4 +879,4 @@ benchmarks_instance_pallet! { } impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); -} \ No newline at end of file +} diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index 10df5193..6fe483f1 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -85,4 +85,4 @@ impl, I: 'static> Pallet { .or(T::CollectionId::initial_value()) .expect("Failed to get next collection ID") } -} \ No newline at end of file +} diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 1b62668a..6d71c1a2 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -253,4 +253,4 @@ impl, I: 'static> Pallet { }; Err(Error::::NoPermission.into()) } -} \ No newline at end of file +} diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 678bada5..b7efd03a 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -156,4 +156,4 @@ impl, I: 'static> Pallet { }) }) } -} \ No newline at end of file +} diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index e6e24a60..cc29f8da 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -277,4 +277,4 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) } -} \ No newline at end of file +} diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 4a1fc765..bc8b67b6 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -1964,4 +1964,4 @@ pub mod pallet { } } -sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); \ No newline at end of file +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 4b0e267b..397a715c 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -4119,4 +4119,4 @@ fn clear_collection_metadata_works() { assert_eq!(Collection::::get(0), None); assert_eq!(Balances::reserved_balance(&account(1)), 10); }); -} \ No newline at end of file +} diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 90edad14..8a5153c3 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -565,4 +565,4 @@ pub struct PreSignedAttributes { pub(super) namespace: AttributeNamespace, /// A deadline for the signature. pub(super) deadline: Deadline, -} \ No newline at end of file +} From 8745056ab391564304275d9dfaefb0c317122431 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:22:46 +0700 Subject: [PATCH 79/79] chore: resolve review comments refactor: item metadata fix: item metadata tests test(nonfungibles): add mint and burn tests refactor: non fungibles sub0 (#364) Co-authored-by: Tin Chung <56880684+chungquantin@users.noreply.github.com> fix: pallet tests + integration tests refactor: remove unused types refactor(nonfungibles): CollectionConfig parameter chore: resolve review comments refactor: item metadata fix: item metadata tests test(nonfungibles): add mint and burn tests refactor: non fungibles sub0 (#364) Co-authored-by: Tin Chung <56880684+chungquantin@users.noreply.github.com> fix: pallet tests + integration tests refactor: remove unused types refactor(nonfungibles): CollectionConfig parameter chore: remove unused code --- Cargo.lock | 35 +- pallets/api/src/fungibles/mod.rs | 4 +- pallets/api/src/fungibles/tests.rs | 363 +++++++----------- pallets/api/src/messaging/mod.rs | 2 +- pallets/api/src/mock.rs | 61 +-- pallets/api/src/nonfungibles/mod.rs | 43 +-- pallets/api/src/nonfungibles/tests.rs | 175 ++++----- pallets/api/src/nonfungibles/types.rs | 28 +- pallets/nfts/Cargo.toml | 1 + pallets/nfts/src/common_functions.rs | 8 + pallets/nfts/src/features/approvals.rs | 31 +- pallets/nfts/src/tests.rs | 18 +- pallets/nfts/src/types.rs | 14 +- pop-api/Cargo.toml | 2 + .../contracts/nonfungibles/lib.rs | 17 +- .../integration-tests/src/nonfungibles/mod.rs | 54 ++- .../src/nonfungibles/utils.rs | 32 +- pop-api/src/lib.rs | 2 + pop-api/src/macros.rs | 53 +++ pop-api/src/v0/fungibles/errors.rs | 13 +- pop-api/src/v0/fungibles/traits.rs | 4 +- pop-api/src/v0/nonfungibles/mod.rs | 13 +- pop-api/src/v0/nonfungibles/types.rs | 166 +++++++- runtime/devnet/src/config/api/mod.rs | 61 +-- runtime/devnet/src/config/api/versioning.rs | 31 +- runtime/devnet/src/config/assets.rs | 13 +- 26 files changed, 678 insertions(+), 566 deletions(-) create mode 100644 pop-api/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index 37f7ee69..bb753385 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -2111,7 +2111,7 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -16862,11 +16862,12 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.218.0" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b896fa8ceb71091ace9bcb81e853f54043183a1c9667cf93422c40252ffa0a" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" dependencies = [ "leb128", + "wasmparser 0.219.1", ] [[package]] @@ -17014,6 +17015,16 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.219.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +dependencies = [ + "bitflags 2.5.0", + "indexmap 2.2.6", +] + [[package]] name = "wasmparser-nostd" version = "0.100.2" @@ -17042,7 +17053,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -17097,7 +17108,7 @@ dependencies = [ "object 0.30.4", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -17132,7 +17143,7 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -17215,14 +17226,14 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.102.0", ] [[package]] name = "wast" -version = "218.0.0" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a53cd1f0fa505df97557e36a58bddb8296e2fcdcd089529545ebfdb18a1b9d7" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" dependencies = [ "bumpalo", "leb128", @@ -17233,9 +17244,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.218.0" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f87f8e14e776762e07927c27c2054d2cf678aab9aae2d431a79b3e31e4dd391" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ "wast", ] diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 50e5a218..bc36936c 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -43,7 +43,7 @@ pub mod pallet { /// State reads for the fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(Clone))] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -84,7 +84,7 @@ pub mod pallet { /// Results of state reads for the fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(Encode, Clone))] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { /// Total token supply for a specified token. TotalSupply(BalanceOf), diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index a71c8394..f5c560bb 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -30,47 +30,47 @@ mod encoding_read_result { #[test] fn total_supply() { let total_supply = 1_000_000 * UNIT; - assert_eq!(ReadResult::::TotalSupply(total_supply).encode(), total_supply.encode()); + assert_eq!(ReadResult::TotalSupply::(total_supply).encode(), total_supply.encode()); } #[test] fn balance_of() { let balance = 100 * UNIT; - assert_eq!(ReadResult::::BalanceOf(balance).encode(), balance.encode()); + assert_eq!(ReadResult::BalanceOf::(balance).encode(), balance.encode()); } #[test] fn allowance() { let allowance = 100 * UNIT; - assert_eq!(ReadResult::::Allowance(allowance).encode(), allowance.encode()); + assert_eq!(ReadResult::Allowance::(allowance).encode(), allowance.encode()); } #[test] fn token_name() { let mut name = Some("some name".as_bytes().to_vec()); - assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); name = None; - assert_eq!(ReadResult::::TokenName(name.clone()).encode(), name.encode()); + assert_eq!(ReadResult::TokenName::(name.clone()).encode(), name.encode()); } #[test] fn token_symbol() { let mut symbol = Some("some symbol".as_bytes().to_vec()); - assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); symbol = None; - assert_eq!(ReadResult::::TokenSymbol(symbol.clone()).encode(), symbol.encode()); + assert_eq!(ReadResult::TokenSymbol::(symbol.clone()).encode(), symbol.encode()); } #[test] fn token_decimals() { let decimals = 42; - assert_eq!(ReadResult::::TokenDecimals(decimals).encode(), decimals.encode()); + assert_eq!(ReadResult::TokenDecimals::(decimals).encode(), decimals.encode()); } #[test] fn token_exists() { let exists = true; - assert_eq!(ReadResult::::TokenExists(exists).encode(), exists.encode()); + assert_eq!(ReadResult::TokenExists::(exists).encode(), exists.encode()); } } @@ -83,21 +83,17 @@ fn transfer_works() { let to = BOB; for origin in vec![root(), none()] { - assert_noop!(Fungibles::transfer(origin, token, account(to), value), BadOrigin); + assert_noop!(Fungibles::transfer(origin, token, to, value), BadOrigin); } // Check error works for `Assets::transfer_keep_alive()`. - assert_noop!( - Fungibles::transfer(signed(from), token, account(to), value), - AssetsError::Unknown - ); + assert_noop!(Fungibles::transfer(signed(from), token, to, value), AssetsError::Unknown); assets::create_and_mint_to(from, token, from, value * 2); - let balance_before_transfer = Assets::balance(token, &account(to)); - assert_ok!(Fungibles::transfer(signed(from), token, account(to), value)); - let balance_after_transfer = Assets::balance(token, &account(to)); + let balance_before_transfer = Assets::balance(token, &to); + assert_ok!(Fungibles::transfer(signed(from), token, to, value)); + let balance_after_transfer = Assets::balance(token, &to); assert_eq!(balance_after_transfer, balance_before_transfer + value); System::assert_last_event( - Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } - .into(), + Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), ); }); } @@ -112,36 +108,26 @@ fn transfer_from_works() { let spender = CHARLIE; for origin in vec![root(), none()] { - assert_noop!( - Fungibles::transfer_from(origin, token, account(from), account(to), value), - BadOrigin - ); + assert_noop!(Fungibles::transfer_from(origin, token, from, to, value), BadOrigin); } // Check error works for `Assets::transfer_approved()`. assert_noop!( - Fungibles::transfer_from(signed(spender), token, account(from), account(to), value), + Fungibles::transfer_from(signed(spender), token, from, to, value), AssetsError::Unknown ); // Approve `spender` to transfer up to `value`. assets::create_mint_and_approve(spender, token, from, value * 2, spender, value); // Successfully call transfer from. - let from_balance_before_transfer = Assets::balance(token, &account(from)); - let to_balance_before_transfer = Assets::balance(token, &account(to)); - assert_ok!(Fungibles::transfer_from( - signed(spender), - token, - account(from), - account(to), - value - )); - let from_balance_after_transfer = Assets::balance(token, &account(from)); - let to_balance_after_transfer = Assets::balance(token, &account(to)); + let from_balance_before_transfer = Assets::balance(token, &from); + let to_balance_before_transfer = Assets::balance(token, &to); + assert_ok!(Fungibles::transfer_from(signed(spender), token, from, to, value)); + let from_balance_after_transfer = Assets::balance(token, &from); + let to_balance_after_transfer = Assets::balance(token, &to); // Check that `to` has received the `value` tokens from `from`. assert_eq!(to_balance_after_transfer, to_balance_before_transfer + value); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - value); System::assert_last_event( - Event::Transfer { token, from: Some(account(from)), to: Some(account(to)), value } - .into(), + Event::Transfer { token, from: Some(from), to: Some(to), value }.into(), ); }); } @@ -158,7 +144,7 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, account(spender), value), + Fungibles::approve(origin, token, spender, value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } @@ -175,20 +161,20 @@ mod approve { for origin in vec![root(), none()] { assert_noop!( - Fungibles::approve(origin, token, account(spender), value), + Fungibles::approve(origin, token, spender, value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()` in `Greater` match arm. assert_noop!( - Fungibles::approve(signed(owner), token, account(spender), value), + Fungibles::approve(signed(owner), token, spender, value), AssetsError::Unknown.with_weight(WeightInfo::approve(1, 0)) ); assets::create_mint_and_approve(owner, token, owner, value, spender, value); // Check error works for `Assets::cancel_approval()` in `Less` match arm. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::approve(signed(owner), token, account(spender), value / 2), + Fungibles::approve(signed(owner), token, spender, value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); @@ -207,61 +193,38 @@ mod approve { // Approves a value to spend that is higher than the current allowance. assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); + assert_eq!(Assets::allowance(token, &owner, &spender), 0); assert_eq!( - Fungibles::approve(signed(owner), token, account(spender), value), + Fungibles::approve(signed(owner), token, spender, value), Ok(Some(WeightInfo::approve(1, 0)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); - System::assert_last_event( - Event::Approval { token, owner: account(owner), spender: account(spender), value } - .into(), - ); + assert_eq!(Assets::allowance(token, &owner, &spender), value); + System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); // Approves a value to spend that is lower than the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, account(spender), value / 2), + Fungibles::approve(signed(owner), token, spender, value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); + assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); System::assert_last_event( - Event::Approval { - token, - owner: account(owner), - spender: account(spender), - value: value / 2, - } - .into(), + Event::Approval { token, owner, spender, value: value / 2 }.into(), ); // Approves a value to spend that is equal to the current allowance. assert_eq!( - Fungibles::approve(signed(owner), token, account(spender), value / 2), + Fungibles::approve(signed(owner), token, spender, value / 2), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); + assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); System::assert_last_event( - Event::Approval { - token, - owner: account(owner), - spender: account(spender), - value: value / 2, - } - .into(), + Event::Approval { token, owner, spender, value: value / 2 }.into(), ); // Sets allowance to zero. assert_eq!( - Fungibles::approve(signed(owner), token, account(spender), 0), + Fungibles::approve(signed(owner), token, spender, 0), Ok(Some(WeightInfo::approve(0, 1)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), 0); - System::assert_last_event( - Event::Approval { - token, - owner: account(owner), - spender: account(spender), - value: 0, - } - .into(), - ); + assert_eq!(Assets::allowance(token, &owner, &spender), 0); + System::assert_last_event(Event::Approval { token, owner, spender, value: 0 }.into()); }); } } @@ -276,34 +239,25 @@ fn increase_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::increase_allowance(origin, token, account(spender), value), + Fungibles::increase_allowance(origin, token, spender, value), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } // Check error works for `Assets::approve_transfer()`. assert_noop!( - Fungibles::increase_allowance(signed(owner), token, account(spender), value), + Fungibles::increase_allowance(signed(owner), token, spender, value), AssetsError::Unknown.with_weight(AssetsWeightInfo::approve_transfer()) ); assets::create_and_mint_to(owner, token, owner, value); - assert_eq!(0, Assets::allowance(token, &account(owner), &account(spender))); - assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); - System::assert_last_event( - Event::Approval { token, owner: account(owner), spender: account(spender), value } - .into(), - ); + assert_eq!(0, Assets::allowance(token, &owner, &spender)); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); + assert_eq!(Assets::allowance(token, &owner, &spender), value); + System::assert_last_event(Event::Approval { token, owner, spender, value }.into()); // Additive. - assert_ok!(Fungibles::increase_allowance(signed(owner), token, account(spender), value)); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value * 2); + assert_ok!(Fungibles::increase_allowance(signed(owner), token, spender, value)); + assert_eq!(Assets::allowance(token, &owner, &spender), value * 2); System::assert_last_event( - Event::Approval { - token, - owner: account(owner), - spender: account(spender), - value: value * 2, - } - .into(), + Event::Approval { token, owner, spender, value: value * 2 }.into(), ); }); } @@ -318,46 +272,40 @@ fn decrease_allowance_works() { for origin in vec![root(), none()] { assert_noop!( - Fungibles::decrease_allowance(origin, token, account(spender), 0), + Fungibles::decrease_allowance(origin, token, spender, 0), BadOrigin.with_weight(WeightInfo::approve(0, 0)) ); } assets::create_mint_and_approve(owner, token, owner, value, spender, value); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + assert_eq!(Assets::allowance(token, &owner, &spender), value); // Check error works for `Assets::cancel_approval()`. No error test for `approve_transfer` // because it is not possible. assert_ok!(Assets::freeze_asset(signed(owner), token)); assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), + Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), AssetsError::AssetNotLive.with_weight(WeightInfo::approve(0, 1)) ); assert_ok!(Assets::thaw_asset(signed(owner), token)); // Owner balance is not changed if decreased by zero. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, account(spender), 0), + Fungibles::decrease_allowance(signed(owner), token, spender, 0), Ok(Some(WeightInfo::approve(0, 0)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value); + assert_eq!(Assets::allowance(token, &owner, &spender), value); // "Unapproved" error is returned if the current allowance is less than amount to decrease // with. assert_noop!( - Fungibles::decrease_allowance(signed(owner), token, account(spender), value * 2), + Fungibles::decrease_allowance(signed(owner), token, spender, value * 2), AssetsError::Unapproved ); // Decrease allowance successfully. assert_eq!( - Fungibles::decrease_allowance(signed(owner), token, account(spender), value / 2), + Fungibles::decrease_allowance(signed(owner), token, spender, value / 2), Ok(Some(WeightInfo::approve(1, 1)).into()) ); - assert_eq!(Assets::allowance(token, &account(owner), &account(spender)), value / 2); + assert_eq!(Assets::allowance(token, &owner, &spender), value / 2); System::assert_last_event( - Event::Approval { - token, - owner: account(owner), - spender: account(spender), - value: value / 2, - } - .into(), + Event::Approval { token, owner, spender, value: value / 2 }.into(), ); }); } @@ -370,19 +318,14 @@ fn create_works() { let admin = ALICE; for origin in vec![root(), none()] { - assert_noop!(Fungibles::create(origin, id, account(admin), 100), BadOrigin); + assert_noop!(Fungibles::create(origin, id, admin, 100), BadOrigin); } assert!(!Assets::asset_exists(id)); - assert_ok!(Fungibles::create(signed(creator), id, account(admin), 100)); + assert_ok!(Fungibles::create(signed(creator), id, admin, 100)); assert!(Assets::asset_exists(id)); - System::assert_last_event( - Event::Created { id, creator: account(creator), admin: account(admin) }.into(), - ); + System::assert_last_event(Event::Created { id, creator, admin }.into()); // Check error works for `Assets::create()`. - assert_noop!( - Fungibles::create(signed(creator), id, account(admin), 100), - AssetsError::InUse - ); + assert_noop!(Fungibles::create(signed(creator), id, admin, 100), AssetsError::InUse); }); } @@ -393,11 +336,11 @@ fn start_destroy_works() { // Check error works for `Assets::start_destroy()`. assert_noop!(Fungibles::start_destroy(signed(ALICE), token), AssetsError::Unknown); - assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); + assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); assert_ok!(Fungibles::start_destroy(signed(ALICE), token)); // Check that the token is not live after starting the destroy process. assert_noop!( - Assets::mint(signed(ALICE), token, account(ALICE), 10 * UNIT), + Assets::mint(signed(ALICE), token, ALICE, 10 * UNIT), AssetsError::AssetNotLive ); }); @@ -416,7 +359,7 @@ fn set_metadata_works() { Fungibles::set_metadata(signed(ALICE), token, name.clone(), symbol.clone(), decimals), AssetsError::Unknown ); - assert_ok!(Assets::create(signed(ALICE), token, account(ALICE), 1)); + assert_ok!(Assets::create(signed(ALICE), token, ALICE, 1)); assert_ok!(Fungibles::set_metadata( signed(ALICE), token, @@ -455,16 +398,16 @@ fn mint_works() { // Check error works for `Assets::mint()`. assert_noop!( - Fungibles::mint(signed(from), token, account(to), value), + Fungibles::mint(signed(from), token, to, value), sp_runtime::TokenError::UnknownAsset ); - assert_ok!(Assets::create(signed(from), token, account(from), 1)); - let balance_before_mint = Assets::balance(token, &account(to)); - assert_ok!(Fungibles::mint(signed(from), token, account(to), value)); - let balance_after_mint = Assets::balance(token, &account(to)); + assert_ok!(Assets::create(signed(from), token, from, 1)); + let balance_before_mint = Assets::balance(token, &to); + assert_ok!(Fungibles::mint(signed(from), token, to, value)); + let balance_after_mint = Assets::balance(token, &to); assert_eq!(balance_after_mint, balance_before_mint + value); System::assert_last_event( - Event::Transfer { token, from: None, to: Some(account(to)), value }.into(), + Event::Transfer { token, from: None, to: Some(to), value }.into(), ); }); } @@ -480,30 +423,27 @@ fn burn_works() { // "BalanceLow" error is returned if token is not created. assert_noop!( - Fungibles::burn(signed(owner), token, account(from), value), + Fungibles::burn(signed(owner), token, from, value), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); assets::create_and_mint_to(owner, token, from, total_supply); assert_eq!(Assets::total_supply(TOKEN), total_supply); // Check error works for `Assets::burn()`. assert_ok!(Assets::freeze_asset(signed(owner), token)); - assert_noop!( - Fungibles::burn(signed(owner), token, account(from), value), - AssetsError::AssetNotLive - ); + assert_noop!(Fungibles::burn(signed(owner), token, from, value), AssetsError::AssetNotLive); assert_ok!(Assets::thaw_asset(signed(owner), token)); // "BalanceLow" error is returned if the balance is less than amount to burn. assert_noop!( - Fungibles::burn(signed(owner), token, account(from), total_supply * 2), + Fungibles::burn(signed(owner), token, from, total_supply * 2), AssetsError::BalanceLow.with_weight(WeightInfo::balance_of()) ); - let balance_before_burn = Assets::balance(token, &account(from)); - assert_ok!(Fungibles::burn(signed(owner), token, account(from), value)); + let balance_before_burn = Assets::balance(token, &from); + assert_ok!(Fungibles::burn(signed(owner), token, from, value)); assert_eq!(Assets::total_supply(TOKEN), total_supply - value); - let balance_after_burn = Assets::balance(token, &account(from)); + let balance_after_burn = Assets::balance(token, &from); assert_eq!(balance_after_burn, balance_before_burn - value); System::assert_last_event( - Event::Transfer { token, from: Some(account(from)), to: None, value }.into(), + Event::Transfer { token, from: Some(from), to: None, value }.into(), ); }); } @@ -513,14 +453,11 @@ fn total_supply_works() { new_test_ext().execute_with(|| { let total_supply = INIT_AMOUNT; assert_eq!( - Fungibles::read(TotalSupply(TOKEN)).encode(), - ReadResult::::TotalSupply(Default::default()).encode() + Fungibles::read(TotalSupply(TOKEN)), + ReadResult::TotalSupply(Default::default()) ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, total_supply); - assert_eq!( - Fungibles::read(TotalSupply(TOKEN)).encode(), - ReadResult::::TotalSupply(total_supply).encode() - ); + assert_eq!(Fungibles::read(TotalSupply(TOKEN)), ReadResult::TotalSupply(total_supply)); assert_eq!( Fungibles::read(TotalSupply(TOKEN)).encode(), Assets::total_supply(TOKEN).encode(), @@ -533,17 +470,17 @@ fn balance_of_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), - ReadResult::::BalanceOf(Default::default()).encode() + Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + ReadResult::BalanceOf(Default::default()) ); assets::create_and_mint_to(ALICE, TOKEN, ALICE, value); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), - ReadResult::::BalanceOf(value).encode() + Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }), + ReadResult::BalanceOf(value) ); assert_eq!( - Fungibles::read(BalanceOf { token: TOKEN, owner: account(ALICE) }).encode(), - Assets::balance(TOKEN, account(ALICE)).encode(), + Fungibles::read(BalanceOf { token: TOKEN, owner: ALICE }).encode(), + Assets::balance(TOKEN, ALICE).encode(), ); }); } @@ -553,32 +490,17 @@ fn allowance_works() { new_test_ext().execute_with(|| { let value = 1_000 * UNIT; assert_eq!( - Fungibles::read(Allowance { - token: TOKEN, - owner: account(ALICE), - spender: account(BOB) - }) - .encode(), - ReadResult::::Allowance(Default::default()).encode() + Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + ReadResult::Allowance(Default::default()) ); assets::create_mint_and_approve(ALICE, TOKEN, ALICE, value * 2, BOB, value); assert_eq!( - Fungibles::read(Allowance { - token: TOKEN, - owner: account(ALICE), - spender: account(BOB) - }) - .encode(), - ReadResult::::Allowance(value).encode() + Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }), + ReadResult::Allowance(value) ); assert_eq!( - Fungibles::read(Allowance { - token: TOKEN, - owner: account(ALICE), - spender: account(BOB) - }) - .encode(), - Assets::allowance(TOKEN, &account(ALICE), &account(BOB)).encode(), + Fungibles::read(Allowance { token: TOKEN, owner: ALICE, spender: BOB }).encode(), + Assets::allowance(TOKEN, &ALICE, &BOB).encode(), ); }); } @@ -589,31 +511,13 @@ fn token_metadata_works() { let name: Vec = vec![11, 12, 13]; let symbol: Vec = vec![21, 22, 23]; let decimals: u8 = 69; - assert_eq!( - Fungibles::read(TokenName(TOKEN)).encode(), - ReadResult::::TokenName(None).encode() - ); - assert_eq!( - Fungibles::read(TokenSymbol(TOKEN)).encode(), - ReadResult::::TokenSymbol(None).encode() - ); - assert_eq!( - Fungibles::read(TokenDecimals(TOKEN)).encode(), - ReadResult::::TokenDecimals(0).encode() - ); + assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(None)); + assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(None)); + assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(0)); assets::create_and_set_metadata(ALICE, TOKEN, name.clone(), symbol.clone(), decimals); - assert_eq!( - Fungibles::read(TokenName(TOKEN)).encode(), - ReadResult::::TokenName(Some(name)).encode() - ); - assert_eq!( - Fungibles::read(TokenSymbol(TOKEN)).encode(), - ReadResult::::TokenSymbol(Some(symbol)).encode() - ); - assert_eq!( - Fungibles::read(TokenDecimals(TOKEN)).encode(), - ReadResult::::TokenDecimals(decimals).encode() - ); + assert_eq!(Fungibles::read(TokenName(TOKEN)), ReadResult::TokenName(Some(name))); + assert_eq!(Fungibles::read(TokenSymbol(TOKEN)), ReadResult::TokenSymbol(Some(symbol))); + assert_eq!(Fungibles::read(TokenDecimals(TOKEN)), ReadResult::TokenDecimals(decimals)); assert_eq!(Fungibles::read(TokenName(TOKEN)).encode(), Some(Assets::name(TOKEN)).encode()); assert_eq!( Fungibles::read(TokenSymbol(TOKEN)).encode(), @@ -629,15 +533,9 @@ fn token_metadata_works() { #[test] fn token_exists_works() { new_test_ext().execute_with(|| { - assert_eq!( - Fungibles::read(TokenExists(TOKEN)).encode(), - ReadResult::::TokenExists(false).encode() - ); - assert_ok!(Assets::create(signed(ALICE), TOKEN, account(ALICE), 1)); - assert_eq!( - Fungibles::read(TokenExists(TOKEN)).encode(), - ReadResult::::TokenExists(true).encode() - ); + assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(false)); + assert_ok!(Assets::create(signed(ALICE), TOKEN, ALICE, 1)); + assert_eq!(Fungibles::read(TokenExists(TOKEN)), ReadResult::TokenExists(true)); assert_eq!( Fungibles::read(TokenExists(TOKEN)).encode(), Assets::asset_exists(TOKEN).encode(), @@ -645,8 +543,8 @@ fn token_exists_works() { }); } -fn signed(account_id: u8) -> RuntimeOrigin { - RuntimeOrigin::signed(account(account_id)) +fn signed(account: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account) } fn root() -> RuntimeOrigin { @@ -661,31 +559,36 @@ fn none() -> RuntimeOrigin { mod assets { use super::*; - pub(super) fn create_and_mint_to(owner: u8, token: TokenId, to: u8, value: Balance) { - assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); - assert_ok!(Assets::mint(signed(owner), token, account(to), value)); + pub(super) fn create_and_mint_to( + owner: AccountId, + token: TokenId, + to: AccountId, + value: Balance, + ) { + assert_ok!(Assets::create(signed(owner), token, owner, 1)); + assert_ok!(Assets::mint(signed(owner), token, to, value)); } pub(super) fn create_mint_and_approve( - owner: u8, + owner: AccountId, token: TokenId, - to: u8, + to: AccountId, mint: Balance, - spender: u8, + spender: AccountId, approve: Balance, ) { create_and_mint_to(owner, token, to, mint); - assert_ok!(Assets::approve_transfer(signed(to), token, account(spender), approve,)); + assert_ok!(Assets::approve_transfer(signed(to), token, spender, approve,)); } pub(super) fn create_and_set_metadata( - owner: u8, + owner: AccountId, token: TokenId, name: Vec, symbol: Vec, decimals: u8, ) { - assert_ok!(Assets::create(signed(owner), token, account(owner), 1)); + assert_ok!(Assets::create(signed(owner), token, owner, 1)); assert_ok!(Assets::set_metadata(signed(owner), token, name, symbol, decimals)); } } @@ -710,11 +613,11 @@ mod read_weights { fn new() -> Self { Self { total_supply: Fungibles::weight(&TotalSupply(TOKEN)), - balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: account(ALICE) }), + balance_of: Fungibles::weight(&BalanceOf { token: TOKEN, owner: ALICE }), allowance: Fungibles::weight(&Allowance { token: TOKEN, - owner: account(ALICE), - spender: account(BOB), + owner: ALICE, + spender: BOB, }), token_name: Fungibles::weight(&TokenName(TOKEN)), token_symbol: Fungibles::weight(&TokenSymbol(TOKEN)), @@ -796,15 +699,15 @@ mod ensure_codec_indexes { [ (TotalSupply::(Default::default()), 0u8, "TotalSupply"), ( - BalanceOf:: { token: Default::default(), owner: account(Default::default()) }, + BalanceOf:: { token: Default::default(), owner: Default::default() }, 1, "BalanceOf", ), ( Allowance:: { token: Default::default(), - owner: account(Default::default()), - spender: account(Default::default()), + owner: Default::default(), + spender: Default::default(), }, 2, "Allowance", @@ -828,7 +731,7 @@ mod ensure_codec_indexes { ( transfer { token: Default::default(), - to: account(Default::default()), + to: Default::default(), value: Default::default(), }, 3u8, @@ -837,8 +740,8 @@ mod ensure_codec_indexes { ( transfer_from { token: Default::default(), - from: account(Default::default()), - to: account(Default::default()), + from: Default::default(), + to: Default::default(), value: Default::default(), }, 4, @@ -847,7 +750,7 @@ mod ensure_codec_indexes { ( approve { token: Default::default(), - spender: account(Default::default()), + spender: Default::default(), value: Default::default(), }, 5, @@ -856,7 +759,7 @@ mod ensure_codec_indexes { ( increase_allowance { token: Default::default(), - spender: account(Default::default()), + spender: Default::default(), value: Default::default(), }, 6, @@ -865,7 +768,7 @@ mod ensure_codec_indexes { ( decrease_allowance { token: Default::default(), - spender: account(Default::default()), + spender: Default::default(), value: Default::default(), }, 7, @@ -874,7 +777,7 @@ mod ensure_codec_indexes { ( create { id: Default::default(), - admin: account(Default::default()), + admin: Default::default(), min_balance: Default::default(), }, 11, @@ -895,7 +798,7 @@ mod ensure_codec_indexes { ( mint { token: Default::default(), - account: account(Default::default()), + account: Default::default(), value: Default::default(), }, 19, @@ -904,7 +807,7 @@ mod ensure_codec_indexes { ( burn { token: Default::default(), - account: account(Default::default()), + account: Default::default(), value: Default::default(), }, 20, diff --git a/pallets/api/src/messaging/mod.rs b/pallets/api/src/messaging/mod.rs index 77b9dd17..7f340bd6 100644 --- a/pallets/api/src/messaging/mod.rs +++ b/pallets/api/src/messaging/mod.rs @@ -322,7 +322,7 @@ pub enum Read { } #[derive(Debug)] -#[cfg_attr(feature = "std", derive(Encode, Clone))] +#[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { Poll(Option), Get(Option>), diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index 920d590f..8a4ad27d 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -1,28 +1,28 @@ +use codec::{Decode, Encode}; use frame_support::{ derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Everything}, }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_nfts::PalletFeatures; +use scale_info::TypeInfo; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, - BuildStorage, MultiSignature, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Lazy, Verify}, + BuildStorage, }; -pub(crate) const ALICE: u8 = 1; -pub(crate) const BOB: u8 = 2; -pub(crate) const CHARLIE: u8 = 3; +pub(crate) const ALICE: AccountId = 1; +pub(crate) const BOB: AccountId = 2; +pub(crate) const CHARLIE: AccountId = 3; pub(crate) const INIT_AMOUNT: Balance = 100_000_000 * UNIT; pub(crate) const UNIT: Balance = 10_000_000_000; type Block = frame_system::mocking::MockBlock; -pub(crate) type AccountId = ::AccountId; +pub(crate) type AccountId = u64; pub(crate) type Balance = u128; // For terminology in tests. pub(crate) type TokenId = u32; -type Signature = MultiSignature; -type AccountPublic = ::Signer; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -99,7 +99,7 @@ impl pallet_assets::Config for Test { type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; type Extra = (); - type ForceOrigin = EnsureRoot; + type ForceOrigin = EnsureRoot; type Freezer = (); type MetadataDepositBase = ConstU128<1>; type MetadataDepositPerByte = ConstU128<1>; @@ -119,12 +119,35 @@ parameter_types! { pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); } +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub struct Noop; + +impl IdentifyAccount for Noop { + type AccountId = AccountId; + + fn into_account(self) -> Self::AccountId { + 0 + } +} + +impl Verify for Noop { + type Signer = Noop; + + fn verify>( + &self, + _msg: L, + _signer: &::AccountId, + ) -> bool { + false + } +} + impl pallet_nfts::Config for Test { type ApprovalsLimit = ConstU32<10>; type AttributeDepositBase = ConstU128<1>; type CollectionDeposit = ConstU128<2>; type CollectionId = u32; - type CreateOrigin = AsEnsureOriginWithArg>; + type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; type DepositPerByte = ConstU128<1>; type Features = Features; @@ -140,11 +163,8 @@ impl pallet_nfts::Config for Test { type MaxDeadlineDuration = ConstU64<10000>; type MaxTips = ConstU32<10>; type MetadataDepositBase = ConstU128<1>; - /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. - type OffchainPublic = AccountPublic; - /// Off-chain = signature On-chain - therefore no conversion needed. - /// It needs to be From for benchmarking. - type OffchainSignature = Signature; + type OffchainPublic = Noop; + type OffchainSignature = Noop; type RuntimeEvent = RuntimeEvent; type StringLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; @@ -155,22 +175,13 @@ impl crate::nonfungibles::Config for Test { type RuntimeEvent = RuntimeEvent; } -/// Initialize a new account ID. -pub(crate) fn account(id: u8) -> AccountId { - [id; 32].into() -} - pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() .expect("Frame system builds valid default genesis config"); pallet_balances::GenesisConfig:: { - balances: vec![ - (account(ALICE), INIT_AMOUNT), - (account(BOB), INIT_AMOUNT), - (account(CHARLIE), INIT_AMOUNT), - ], + balances: vec![(ALICE, INIT_AMOUNT), (BOB, INIT_AMOUNT), (CHARLIE, INIT_AMOUNT)], } .assimilate_storage(&mut t) .expect("Pallet balances storage can be assimilated"); diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 0c7985aa..8d3f8db2 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -17,21 +17,20 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_nfts::{ CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, - ItemMetadataOf, MintSettings, MintWitness, + MintSettings, MintWitness, }; use sp_runtime::BoundedVec; use sp_std::vec::Vec; use types::{ - AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionDetailsFor, CollectionIdOf, - CreateCollectionConfigFor, ItemIdOf, ItemPriceOf, NextCollectionIdOf, NftsOf, - NftsWeightInfoOf, + AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionConfigFor, CollectionDetailsFor, + CollectionIdOf, ItemIdOf, ItemPriceOf, NextCollectionIdOf, NftsOf, NftsWeightInfoOf, }; use super::*; /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(Clone))] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { @@ -72,7 +71,7 @@ pub mod pallet { /// Results of state reads for the non-fungibles API. #[derive(Debug)] - #[cfg_attr(feature = "std", derive(Encode, Clone))] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { /// Total item supply of a collection. TotalSupply(u128), @@ -83,13 +82,13 @@ pub mod pallet { /// Owner of a specified collection owner. OwnerOf(Option>), /// Attribute value of a collection item. - GetAttribute(Option>), + GetAttribute(Option>), /// Details of a collection. Collection(Option>), /// Next collection ID. NextCollectionId(Option>), /// Collection item metadata. - ItemMetadata(Option>), + ItemMetadata(Option>), } impl ReadResult { @@ -217,24 +216,12 @@ pub mod pallet { #[pallet::weight(NftsWeightInfoOf::::create())] pub fn create( origin: OriginFor, + id: CollectionIdOf, admin: AccountIdOf, - config: CreateCollectionConfigFor, + config: CollectionConfigFor, ) -> DispatchResult { - let id = NextCollectionIdOf::::get() - .or(T::CollectionId::initial_value()) - .ok_or(pallet_nfts::Error::::UnknownCollection)?; let creator = ensure_signed(origin.clone())?; - let collection_config = CollectionConfig { - settings: CollectionSettings::all_enabled(), - max_supply: config.max_supply, - mint_settings: MintSettings { - mint_type: config.mint_type, - start_block: config.start_block, - end_block: config.end_block, - ..MintSettings::default() - }, - }; - NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), collection_config)?; + NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), config)?; Self::deposit_event(Event::Created { id, admin, creator }); Ok(()) } @@ -350,16 +337,16 @@ pub mod pallet { to: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf, - mint_price: Option>, + witness: MintWitness, ItemPriceOf>, ) -> DispatchResult { let account = ensure_signed(origin.clone())?; - let witness_data = MintWitness { mint_price, owned_item: Some(item) }; + let mint_price = witness.mint_price; NftsOf::::mint( origin, collection, item, T::Lookup::unlookup(to.clone()), - Some(witness_data), + Some(witness), )?; Self::deposit_event(Event::Transfer { collection, @@ -425,12 +412,12 @@ pub mod pallet { ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( pallet_nfts::Attribute::::get((collection, Some(item), namespace, key)) - .map(|attribute| attribute.0), + .map(|attribute| attribute.0.into()), ), Collection(collection) => ReadResult::Collection(pallet_nfts::Collection::::get(collection)), ItemMetadata { collection, item } => ReadResult::ItemMetadata( - ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data), + NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), ), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index a9545f0c..803e8cf8 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -3,14 +3,14 @@ use frame_support::{assert_noop, assert_ok, traits::Incrementable}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_nfts::{ AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, DestroyWitness, - MintSettings, + MintSettings, MintWitness, }; use sp_runtime::{BoundedVec, DispatchError::BadOrigin}; use super::types::{CollectionIdOf, ItemIdOf}; use crate::{ mock::*, - nonfungibles::{CreateCollectionConfig, Event, Read::*, ReadResult}, + nonfungibles::{Event, Read::*, ReadResult}, Read, }; @@ -41,7 +41,7 @@ mod encoding_read_result { #[test] fn owner_of() { - let mut owner = Some(account(ALICE)); + let mut owner = Some(ALICE); assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); owner = None; assert_eq!(ReadResult::OwnerOf::(owner.clone()).encode(), owner.encode()); @@ -49,7 +49,7 @@ mod encoding_read_result { #[test] fn get_attribute() { - let mut attribute = Some(BoundedVec::truncate_from("some attribute".as_bytes().to_vec())); + let mut attribute = Some("some attribute".as_bytes().to_vec()); assert_eq!( ReadResult::GetAttribute::(attribute.clone()).encode(), attribute.encode() @@ -64,7 +64,7 @@ mod encoding_read_result { #[test] fn collection() { let mut collection_details = Some(CollectionDetails { - owner: account(ALICE), + owner: ALICE, owner_deposit: 0, items: 0, item_metadatas: 0, @@ -98,7 +98,7 @@ mod encoding_read_result { #[test] fn item_metadata_works() { - let mut data = Some(BoundedVec::truncate_from("some metadata".as_bytes().to_vec())); + let mut data = Some("some metadata".as_bytes().to_vec()); assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); data = None; assert_eq!(ReadResult::ItemMetadata::(data.clone()).encode(), data.encode()); @@ -113,26 +113,17 @@ fn transfer() { let (collection, item) = nfts::create_collection_mint(owner, ITEM); for origin in vec![root(), none()] { - assert_noop!( - NonFungibles::transfer(origin, collection, item, account(dest)), - BadOrigin - ); + assert_noop!(NonFungibles::transfer(origin, collection, item, dest), BadOrigin); } // Successfully burn an existing new collection item. - let balance_before_transfer = AccountBalance::::get(collection, &account(dest)); - assert_ok!(NonFungibles::transfer(signed(owner), collection, ITEM, account(dest))); - let balance_after_transfer = AccountBalance::::get(collection, &account(dest)); - assert_eq!(AccountBalance::::get(collection, &account(owner)), 0); + let balance_before_transfer = AccountBalance::::get(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(owner), collection, ITEM, dest)); + let balance_after_transfer = AccountBalance::::get(collection, &dest); + assert_eq!(AccountBalance::::get(collection, &owner), 0); assert_eq!(balance_after_transfer - balance_before_transfer, 1); System::assert_last_event( - Event::Transfer { - collection, - item, - from: Some(account(owner)), - to: Some(account(dest)), - price: None, - } - .into(), + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + .into(), ); }); } @@ -144,20 +135,20 @@ fn mint_works() { let collection = nfts::create_collection(owner); // Successfully mint a new collection item. - let balance_before_mint = AccountBalance::::get(collection, account(owner)); - assert_ok!(NonFungibles::mint(signed(owner), account(owner), collection, ITEM, None)); - let balance_after_mint = AccountBalance::::get(collection, account(owner)); + let balance_before_mint = AccountBalance::::get(collection, owner); + assert_ok!(NonFungibles::mint( + signed(owner), + owner, + collection, + ITEM, + MintWitness { mint_price: None, owned_item: None } + )); + let balance_after_mint = AccountBalance::::get(collection, owner); assert_eq!(balance_after_mint, 1); assert_eq!(balance_after_mint - balance_before_mint, 1); System::assert_last_event( - Event::Transfer { - collection, - item: ITEM, - from: None, - to: Some(account(owner)), - price: None, - } - .into(), + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } + .into(), ); }); } @@ -171,8 +162,7 @@ fn burn_works() { let (collection, item) = nfts::create_collection_mint(owner, ITEM); assert_ok!(NonFungibles::burn(signed(owner), collection, ITEM)); System::assert_last_event( - Event::Transfer { collection, item, from: Some(account(owner)), to: None, price: None } - .into(), + Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), ); }); } @@ -184,25 +174,13 @@ fn approve_works() { let operator = BOB; let (collection, item) = nfts::create_collection_mint(owner, ITEM); // Successfully approve `oeprator` to transfer the collection item. - assert_ok!(NonFungibles::approve( - signed(owner), - collection, - Some(item), - account(operator), - true - )); + assert_ok!(NonFungibles::approve(signed(owner), collection, Some(item), operator, true)); System::assert_last_event( - Event::Approval { - collection, - item: Some(item), - owner: account(owner), - operator: account(operator), - approved: true, - } - .into(), + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), ); // Successfully transfer the item by the delegated account `operator`. - assert_ok!(Nfts::transfer(signed(operator), collection, item, account(operator))); + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); }); } @@ -213,16 +191,10 @@ fn cancel_approval_works() { let operator = BOB; let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); // Successfully cancel the transfer approval of `operator` by `owner`. - assert_ok!(NonFungibles::approve( - signed(owner), - collection, - Some(item), - account(operator), - false - )); + assert_ok!(NonFungibles::approve(signed(owner), collection, Some(item), operator, false)); // Failed to transfer the item by `operator` without permission. assert_noop!( - Nfts::transfer(signed(operator), collection, item, account(operator)), + Nfts::transfer(signed(operator), collection, item, operator), NftsError::NoPermission ); }); @@ -235,10 +207,10 @@ fn set_max_supply_works() { let collection = nfts::create_collection(owner); assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, 10)); (0..10).into_iter().for_each(|i| { - assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); }); assert_noop!( - Nfts::mint(signed(owner), collection, 42, account(owner), None), + Nfts::mint(signed(owner), collection, 42, owner, None), NftsError::MaxSupplyReached ); }); @@ -371,12 +343,12 @@ fn approve_item_attribute_works() { let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); // Successfully approve delegate to set attributes. - assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, BOB)); assert_ok!(Nfts::set_attribute( signed(BOB), collection, Some(item), - pallet_nfts::AttributeNamespace::Account(account(BOB)), + pallet_nfts::AttributeNamespace::Account(BOB), attribute.clone(), value.clone() )); @@ -385,7 +357,7 @@ fn approve_item_attribute_works() { NonFungibles::read(GetAttribute { collection, item, - namespace: pallet_nfts::AttributeNamespace::Account(account(BOB)), + namespace: pallet_nfts::AttributeNamespace::Account(BOB), key: attribute }) .encode(), @@ -402,12 +374,12 @@ fn cancel_item_attribute_approval_works() { let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); // Successfully approve delegate to set attributes. - assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, account(BOB))); + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, BOB)); assert_ok!(Nfts::set_attribute( signed(BOB), collection, Some(item), - pallet_nfts::AttributeNamespace::Account(account(BOB)), + pallet_nfts::AttributeNamespace::Account(BOB), attribute.clone(), value.clone() )); @@ -415,7 +387,7 @@ fn cancel_item_attribute_approval_works() { signed(ALICE), collection, item, - account(BOB), + BOB, pallet_nfts::CancelAttributesApprovalWitness { account_attributes: 1 } )); assert_noop!( @@ -423,7 +395,7 @@ fn cancel_item_attribute_approval_works() { signed(BOB), collection, Some(item), - pallet_nfts::AttributeNamespace::Account(account(BOB)), + pallet_nfts::AttributeNamespace::Account(BOB), attribute.clone(), value.clone() ), @@ -452,7 +424,7 @@ fn total_supply_works() { let owner = ALICE; let collection = nfts::create_collection(owner); (0..10).into_iter().for_each(|i| { - assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); assert_eq!( NonFungibles::read(TotalSupply(collection)).encode(), ((i + 1) as u128).encode() @@ -472,16 +444,15 @@ fn create_works() { let next_collection_id = pallet_nfts::NextCollectionId::::get().unwrap_or_default(); assert_ok!(NonFungibles::create( signed(owner), - account(owner), - CreateCollectionConfig { + next_collection_id, + owner, + CollectionConfig { max_supply: None, - mint_type: pallet_nfts::MintType::Public, - price: None, - start_block: None, - end_block: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled() }, )); - assert_eq!(Nfts::collection_owner(next_collection_id), Some(account(owner))); + assert_eq!(Nfts::collection_owner(next_collection_id), Some(owner)); }); } @@ -515,14 +486,14 @@ fn balance_of_works() { let owner = ALICE; let collection = nfts::create_collection(owner); (0..10).into_iter().for_each(|i| { - assert_ok!(Nfts::mint(signed(owner), collection, i, account(owner), None)); + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), + NonFungibles::read(BalanceOf { collection, owner }).encode(), (i + 1).encode() ); assert_eq!( - NonFungibles::read(BalanceOf { collection, owner: account(owner) }).encode(), - AccountBalance::::get(collection, account(owner)).encode() + NonFungibles::read(BalanceOf { collection, owner }).encode(), + AccountBalance::::get(collection, owner).encode() ); }); }); @@ -535,32 +506,22 @@ fn allowance_works() { let operator = BOB; let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); assert_eq!( - NonFungibles::read(Allowance { - collection, - item: Some(item), - owner: account(owner), - operator: account(operator), - }) - .encode(), + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) + .encode(), true.encode() ); assert_eq!( - NonFungibles::read(Allowance { - collection, - item: Some(item), - owner: account(owner), - operator: account(operator), - }) - .encode(), - Nfts::check_allowance(&collection, &Some(item), &account(owner), &account(operator)) + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) + .encode(), + Nfts::check_allowance(&collection, &Some(item), &owner, &operator) .is_ok() .encode() ); }); } -fn signed(account_id: u8) -> RuntimeOrigin { - RuntimeOrigin::signed(account(account_id)) +fn signed(account_id: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account_id) } fn root() -> RuntimeOrigin { @@ -575,32 +536,26 @@ mod nfts { use super::*; pub(super) fn create_collection_mint_and_approve( - owner: u8, + owner: AccountId, item: ItemIdOf, - operator: u8, + operator: AccountId, ) -> (u32, u32) { let (collection, item) = create_collection_mint(owner, item); - assert_ok!(Nfts::approve_transfer( - signed(owner), - collection, - Some(item), - account(operator), - None - )); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), operator, None)); (collection, item) } - pub(super) fn create_collection_mint(owner: u8, item: ItemIdOf) -> (u32, u32) { + pub(super) fn create_collection_mint(owner: AccountId, item: ItemIdOf) -> (u32, u32) { let collection = create_collection(owner); - assert_ok!(Nfts::mint(signed(owner), collection, item, account(owner), None)); + assert_ok!(Nfts::mint(signed(owner), collection, item, owner, None)); (collection, item) } - pub(super) fn create_collection(owner: u8) -> u32 { + pub(super) fn create_collection(owner: AccountId) -> u32 { let next_id = next_collection_id(); assert_ok!(Nfts::create( signed(owner), - account(owner), + owner, collection_config_with_all_settings_enabled() )); next_id diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs index be6d47e6..05ff7d98 100644 --- a/pallets/api/src/nonfungibles/types.rs +++ b/pallets/api/src/nonfungibles/types.rs @@ -1,9 +1,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; use frame_system::pallet_prelude::BlockNumberFor; -pub use pallet_nfts::{AttributeNamespace, CollectionDetails, ItemDeposit, ItemDetails, MintType}; +pub use pallet_nfts::{ + AttributeNamespace, CollectionConfig, CollectionDetails, CollectionSetting, CollectionSettings, + DestroyWitness, ItemDeposit, ItemDetails, ItemSetting, MintSettings, MintType, MintWitness, +}; use scale_info::TypeInfo; -use sp_runtime::{BoundedBTreeMap, RuntimeDebug}; +use sp_runtime::RuntimeDebug; // Type aliases for pallet-nfts. pub(super) type NftsOf = pallet_nfts::Pallet; @@ -18,27 +21,10 @@ pub(super) type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; pub(super) type ItemIdOf = as Inspect<::AccountId>>::ItemId; -pub(super) type ApprovalsOf = BoundedBTreeMap< - AccountIdOf, - Option>, - ::ApprovalsLimit, ->; pub(super) type ItemPriceOf = BalanceOf; // TODO: Multi-instances. -pub(super) type ItemDepositOf = ItemDeposit, AccountIdOf>; pub(super) type CollectionDetailsFor = CollectionDetails, BalanceOf>; -pub(super) type ItemDetailsFor = - ItemDetails, ItemDepositOf, ApprovalsOf>; pub(super) type AttributeNamespaceOf = AttributeNamespace>; -pub(super) type CreateCollectionConfigFor = - CreateCollectionConfig, BlockNumberFor, CollectionIdOf>; - -#[derive(Clone, Copy, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct CreateCollectionConfig { - pub max_supply: Option, - pub mint_type: MintType, - pub price: Option, - pub start_block: Option, - pub end_block: Option, -} +pub(super) type CollectionConfigFor = + CollectionConfig, BlockNumberFor, CollectionIdOf>; diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index 1fcfe9dd..7ecbf385 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -41,6 +41,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-io/std", diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index 6fe483f1..89de1f05 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -39,6 +39,14 @@ impl, I: 'static> Pallet { Collection::::get(collection).map(|i| i.items) } + /// Get the metadata of the collection item. + pub fn item_metadata( + collection: T::CollectionId, + item: T::ItemId, + ) -> Option> { + ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data) + } + /// Validates the signature of the given data with the provided signer's account ID. /// /// # Errors diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 6d71c1a2..e1e79ef4 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -180,24 +180,26 @@ impl, I: 'static> Pallet { Self::is_pallet_feature_enabled(PalletFeature::Approvals), Error::::MethodDisabled ); - if !Collection::::contains_key(collection) { - return Err(Error::::UnknownCollection.into()); - } + let owner = Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + let collection_config = Self::get_collection_config(&collection)?; ensure!( collection_config.is_setting_enabled(CollectionSetting::TransferableItems), Error::::ItemsNonTransferable ); - let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; - Allowances::::mutate((&collection, &origin, &delegate), |allowance| { + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == owner, Error::::NoPermission); + } + + Allowances::::mutate((&collection, &owner, &delegate), |allowance| { *allowance = true; }); Self::deposit_event(Event::TransferApproved { collection, item: None, - owner: origin, + owner, delegate, deadline: None, }); @@ -209,19 +211,14 @@ impl, I: 'static> Pallet { collection: T::CollectionId, delegate: T::AccountId, ) -> DispatchResult { - if !Collection::::contains_key(collection) { - return Err(Error::::UnknownCollection.into()); - } + let owner = Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; - let origin = maybe_check_origin.ok_or(Error::::WrongOrigin)?; - Allowances::::remove((&collection, &origin, &delegate)); + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == owner, Error::::NoPermission); + } + Allowances::::remove((&collection, &owner, &delegate)); - Self::deposit_event(Event::ApprovalCancelled { - collection, - owner: origin, - item: None, - delegate, - }); + Self::deposit_event(Event::ApprovalCancelled { collection, owner, item: None, delegate }); Ok(()) } diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 397a715c..4d0f08c9 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -318,7 +318,7 @@ fn destroy_should_work() { assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_ok!(Nfts::approve_transfer( - RuntimeOrigin::signed(account(2)), + RuntimeOrigin::signed(account(1)), 0, None, account(3), @@ -2017,22 +2017,22 @@ fn cancel_approval_collection_works_with_admin() { )); assert_ok!(Nfts::approve_transfer( - RuntimeOrigin::signed(account(2)), + RuntimeOrigin::signed(account(1)), 0, None, account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, None, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 1, None, account(3)), Error::::UnknownCollection ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, None, account(3))); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, None, account(3))); assert!(events().contains(&Event::::ApprovalCancelled { collection: 0, item: None, - owner: account(2), + owner: account(1), delegate: account(3) })); assert_eq!(Allowances::::get((0, account(2), account(3))), false); @@ -2156,7 +2156,7 @@ fn approval_collection_works_with_admin() { RuntimeOrigin::signed(account(1)), 0, 42, - account(2), + account(1), default_item_config() )); @@ -2178,7 +2178,7 @@ fn approval_collection_works_with_admin() { ); assert_ok!(Nfts::approve_transfer( - RuntimeOrigin::signed(account(2)), + RuntimeOrigin::signed(account(1)), 0, None, account(3), @@ -2187,11 +2187,11 @@ fn approval_collection_works_with_admin() { assert!(events().contains(&Event::::TransferApproved { collection: 0, item: None, - owner: account(2), + owner: account(1), delegate: account(3), deadline: None })); - assert_eq!(Allowances::::get((0, account(2), account(3))), true); + assert_eq!(Allowances::::get((0, account(1), account(3))), true); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); }); } diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 8a5153c3..941da6ca 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -145,21 +145,21 @@ pub struct MintWitness { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ItemDetails { /// The owner of this item. - pub owner: AccountId, + pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. - pub approvals: Approvals, + 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 deposit: Deposit, + 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 account: AccountId, + pub(super) account: AccountId, /// An amount that gets reserved. - pub amount: DepositBalance, + pub(super) amount: DepositBalance, } /// Information about the collection's metadata. @@ -184,11 +184,11 @@ pub struct ItemMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. - pub deposit: Deposit, + pub(super) deposit: Deposit, /// General information concerning this item. Limited in length by `StringLimit`. This will /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. - pub data: BoundedVec, + pub(super) data: BoundedVec, } /// Information about the tip. diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 48932cf0..b66fc4e8 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -6,6 +6,8 @@ name = "pop-api" version = "0.0.0" [dependencies] +bitflags = { version = "1.3.2" } +enumflags2 = "0.7.9" ink = { git = "https://github.com/r0gue-io/ink", branch = "sub0", default-features = false } polkavm-derive = "0.11.0" pop-primitives = { path = "../primitives", default-features = false } diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs index 5cdc85f6..201f8fd9 100644 --- a/pop-api/integration-tests/contracts/nonfungibles/lib.rs +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -9,8 +9,8 @@ use pop_api::{ nonfungibles::{ self as api, events::{Approval, AttributeSet, Transfer}, - AttributeNamespace, CancelAttributesApprovalWitness, CollectionDetails, CollectionId, - CreateCollectionConfig, DestroyWitness, ItemId, + AttributeNamespace, CancelAttributesApprovalWitness, CollectionConfig, CollectionDetails, + CollectionId, DestroyWitness, ItemId, MintWitness, }, StatusCode, }; @@ -134,12 +134,11 @@ mod nonfungibles { #[ink(message)] pub fn create( &mut self, + id: CollectionId, admin: AccountId, - config: CreateCollectionConfig, - ) -> Result { - let next_collection_id = api::next_collection_id(); - api::create(admin, config)?; - next_collection_id + config: CollectionConfig, + ) -> Result<()> { + api::create(id, admin, config) } #[ink(message)] @@ -237,9 +236,9 @@ mod nonfungibles { to: AccountId, collection: CollectionId, item: ItemId, - mint_price: Option, + witness: MintWitness, ) -> Result<()> { - api::mint(to, collection, item, mint_price) + api::mint(to, collection, item, witness) } #[ink(message)] diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs index 5e34428c..bcf75566 100644 --- a/pop-api/integration-tests/src/nonfungibles/mod.rs +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -1,5 +1,4 @@ use frame_support::BoundedVec; -use pallet_nfts::CollectionConfig; use pop_api::{ nonfungibles::{ events::{Approval, AttributeSet, Transfer}, @@ -318,11 +317,7 @@ fn set_metadata_works() { let (collection, item) = nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); assert_ok!(set_metadata(&addr.clone(), collection, item, vec![])); - assert_eq!( - pallet_nfts::ItemMetadataOf::::get(collection, item) - .map(|metadata| metadata.data), - Some(MetadataData::default()) - ); + assert_eq!(Nfts::item_metadata(collection, item), Some(MetadataData::default())); }); } @@ -341,11 +336,7 @@ fn clear_metadata_works() { MetadataData::default() )); assert_ok!(clear_metadata(&addr.clone(), collection, item)); - assert_eq!( - pallet_nfts::ItemMetadataOf::::get(collection, item) - .map(|metadata| metadata.data), - None - ); + assert_eq!(Nfts::item_metadata(collection, item), None); }); } @@ -358,13 +349,12 @@ fn create_works() { let collection = nfts::next_collection_id(); assert_ok!(create( &addr.clone(), + COLLECTION_ID, account_id.clone(), - CreateCollectionConfig { + CollectionConfig { max_supply: Some(100), - mint_type: MintType::Public, - price: None, - start_block: None, - end_block: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), } )); assert_eq!( @@ -426,3 +416,35 @@ fn set_max_supply_works() { .is_err()); }); } + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + let value = 10; + + let collection = nfts::create_collection(&account_id, &account_id); + assert_ok!(mint( + &addr.clone(), + ALICE, + collection, + ITEM_ID, + MintWitness { mint_price: None, owned_item: None } + )); + assert_eq!(nfts::balance_of(COLLECTION_ID, ALICE), 1); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(burn(&addr.clone(), collection, ITEM_ID,)); + assert_eq!(nfts::balance_of(COLLECTION_ID, account_id), 0); + }); +} diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs index 0a581636..53d44ea1 100644 --- a/pop-api/integration-tests/src/nonfungibles/utils.rs +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -1,6 +1,7 @@ +use pallet_revive::AddressMapper; + use super::*; use crate::sp_core::H160; -use pallet_revive::AddressMapper; pub(super) type AttributeKey = BoundedVec::KeyLimit>; pub(super) type AttributeValue = BoundedVec::ValueLimit>; @@ -106,10 +107,11 @@ pub(super) fn get_attribute( pub(super) fn create( addr: &H160, + id: CollectionId, admin: AccountId32, - config: CreateCollectionConfig, + config: CollectionConfig, ) -> Result<(), Error> { - let params = [admin.encode(), config.encode()].concat(); + let params = [id.encode(), admin.encode(), config.encode()].concat(); let result = do_bare_call("create", &addr, params); decoded::>(result.clone()) .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) @@ -240,6 +242,26 @@ pub(super) fn item_metadata( .map(|value| value.map(|v| v.to_vec())) } +pub(super) fn mint( + addr: &H160, + to: AccountId32, + collection: CollectionId, + item: ItemId, + witness: MintWitness, +) -> Result<(), Error> { + let params = [to.encode(), collection.encode(), item.encode(), witness.encode()].concat(); + let result = do_bare_call("mint", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn burn(addr: &H160, collection: CollectionId, item: ItemId) -> Result<(), Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("burn", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + pub(super) mod nfts { use super::*; @@ -307,8 +329,8 @@ pub(super) mod nfts { } pub(super) fn collection_config_with_all_settings_enabled( - ) -> CollectionConfig { - CollectionConfig { + ) -> pallet_nfts::CollectionConfig { + pallet_nfts::CollectionConfig { settings: pallet_nfts::CollectionSettings::all_enabled(), max_supply: None, mint_settings: pallet_nfts::MintSettings::default(), diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 88a3880b..d11f3710 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -12,6 +12,8 @@ use constants::DECODING_FAILED; use ink::env::chain_extension::{ChainExtensionMethod, FromStatusCode}; pub use v0::*; +/// Module providing macros. +pub mod macros; /// Module providing primitives types. pub mod primitives; /// The first version of the API. diff --git a/pop-api/src/macros.rs b/pop-api/src/macros.rs new file mode 100644 index 00000000..7a9fd34d --- /dev/null +++ b/pop-api/src/macros.rs @@ -0,0 +1,53 @@ +/// Implements encoding and decoding traits for a wrapper type that represents +/// bitflags. The wrapper type should contain a field of type `$size`, where +/// `$size` is an integer type (e.g., u8, u16, u32) that can represent the bitflags. +/// The `$bitflag_enum` type is the enumeration type that defines the individual bitflags. +/// +/// This macro provides implementations for the following traits: +/// - `MaxEncodedLen`: Calculates the maximum encoded length for the wrapper type. +/// - `Encode`: Encodes the wrapper type using the provided encoding function. +/// - `EncodeLike`: Trait indicating the type can be encoded as is. +/// - `Decode`: Decodes the wrapper type from the input. +/// - `TypeInfo`: Provides type information for the wrapper type. +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl ink::scale::MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl ink::scale::Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl ink::scale::EncodeLike for $wrapper {} + impl ink::scale::Decode for $wrapper { + fn decode( + input: &mut I, + ) -> ::core::result::Result { + let field = <$size>::decode(input)?; + Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) + } + } + + #[cfg(feature = "std")] + impl ink::scale_info::TypeInfo for $wrapper { + type Identity = Self; + + fn type_info() -> ink::scale_info::Type { + ink::scale_info::Type::builder() + .path(ink::scale_info::Path::new("BitFlags", module_path!())) + .type_params(vec![ink::scale_info::TypeParameter::new( + "T", + Some(ink::scale_info::meta_type::<$bitflag_enum>()), + )]) + .composite( + ink::scale_info::build::Fields::unnamed() + .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), + ) + } + } + }; +} +pub(crate) use impl_codec_bitflags; diff --git a/pop-api/src/v0/fungibles/errors.rs b/pop-api/src/v0/fungibles/errors.rs index a5c548a6..74083c24 100644 --- a/pop-api/src/v0/fungibles/errors.rs +++ b/pop-api/src/v0/fungibles/errors.rs @@ -1,8 +1,10 @@ -//! A set of errors for use in smart contracts that interact with the fungibles api. This includes errors compliant to standards. +//! A set of errors for use in smart contracts that interact with the fungibles api. This includes +//! errors compliant to standards. -use super::*; use ink::prelude::string::{String, ToString}; +use super::*; + /// Represents various errors related to fungible tokens. /// /// The `FungiblesError` provides a detailed and specific set of error types that can occur when @@ -103,6 +105,11 @@ impl From for PSP22Error { #[cfg(test)] mod tests { + use ink::{ + prelude::string::String, + scale::{Decode, Encode}, + }; + use super::{FungiblesError, PSP22Error}; use crate::{ constants::{ASSETS, BALANCES}, @@ -114,8 +121,6 @@ mod tests { }, StatusCode, }; - use ink::prelude::string::String; - use ink::scale::{Decode, Encode}; fn error_into_status_code(error: Error) -> StatusCode { let mut encoded_error = error.encode(); diff --git a/pop-api/src/v0/fungibles/traits.rs b/pop-api/src/v0/fungibles/traits.rs index 92ad55e3..8a07f2a9 100644 --- a/pop-api/src/v0/fungibles/traits.rs +++ b/pop-api/src/v0/fungibles/traits.rs @@ -1,9 +1,11 @@ //! Traits that can be used by contracts. Including standard compliant traits. -use super::*; use core::result::Result; + use ink::prelude::string::String; +use super::*; + /// The PSP22 trait. #[ink::trait_definition] pub trait Psp22 { diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs index ed2405df..efe185ba 100644 --- a/pop-api/src/v0/nonfungibles/mod.rs +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -102,12 +102,13 @@ pub fn get_attribute( } #[inline] -pub fn create(admin: AccountId, config: CreateCollectionConfig) -> Result<()> { +pub fn create(id: CollectionId, admin: AccountId, config: CollectionConfig) -> Result<()> { build_dispatch(CREATE) - .input::<(AccountId, CreateCollectionConfig)>() + .input::<(CollectionId, AccountId, CollectionConfig)>() .output::, true>() .handle_error_code::() - .call(&(admin, config)) + .call(&(id, admin, config))?; + Ok(()) } #[inline] @@ -225,13 +226,13 @@ pub fn mint( to: AccountId, collection: CollectionId, item: ItemId, - mint_price: Option, + witness: MintWitness, ) -> Result<()> { build_dispatch(MINT) - .input::<(AccountId, CollectionId, ItemId, Option)>() + .input::<(AccountId, CollectionId, ItemId, MintWitness)>() .output::, true>() .handle_error_code::() - .call(&(to, collection, item, mint_price)) + .call(&(to, collection, item, witness)) } #[inline] diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs index 3b0174b9..e7029418 100644 --- a/pop-api/src/v0/nonfungibles/types.rs +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -1,5 +1,7 @@ +use enumflags2::{bitflags, BitFlags}; + use super::*; -use crate::primitives::AccountId; +use crate::{macros::impl_codec_bitflags, primitives::AccountId}; pub type ItemId = u32; pub type CollectionId = u32; @@ -24,16 +26,6 @@ pub struct CollectionDetails { pub attributes: u32, } -#[derive(Debug, PartialEq, Eq)] -#[ink::scale_derive(Encode, Decode, TypeInfo)] -pub struct CreateCollectionConfig { - pub max_supply: Option, - pub mint_type: MintType, - pub price: Option, - pub start_block: Option, - pub end_block: Option, -} - /// Attribute namespaces for non-fungible tokens. #[derive(Debug, PartialEq, Eq)] #[ink::scale_derive(Encode, Decode, TypeInfo)] @@ -49,9 +41,9 @@ pub enum AttributeNamespace { Account(AccountId), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[ink::scale_derive(Encode, Decode, TypeInfo)] -pub enum MintType { +pub enum MintType { /// Only an `Issuer` could mint items. Issuer, /// Anyone could mint items. @@ -81,3 +73,151 @@ pub struct DestroyWitness { #[codec(compact)] pub attributes: u32, } + +/// Witness data for items mint transactions. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owned_item: Option, + /// The price specified in mint settings. + pub mint_price: Option, +} + +/// Collection's configuration. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CollectionConfig { + /// Collection's settings. + pub settings: CollectionSettings, + /// Collection's max supply. + pub max_supply: Option, + /// Default settings each item will get during the mint. + pub mint_settings: MintSettings, +} + +impl CollectionConfig { + pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { + !self.settings.is_disabled(setting) + } + + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { + self.settings.is_disabled(setting) + } + + pub fn enable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.remove(setting); + } + + pub fn disable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 user-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum CollectionSetting { + /// Items in this collection are transferable. + TransferableItems, + /// The metadata of this collection can be modified. + UnlockedMetadata, + /// Attributes of this collection can be modified. + UnlockedAttributes, + /// The supply of this collection can be modified. + UnlockedMaxSupply, + /// When this isn't set then the deposit is required to hold the items of this collection. + DepositRequired, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CollectionSettings(pub BitFlags); + +impl CollectionSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + + 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); + +/// Support for up to 64 user-enabled features on an item. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum ItemSetting { + /// This item is transferable. + Transferable, + /// The metadata of this item can be modified. + UnlockedMetadata, + /// Attributes of this item can be modified. + UnlockedAttributes, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ItemSettings(pub BitFlags); + +impl ItemSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + + 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); + +/// Holds the information about minting. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct MintSettings { + /// Whether anyone can mint or if minters are restricted to some subset. + pub mint_type: MintType, + /// An optional price per mint. + pub price: Option, + /// When the mint starts. + pub start_block: Option, + /// When the mint ends. + pub end_block: Option, + /// Default settings each item will get during the mint. + pub 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(), + } + } +} diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 10bcbc1e..2677626c 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use codec::{Decode, Encode}; +use codec::Decode; use cumulus_primitives_core::Weight; use frame_support::traits::{ConstU32, Contains}; pub(crate) use pallet_api::Extension; @@ -28,7 +28,7 @@ type DecodesAs = pallet_api::extension::DecodesAs< /// A query of runtime state. #[derive(Decode, Debug)] -#[cfg_attr(test, derive(Encode, Clone))] +#[cfg_attr(test, derive(PartialEq, Clone))] #[repr(u8)] pub enum RuntimeRead { /// Fungible token queries. @@ -37,7 +37,7 @@ pub enum RuntimeRead { /// Messaging state queries. #[codec(index = 151)] Messaging(messaging::Read), - // Non-fungible token queries. + /// Non-fungible token queries. #[codec(index = 154)] NonFungibles(nonfungibles::Read), } @@ -69,7 +69,7 @@ impl Readable for RuntimeRead { /// The result of a runtime state read. #[derive(Debug)] -#[cfg_attr(test, derive(Encode, Clone))] +#[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum RuntimeResult { /// Fungible token read results. Fungibles(fungibles::ReadResult), @@ -278,6 +278,8 @@ impl Contains for Filter { #[cfg(test)] mod tests { use codec::Encode; + use pallet_api::fungibles::Call::*; + use pallet_nfts::MintWitness; use sp_core::{bounded_vec, crypto::AccountId32}; use RuntimeCall::{Balances, Fungibles, NonFungibles}; @@ -301,7 +303,7 @@ mod tests { use pallet_balances::{AdjustmentDirection, Call::*}; use sp_runtime::MultiAddress; - for call in vec![ + const CALLS: [RuntimeCall; 4] = [ Balances(force_adjust_total_issuance { direction: AdjustmentDirection::Increase, delta: 0, @@ -313,18 +315,16 @@ mod tests { value: 0, }), Balances(force_unreserve { who: MultiAddress::Address32([0u8; 32]), amount: 0 }), - ] - .iter() - { - assert!(!Filter::::contains(call)) + ]; + + for call in CALLS { + assert!(!Filter::::contains(&call)) } } #[test] fn filter_allows_fungibles_calls() { - use pallet_api::fungibles::Call::*; - - for call in vec![ + const CALLS: [RuntimeCall; 11] = [ Fungibles(transfer { token: 0, to: ACCOUNT, value: 0 }), Fungibles(transfer_from { token: 0, from: ACCOUNT, to: ACCOUNT, value: 0 }), Fungibles(approve { token: 0, spender: ACCOUNT, value: 0 }), @@ -336,17 +336,17 @@ mod tests { Fungibles(clear_metadata { token: 0 }), Fungibles(mint { token: 0, account: ACCOUNT, value: 0 }), Fungibles(burn { token: 0, account: ACCOUNT, value: 0 }), - ] - .iter() - { - assert!(Filter::::contains(call)) + ]; + + for call in CALLS { + assert!(Filter::::contains(&call)) } } #[test] fn filter_allows_nonfungibles_calls() { use pallet_api::nonfungibles::{ - types::{CreateCollectionConfig, MintType}, + types::{CollectionConfig, CollectionSettings, MintSettings}, Call::*, }; use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness}; @@ -360,13 +360,12 @@ mod tests { approved: false, }), NonFungibles(create { + id: 0, admin: ACCOUNT, - config: CreateCollectionConfig { + config: CollectionConfig { max_supply: Some(0), - mint_type: MintType::Public, - price: None, - start_block: Some(0), - end_block: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), }, }), NonFungibles(destroy { @@ -396,7 +395,12 @@ mod tests { witness: CancelAttributesApprovalWitness { account_attributes: 0 }, }), NonFungibles(set_max_supply { collection: 0, max_supply: 0 }), - NonFungibles(mint { to: ACCOUNT, collection: 0, item: 0, mint_price: None }), + NonFungibles(mint { + to: ACCOUNT, + collection: 0, + item: 0, + witness: MintWitness { mint_price: None, owned_item: None }, + }), NonFungibles(burn { collection: 0, item: 0 }), ] .iter() @@ -408,8 +412,7 @@ mod tests { #[test] fn filter_allows_fungibles_reads() { use super::{fungibles::Read::*, RuntimeRead::*}; - - for read in vec![ + const READS: [RuntimeRead; 7] = [ Fungibles(TotalSupply(1)), Fungibles(BalanceOf { token: 1, owner: ACCOUNT }), Fungibles(Allowance { token: 1, owner: ACCOUNT, spender: ACCOUNT }), @@ -417,10 +420,10 @@ mod tests { Fungibles(TokenSymbol(1)), Fungibles(TokenDecimals(10)), Fungibles(TokenExists(1)), - ] - .iter() - { - assert!(Filter::::contains(read)) + ]; + + for read in READS { + assert!(Filter::::contains(&read)) } } diff --git a/runtime/devnet/src/config/api/versioning.rs b/runtime/devnet/src/config/api/versioning.rs index 1cb10d9d..76e59e6a 100644 --- a/runtime/devnet/src/config/api/versioning.rs +++ b/runtime/devnet/src/config/api/versioning.rs @@ -1,4 +1,3 @@ -use codec::Encode; use sp_runtime::ModuleError; use super::*; @@ -41,7 +40,7 @@ impl From for RuntimeRead { /// Versioned runtime state read results. #[derive(Debug)] -#[cfg_attr(test, derive(Encode, Clone))] +#[cfg_attr(test, derive(PartialEq, Clone))] pub enum VersionedRuntimeResult { /// Version zero of runtime read results. V0(RuntimeResult), @@ -176,19 +175,13 @@ mod tests { fn from_versioned_runtime_call_to_runtime_call_works() { let call = RuntimeCall::System(Call::remark_with_event { remark: "pop".as_bytes().to_vec() }); - assert_eq!( - RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())).encode(), - call.encode() - ); + assert_eq!(RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())), call); } #[test] fn from_versioned_runtime_read_to_runtime_read_works() { let read = RuntimeRead::Fungibles(fungibles::Read::::TotalSupply(42)); - assert_eq!( - RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())).encode(), - read.encode() - ); + assert_eq!(RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())), read); } #[test] @@ -196,21 +189,21 @@ mod tests { let result = RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)); let v0 = 0; assert_eq!( - VersionedRuntimeResult::try_from((result.clone(), v0)).unwrap().encode(), - VersionedRuntimeResult::V0(result.clone()).encode() + VersionedRuntimeResult::try_from((result.clone(), v0)), + Ok(VersionedRuntimeResult::V0(result.clone())) ); } #[test] fn versioned_runtime_result_fails() { // Unknown version 1. - let err = VersionedRuntimeResult::try_from(( - RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), - 1, - )) - .unwrap_err(); - let expected_err: DispatchError = pallet_revive::Error::::DecodingFailed.into(); - assert_eq!(err.encode(), expected_err.encode()); + assert_eq!( + VersionedRuntimeResult::try_from(( + RuntimeResult::Fungibles(fungibles::ReadResult::::TotalSupply(1_000)), + 1 + )), + Err(pallet_revive::Error::::DecodingFailed.into()) + ); } #[test] diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index 326b7e59..28a51d52 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -6,7 +6,7 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureSigned}; use pallet_nfts::PalletFeatures; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId, Signature}; -use sp_runtime::traits::Verify; +use sp_runtime::traits::{Get, Verify}; use crate::{ deposit, AccountId, Assets, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, @@ -37,6 +37,15 @@ parameter_types! { pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(PartialEq, Clone))] +pub struct KeyLimit; +impl Get for KeyLimit { + fn get() -> u32 { + N + } +} + impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; @@ -56,7 +65,7 @@ impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ItemId = ItemId; // TODO: source from primitives - type KeyLimit = ConstU32<64>; + type KeyLimit = KeyLimit<64>; type Locker = (); type MaxAttributesPerCall = ConstU32<10>; type MaxDeadlineDuration = NftsMaxDeadlineDuration;