From 0ae0d7b25b947d2bf647a56f739f19baeb41a0d2 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:13:21 +0700 Subject: [PATCH 01/25] chore: rebase --- Cargo.lock | 1 + pallets/api/Cargo.toml | 4 + pallets/api/src/lib.rs | 1 + pallets/api/src/mock.rs | 95 +- pallets/api/src/nonfungibles/benchmarking.rs | 110 ++ pallets/api/src/nonfungibles/impls.rs | 91 ++ pallets/api/src/nonfungibles/mod.rs | 613 +++++++++ pallets/api/src/nonfungibles/tests.rs | 1262 ++++++++++++++++++ pallets/api/src/nonfungibles/weights.rs | 217 +++ 9 files changed, 2391 insertions(+), 3 deletions(-) create mode 100644 pallets/api/src/nonfungibles/benchmarking.rs create mode 100644 pallets/api/src/nonfungibles/impls.rs 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/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 78f9f2c4f..3eb0a3457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8851,6 +8851,7 @@ dependencies = [ "log", "pallet-assets 41.0.0", "pallet-balances 40.0.0", + "pallet-nfts 31.0.0", "parity-scale-codec", "pop-chain-extension", "scale-info", diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index db655d296..21c3f2f8e 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", ] @@ -48,6 +50,7 @@ std = [ "log/std", "pallet-assets/std", "pallet-balances/std", + "pallet-nfts/std", "pop-chain-extension/std", "scale-info/std", "sp-core/std", @@ -58,5 +61,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-nfts/try-runtime", "sp-runtime/try-runtime", ] diff --git a/pallets/api/src/lib.rs b/pallets/api/src/lib.rs index f1d759fbf..d0e82e5a5 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 cd38c426a..f435fbfb9 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -1,11 +1,14 @@ +use codec::{Decode, Encode}; 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 scale_info::TypeInfo; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Lazy, Verify}, BuildStorage, }; @@ -29,6 +32,8 @@ frame_support::construct_runtime!( Assets: pallet_assets::, Balances: pallet_balances, Fungibles: crate::fungibles, + Nfts: pallet_nfts::, + NonFungibles: crate::nonfungibles } ); @@ -92,7 +97,7 @@ 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; @@ -111,6 +116,90 @@ impl crate::fungibles::Config for Test { type WeightInfo = (); } +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 + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_nfts::pallet::BenchmarkHelper for () { + fn collection(i: u16) -> u32 { + i.into() + } + + fn item(i: u16) -> u32 { + i.into() + } + + fn signer() -> (Noop, u64) { + unimplemented!() + } + + fn sign(signer: &Noop, message: &[u8]) -> Noop { + unimplemented!() + } +} + +type NftsInstance = pallet_nfts::Instance1; +impl pallet_nfts::Config for Test { + type ApprovalsLimit = ConstU32<10>; + type AttributeDepositBase = ConstU128<1>; + type BalanceDeposit = ConstU128<1>; + type CollectionApprovalDeposit = 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>; + type OffchainPublic = Noop; + type OffchainSignature = Noop; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type WeightInfo = (); +} + +impl crate::nonfungibles::Config for Test { + type NftsInstance = NftsInstance; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() diff --git a/pallets/api/src/nonfungibles/benchmarking.rs b/pallets/api/src/nonfungibles/benchmarking.rs new file mode 100644 index 000000000..45d9d5bd2 --- /dev/null +++ b/pallets/api/src/nonfungibles/benchmarking.rs @@ -0,0 +1,110 @@ +//! Benchmarking setup for pallet_api::nonfungibles + +use frame_benchmarking::{account, v2::*}; +use frame_support::{traits::nonfungibles_v2::Inspect, BoundedVec}; +use sp_runtime::traits::Zero; + +use super::{AttributeNamespace, CollectionIdOf, Config, ItemIdOf, NftsInstanceOf, Pallet, Read}; +use crate::Read as _; + +const SEED: u32 = 1; + +#[benchmarks( + where + > as Inspect<::AccountId>>::ItemId: Zero, + > as Inspect<::AccountId>>::CollectionId: Zero, +)] +mod benchmarks { + use super::*; + + #[benchmark] + // Storage: `Collection` + fn total_supply() { + #[block] + { + Pallet::::read(Read::TotalSupply(CollectionIdOf::::zero())); + } + } + + #[benchmark] + // Storage: `AccountBalance` + fn balance_of() { + #[block] + { + Pallet::::read(Read::BalanceOf { + collection: CollectionIdOf::::zero(), + owner: account("Alice", 0, SEED), + }); + } + } + + #[benchmark] + // Storage: `Allowances`, `Item` + fn allowance() { + #[block] + { + Pallet::::read(Read::Allowance { + collection: CollectionIdOf::::zero(), + owner: account("Alice", 0, SEED), + operator: account("Bob", 0, SEED), + item: Some(ItemIdOf::::zero()), + }); + } + } + + #[benchmark] + // Storage: `Item` + fn owner_of() { + #[block] + { + Pallet::::read(Read::OwnerOf { + collection: CollectionIdOf::::zero(), + item: ItemIdOf::::zero(), + }); + } + } + + #[benchmark] + // Storage: `Attribute` + fn get_attribute() { + #[block] + { + Pallet::::read(Read::GetAttribute { + key: BoundedVec::default(), + collection: CollectionIdOf::::zero(), + item: ItemIdOf::::zero(), + namespace: AttributeNamespace::CollectionOwner, + }); + } + } + + #[benchmark] + // Storage: `Collection` + fn collection() { + #[block] + { + Pallet::::read(Read::Collection(CollectionIdOf::::zero())); + } + } + + #[benchmark] + // Storage: `NextCollectionId` + fn next_collection_id() { + #[block] + { + Pallet::::read(Read::NextCollectionId); + } + } + + #[benchmark] + // Storage: `ItemMetadata` + fn item_metadata() { + #[block] + { + Pallet::::read(Read::ItemMetadata { + collection: CollectionIdOf::::zero(), + item: ItemIdOf::::zero(), + }); + } + } +} diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs new file mode 100644 index 000000000..550872972 --- /dev/null +++ b/pallets/api/src/nonfungibles/impls.rs @@ -0,0 +1,91 @@ +use frame_support::dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}; +use frame_system::pallet_prelude::*; +use pallet_nfts::WeightInfo as NftsWeightInfoTrait; +use sp_runtime::traits::StaticLookup; + +use super::{pallet::*, AccountIdOf, CollectionIdOf, ItemIdOf, NftsOf, NftsWeightInfoOf}; + +impl Pallet { + /// Approves the transfer of a specific item or all collection items owned by the origin to + /// an operator. + /// + /// # Parameters + /// - `owner` - The owner of the specified collection item(s). + /// - `collection` - The identifier of the collection. + /// - `maybe_item` - The optional item of the collection to be approved for delegated transfer. + /// If `None`, the approval applies to all `owner`'s collection items. + /// - `operator`: The account that will be allowed to take control of the specified item or all + /// owner's collection items. + pub(crate) fn do_approve( + owner: OriginFor, + collection: CollectionIdOf, + maybe_item: Option>, + operator: &AccountIdOf, + ) -> DispatchResultWithPostInfo { + Ok(Some(match maybe_item { + Some(item) => { + NftsOf::::approve_transfer( + owner, + collection, + item, + T::Lookup::unlookup(operator.clone()), + None, + ) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_transfer()))?; + NftsWeightInfoOf::::approve_transfer() + }, + None => { + NftsOf::::approve_collection_transfer( + owner, + collection, + T::Lookup::unlookup(operator.clone()), + None, + ) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_collection_transfer()))?; + NftsWeightInfoOf::::approve_collection_transfer() + }, + }) + .into()) + } + + /// Cancel an approval to transfer a specific item or all items within a collection owned by + /// the origin. + /// + /// # Parameters + /// - `owner` - The owner of the specified collection item(s). + /// - `collection` - The identifier of the collection. + /// - `maybe_item` - The optional item of the collection that the operator has an approval to + /// transfer. If not provided, an approval to transfer all `owner`'s collection items will be + /// cancelled. + /// - `operator` - The account that had permission to transfer the sepcified item or all owner's + /// collection items. + pub(crate) fn do_cancel_approval( + owner: OriginFor, + collection: CollectionIdOf, + maybe_item: Option>, + operator: &AccountIdOf, + ) -> DispatchResultWithPostInfo { + Ok(Some(match maybe_item { + Some(item) => { + NftsOf::::cancel_approval( + owner, + collection, + item, + T::Lookup::unlookup(operator.clone()), + ) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_approval()))?; + NftsWeightInfoOf::::cancel_approval() + }, + None => { + NftsOf::::cancel_collection_approval( + owner, + collection, + T::Lookup::unlookup(operator.clone()), + ) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_collection_approval()))?; + NftsWeightInfoOf::::cancel_collection_approval() + }, + }) + .into()) + } +} diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs new file mode 100644 index 000000000..19c031983 --- /dev/null +++ b/pallets/api/src/nonfungibles/mod.rs @@ -0,0 +1,613 @@ +//! 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::Inspect, Currency}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use pallet::*; +use pallet_nfts::WeightInfo as NftsWeightInfoTrait; +pub use pallet_nfts::{ + AttributeNamespace, CancelAttributesApprovalWitness, CollectionConfig, CollectionDetails, + CollectionSetting, CollectionSettings, DestroyWitness, ItemDeposit, ItemDetails, ItemMetadata, + ItemSetting, MintSettings, MintType, MintWitness, +}; +use sp_runtime::traits::StaticLookup; +use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +mod impls; +#[cfg(test)] +mod tests; +pub mod weights; + +type AccountIdOf = ::AccountId; +type NftsOf = pallet_nfts::Pallet>; +type NftsErrorOf = pallet_nfts::Error>; +type NftsWeightInfoOf = >>::WeightInfo; +type NftsInstanceOf = ::NftsInstance; +type BalanceOf = <>>::Currency as Currency< + ::AccountId, +>>::Balance; +type CollectionIdOf = + as Inspect<::AccountId>>::CollectionId; +type ItemIdOf = as Inspect<::AccountId>>::ItemId; +type ItemPriceOf = BalanceOf; +type CollectionDetailsFor = CollectionDetails, BalanceOf>; +type AttributeNamespaceOf = AttributeNamespace>; +type CollectionConfigFor = + CollectionConfig, BlockNumberFor, CollectionIdOf>; +// Type aliases for pallet-nfts storage items. +pub(super) type AccountBalanceOf = pallet_nfts::AccountBalance>; +pub(super) type AttributeOf = pallet_nfts::Attribute>; +pub(super) type NextCollectionIdOf = pallet_nfts::NextCollectionId>; +pub(super) type CollectionOf = pallet_nfts::Collection>; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + dispatch::{DispatchResult, DispatchResultWithPostInfo}, + pallet_prelude::*, + traits::Incrementable, + }; + use frame_system::pallet_prelude::*; + use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness, MintWitness}; + use sp_runtime::BoundedVec; + use sp_std::vec::Vec; + + use super::*; + + /// 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 { + /// Total item supply of a specified `collection`. + #[codec(index = 0)] + TotalSupply(CollectionIdOf), + /// Account balance for a specified `collection`. + #[codec(index = 1)] + BalanceOf { + /// The collection. + collection: CollectionIdOf, + /// The owner of the collection . + owner: AccountIdOf, + }, + /// Allowance for an `operator` approved by an `owner`, for a specified collection or item. + #[codec(index = 2)] + Allowance { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: Option>, + /// The owner of the collection item. + owner: AccountIdOf, + /// The delegated operator of collection item. + operator: AccountIdOf, + }, + /// Owner of a specified collection item. + #[codec(index = 5)] + OwnerOf { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + }, + /// Attribute value of a specified collection item. + #[codec(index = 6)] + GetAttribute { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + /// The namespace of the attribute. + namespace: AttributeNamespaceOf, + /// The key of the attribute. + key: BoundedVec, + }, + /// Details of a specified collection. + #[codec(index = 9)] + Collection(CollectionIdOf), + /// Next collection ID. + #[codec(index = 10)] + NextCollectionId, + /// Metadata of a specified collection item. + #[codec(index = 11)] + ItemMetadata { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + }, + } + + /// 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(u128), + /// 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 specified collection item. + GetAttribute(Option>), + /// Details of a specified collection. + Collection(Option>), + /// Next collection ID. + NextCollectionId(Option>), + /// Metadata of a specified collection item. + ItemMetadata(Option>), + } + + impl ReadResult { + /// Encodes the result. + pub fn encode(&self) -> Vec { + use ReadResult::*; + match self { + OwnerOf(result) => result.encode(), + TotalSupply(result) => result.encode(), + BalanceOf(result) => result.encode(), + Collection(result) => result.encode(), + Allowance(result) => result.encode(), + GetAttribute(result) => result.encode(), + NextCollectionId(result) => result.encode(), + ItemMetadata(result) => result.encode(), + } + } + } + + /// 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>; + /// The instance of pallet-nfts. + type NftsInstance; + /// Weight information for dispatchables in this pallet. + type WeightInfo: WeightInfo; + } + + #[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 `operator` changes. + Approval { + /// The identifier of the collection. + collection: CollectionIdOf, + /// The item which is (dis)approved. `None` for all collection items owned by the + /// `owner`. + item: Option>, + /// The owner providing the allowance. + owner: AccountIdOf, + /// The beneficiary of the allowance. + operator: AccountIdOf, + /// Whether allowance is set or removed. + approved: bool, + }, + /// Event emitted when a token transfer occurs. + // Differing style: event name abides by the PSP22 standard. + Transfer { + /// The collection ID. + collection: CollectionIdOf, + /// The collection item ID. + item: ItemIdOf, + /// The source of the transfer. `None` when minting. + from: Option>, + /// The recipient of the transfer. `None` when burning. + to: Option>, + /// 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] + impl Pallet { + /// Transfers the collection item from the `origin` to account `to`. + /// + /// # Parameters + /// - `collection` - The collection of the item to be transferred. + /// - `item` - The item to transfer. + /// - `to` - The recipient account. + #[pallet::call_index(3)] + #[pallet::weight(NftsWeightInfoOf::::transfer() + T::DbWeight::get().reads_writes(1, 0))] + pub fn transfer( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + to: AccountIdOf, + ) -> DispatchResult { + ensure_signed(origin.clone())?; + let owner = + NftsOf::::owner(collection, item).ok_or(NftsErrorOf::::UnknownItem)?; + NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(owner), + to: Some(to), + price: None, + }); + Ok(()) + } + + /// Either approve or cancel approval for an `operator` to perform transfers of a specific + /// collection item or all collection items owned by the `origin`. + /// + /// # Parameters + /// - `collection` - The identifier of the collection. + /// - `item` - An optional parameter specifying the item to approve for the delegated + /// transfer. If `None`, all owner's collection items will be approved. + /// - `operator` - The account being granted or revoked approval to transfer the specified + /// collection item(s). + /// - `approved` - A boolean indicating the desired approval status: + /// - `true` to approve the `operator`. + /// - `false` to cancel the approval granted to the `operator`. + #[pallet::call_index(4)] + #[pallet::weight( + NftsWeightInfoOf::::approve_transfer() + + NftsWeightInfoOf::::approve_collection_transfer() + + NftsWeightInfoOf::::cancel_collection_approval() + + NftsWeightInfoOf::::cancel_approval() + )] + pub fn approve( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + operator: AccountIdOf, + approved: bool, + ) -> DispatchResultWithPostInfo { + let owner = ensure_signed(origin.clone())?; + let result = if approved { + Self::do_approve(origin, collection, item, &operator) + } else { + Self::do_cancel_approval(origin, collection, item, &operator) + }; + Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); + result + } + + /// Cancel all the approvals of a specific item. + /// + /// # Parameters + /// - `collection` - The collection of the item of whose approvals will be cleared. + /// - `item` - The item of the collection of whose approvals will be cleared. + #[pallet::call_index(5)] + #[pallet::weight(NftsWeightInfoOf::::clear_all_transfer_approvals())] + pub fn clear_all_transfer_approvals( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::clear_all_transfer_approvals(origin, collection, item) + } + + /// Cancel approvals to transfer all owner's collection items. + /// + /// # Parameters + /// - `collection` - The collection whose approvals will be cleared. + /// - `limit` - The amount of collection approvals that will be cleared. + #[pallet::call_index(6)] + #[pallet::weight(NftsWeightInfoOf::::clear_collection_approvals(*limit))] + pub fn clear_collection_approvals( + origin: OriginFor, + collection: CollectionIdOf, + limit: u32, + ) -> DispatchResultWithPostInfo { + NftsOf::::clear_collection_approvals(origin, collection, limit) + } + + /// Issue a new collection of non-fungible items from a public origin. + /// + /// # Parameters + /// - `admin` - The admin of this collection. The admin is the initial address of each + /// member of the collection's admin team. + /// - `config` - The configuration of the collection. + #[pallet::call_index(7)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create( + origin: OriginFor, + admin: AccountIdOf, + config: CollectionConfigFor, + ) -> DispatchResult { + let creator = ensure_signed(origin.clone())?; + // TODO: re-evaluate next collection id in nfts pallet. The `Incrementable` trait causes + // issues for setting it to xcm's `Location`. This can easily be done differently. + let id = NextCollectionIdOf::::get() + .or(T::CollectionId::initial_value()) + .ok_or(NftsErrorOf::::UnknownCollection)?; + NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), config)?; + Self::deposit_event(Event::Created { id, creator, admin }); + Ok(()) + } + + /// Destroy a collection of fungible items. + /// + /// # Parameters + /// - `collection` - The collection to destroy. + /// - `witness` - Information on the items minted in the collection. This must be + /// correct. + #[pallet::call_index(8)] + #[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) + } + + /// Set an attribute for a collection or item. + /// + /// # Parameters + /// - `collection` - The collection whose item's metadata to set. + /// - `maybe_item` - 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. + #[pallet::call_index(12)] + #[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) + } + + /// Clear an attribute for the collection or item. + /// + /// # Parameters + /// - `collection` - The collection whose item's metadata to clear. + /// - `maybe_item` - The item whose metadata to clear. + /// - `namespace` - Attribute's namespace. + /// - `key` - The key of the attribute. + #[pallet::call_index(13)] + #[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) + } + + /// Set the metadata for an item. + /// + /// # Parameters + /// - `collection` - The collection whose item's metadata to set. + /// - `item` - The item whose metadata to set. + /// - `data` - The general information of this item. Limited in length by `StringLimit`. + #[pallet::call_index(14)] + #[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(15)] + #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + NftsOf::::clear_metadata(origin, collection, item) + } + + /// Approve item's attributes to be changed by a delegated third-party account. + /// + /// # Parameters + /// - `collection` - The collection of the item. + /// - `item` - The item that holds attributes. + /// - `delegate` - The account to delegate permission to change attributes of the item. + #[pallet::call_index(16)] + #[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()), + ) + } + + /// Cancel the previously provided approval to change item's attributes. + /// All the previously set attributes by the `delegate` will be removed. + /// + /// # Parameters + /// - `collection` - The collection that the item is contained within. + /// - `item` - The item that holds attributes. + /// - `delegate` - The previously approved account to remove. + /// - `witness` - A witness data to cancel attributes approval operation. + #[pallet::call_index(17)] + #[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, + ) + } + + /// Set the maximum number of items a collection could have. + /// + /// # Parameters + /// - `collection` - The identifier of the collection to change. + /// - `max_supply` - The maximum number of items a collection could have. + #[pallet::call_index(18)] + #[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) + } + + /// Mint an item of a particular collection. + /// + /// # Parameters + /// - `to` - Account into which the item will be minted. + /// - `collection` - The collection of the item to mint. + /// - `item` - An identifier of the new item. + /// - `witness_data` - When the mint type is `HolderOf(collection_id)`, then the owned + /// item_id from that collection needs to be provided within the witness data object. If + /// the mint price is set, then it should be additionally confirmed in the `witness_data`. + #[pallet::call_index(19)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + witness: MintWitness, ItemPriceOf>, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + let mint_price = witness.mint_price; + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness), + )?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: None, + to: Some(owner), + price: mint_price, + }); + Ok(()) + } + + /// Destroy a single collection item. + /// + /// # Parameters + /// - `collection` - The collection of the item to burn. + /// - `item` - The item to burn. + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(owner), + to: None, + price: None, + }); + Ok(()) + } + } + + 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 { + use Read::*; + match request { + TotalSupply(_) => ::WeightInfo::total_supply(), + BalanceOf { .. } => ::WeightInfo::balance_of(), + Allowance { .. } => ::WeightInfo::allowance(), + OwnerOf { .. } => ::WeightInfo::owner_of(), + GetAttribute { .. } => ::WeightInfo::get_attribute(), + Collection(_) => ::WeightInfo::collection(), + ItemMetadata { .. } => ::WeightInfo::item_metadata(), + NextCollectionId => ::WeightInfo::next_collection_id(), + } + } + + /// Performs the requested read and returns the result. + /// + /// # Parameters + /// - `request` - The read request. + fn read(value: Self::Read) -> Self::Result { + use Read::*; + match value { + TotalSupply(collection) => ReadResult::TotalSupply( + NftsOf::::collection_items(collection).unwrap_or_default() as u128, + ), + BalanceOf { collection, owner } => ReadResult::BalanceOf( + AccountBalanceOf::::get(collection, owner) + .map(|(balance, _)| balance) + .unwrap_or_default(), + ), + Allowance { collection, owner, operator, item } => ReadResult::Allowance( + NftsOf::::check_approval(&collection, &item, &owner, &operator).is_ok(), + ), + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), + GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( + AttributeOf::::get((collection, Some(item), namespace, key)) + .map(|attribute| attribute.0.into()), + ), + Collection(collection) => + ReadResult::Collection(CollectionOf::::get(collection)), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + 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 new file mode 100644 index 000000000..aec7c9dcb --- /dev/null +++ b/pallets/api/src/nonfungibles/tests.rs @@ -0,0 +1,1262 @@ +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, + dispatch::WithPostDispatchInfo, + sp_runtime::{traits::Zero, BoundedVec, DispatchError::BadOrigin}, +}; +use pallet_nfts::{Config, WeightInfo as NftsWeightInfoTrait}; + +use crate::{ + mock::*, + nonfungibles::{ + AccountBalanceOf, AttributeNamespace, AttributeOf, BlockNumberFor, + CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, CollectionOf, + CollectionSettings, DestroyWitness, ItemIdOf, MintSettings, MintWitness, + NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, + }, + Read, +}; + +const COLLECTION: u32 = 0; +const ITEM: u32 = 1; + +type NftsError = pallet_nfts::Error>; +type Event = crate::nonfungibles::Event; +type CollectionApprovals = pallet_nfts::CollectionApprovals>; + +mod encoding_read_result { + use super::*; + + #[test] + fn total_supply() { + let total_supply: u128 = 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(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("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 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] + fn item_metadata_works() { + 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()); + } +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let dest = BOB; + let item = ITEM; + let owner = ALICE; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::transfer(origin, collection, item, dest), BadOrigin); + } + // Check error works for `Nfts::transfer`. + assert_noop!( + NonFungibles::transfer(signed(owner), collection, item, dest), + NftsError::UnknownItem + ); + // Successfully transfer a collection item. + nfts::create_collection_mint(owner, owner, item); + let balance_before_transfer = nfts::balance_of(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); + let balance_after_transfer = nfts::balance_of(collection, &dest); + assert!(nfts::balance_of(collection, &owner).is_zero()); + assert_eq!(balance_after_transfer - balance_before_transfer, 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + .into(), + ); + }); +} + +#[test] +fn approved_transfer_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let dest = CHARLIE; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + nfts::create_collection_mint(owner, owner, item); + // Approve `operator` to transfer `collection` items owned by the `owner`. + assert_ok!(Nfts::approve_collection_transfer(signed(owner), collection, operator, None)); + // Successfully transfers a collection item. + let from_balance_before_transfer = nfts::balance_of(collection, &owner); + let to_balance_before_transfer = nfts::balance_of(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(operator), collection, item, dest)); + let from_balance_after_transfer = nfts::balance_of(collection, &owner); + let to_balance_after_transfer = nfts::balance_of(collection, &dest); + // Check that `to` has received the `value` tokens from `from`. + assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); + assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + .into(), + ); + }); +} + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + let witness = MintWitness { mint_price: None, owned_item: None }; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::mint(origin, owner, collection, item, witness.clone()), + BadOrigin + ); + } + // Check error works for `Nfts::mint()`. + assert_noop!( + NonFungibles::mint(signed(owner), owner, collection, item, witness.clone()), + NftsError::NoConfig + ); + // Successfully mints a new collection item. + nfts::create_collection(owner); + let balance_before_mint = nfts::balance_of(collection, &owner); + assert_ok!(NonFungibles::mint(signed(owner), owner, collection, item, witness)); + let balance_after_mint = nfts::balance_of(collection, &owner); + assert_eq!(balance_after_mint, balance_before_mint + 1); + System::assert_last_event( + Event::Transfer { collection, item, from: None, to: Some(owner), price: None }.into(), + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let owner = ALICE; + let item = ITEM; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::burn(origin, collection, item), BadOrigin); + } + // Check error works for `Nfts::burn()`. + assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); + // Successfully burns an existing new collection item. + nfts::create_collection_mint(owner, owner, ITEM); + let balance_before_burn = nfts::balance_of(collection, &owner); + assert_ok!(NonFungibles::burn(signed(owner), collection, item)); + let balance_after_burn = nfts::balance_of(collection, &owner); + assert_eq!(balance_after_burn, balance_before_burn - 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Origin checks for `approve`. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::approve(origin, collection, Some(item), operator, true), + BadOrigin + ); + } + // Check error works for `Nfts::approve_transfer()`. + assert_noop!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::approve_transfer()) + ); + nfts::create_collection_mint(owner, owner, item); + // Successfully approves `operator` to transfer the collection item. + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + ); + assert_ok!(Nfts::check_approval(&collection, &Some(item), &owner, &operator)); + System::assert_last_event( + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), + ); + // Re-approves `operator` to transfer the collection item. + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + ); + assert_ok!(Nfts::check_approval(&collection, &Some(item), &owner, &operator)); + System::assert_last_event( + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), + ); + // Successfully transfers the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); + }); +} + +#[test] +fn approve_collection_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::approve(origin, collection, None, operator, true), + BadOrigin + ); + } + // Check error works for `Nfts::approve_collection_transfer()`. + assert_noop!( + NonFungibles::approve(signed(owner), collection, None, operator, true), + NftsError::NoItemOwned + .with_weight(NftsWeightInfoOf::::approve_collection_transfer()) + ); + // Approving to transfer `collection` reserves funds from the `operator`. + nfts::create_collection_mint(owner, owner, item); + let reserved_balance_before_approve = Balances::reserved_balance(&owner); + assert_eq!( + NonFungibles::approve(signed(owner), collection, None, operator, true), + Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) + ); + let reserved_balance_after_approve = Balances::reserved_balance(&owner); + assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); + assert_ok!(Nfts::check_approval(&collection, &None, &owner, &operator)); + // Re-approving the transfer of `collection` does not require reserving additional funds. + assert_eq!( + NonFungibles::approve(signed(owner), collection, None, operator, true), + Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) + ); + assert_eq!(Balances::reserved_balance(&owner), reserved_balance_after_approve); + assert_ok!(Nfts::check_approval(&collection, &None, &owner, &operator)); + System::assert_last_event( + Event::Approval { collection, item: None, owner, operator, approved: true }.into(), + ); + // Successfully transfer the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::approve(origin, collection, Some(item), operator, false), + BadOrigin + ); + } + // Check error works for `Nfts::cancel_approval()`. + assert_noop!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, false), + NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::cancel_approval()) + ); + // Successfully cancels the transfer approval of `operator` by `owner`. + nfts::create_collection_mint_and_approve(owner, owner, item, operator); + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, false), + Ok(Some(NftsWeightInfoOf::::cancel_approval()).into()) + ); + assert_eq!( + Nfts::check_approval(&collection, &Some(item), &owner, &operator), + Err(NftsError::NoPermission.into()) + ); + }); +} + +#[test] +fn cancel_collection_approval_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::approve(origin, collection, None, operator, false), + BadOrigin + ); + } + // Check error works for `Nfts::cancel_approval()`. + assert_noop!( + NonFungibles::approve(signed(owner), collection, None, operator, false), + NftsError::Unapproved + .with_weight(NftsWeightInfoOf::::cancel_collection_approval()) + ); + // Successfully cancel the transfer collection approval of `operator` by `owner`. + nfts::create_collection_mint(owner, owner, item); + assert_ok!(Nfts::approve_collection_transfer(signed(owner), collection, operator, None)); + assert_eq!( + NonFungibles::approve(signed(owner), collection, None, operator, false), + Ok(Some(NftsWeightInfoOf::::cancel_collection_approval()).into()) + ); + assert_eq!( + Nfts::check_approval(&collection, &None, &owner, &operator), + Err(NftsError::NoPermission.into()) + ); + // Failed to transfer the item by `operator` without permission. + assert_noop!( + Nfts::transfer(signed(operator), collection, item, operator), + NftsError::NoPermission + ); + }); +} + +#[test] +fn clear_all_transfer_approvals_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let delegates = 10..20; + let item = ITEM; + let owner = ALICE; + + // Check error works for `Nfts::clear_all_transfer_approvals()`. + assert_noop!( + NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item), + NftsError::UnknownCollection + ); + + nfts::create_collection_mint(owner, owner, item); + delegates.clone().for_each(|delegate| { + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, delegate, None)); + }); + // Successfully clear all transfer approvals. + assert_ok!(NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item)); + delegates.for_each(|delegate| { + assert!(Nfts::check_approval(&collection, &Some(item), &owner, &delegate).is_err()); + }); + }); +} + +#[test] +fn clear_collection_approvals_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let delegates = 10..20; + let owner = ALICE; + let approvals = (delegates.end - delegates.start) as u32; + + nfts::create_collection_mint(owner, owner, ITEM); + delegates.clone().for_each(|delegate| { + assert_ok!(Nfts::approve_collection_transfer( + signed(owner), + collection, + delegate, + None + )); + }); + // Partially clear collection approvals. + assert_eq!( + NonFungibles::clear_collection_approvals(signed(owner), collection, 1), + Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(1)).into()) + ); + assert_eq!( + CollectionApprovals::iter_prefix((collection, owner,)).count(), + (approvals - 1) as usize + ); + // Successfully clear all collection approvals. + assert_eq!( + NonFungibles::clear_collection_approvals(signed(owner), collection, approvals), + Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(approvals - 1)).into()) + ); + assert!(CollectionApprovals::iter_prefix((collection, owner,)).count().is_zero()); + }); +} + +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let owner = ALICE; + let max_supply = 10; + + nfts::create_collection(owner); + // Successfully set the max supply for the collection. + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply)); + (0..max_supply).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + }); + // Throws `MaxSupplyReached` error if number of minted items is over the max supply. + assert_noop!( + Nfts::mint(signed(owner), collection, 42, owner, None), + NftsError::MaxSupplyReached + ); + // Override the max supply. + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply * 2)); + assert_ok!(Nfts::mint(signed(owner), collection, 42, owner, None)); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let metadata = BoundedVec::truncate_from("some metadata".into()); + let owner = ALICE; + + // Check error works for `Nfts::set_metadata()`. + assert_noop!( + NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone()), + NftsError::NoPermission + ); + nfts::create_collection_mint(owner, owner, item); + // Successfully set the metadata. + assert_ok!(NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone())); + assert_eq!(Nfts::item_metadata(collection, item), Some(metadata)); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let metadata = BoundedVec::truncate_from("some metadata".into()); + let owner = ALICE; + + // Check error works for `Nfts::clear_metadata()`. + assert_noop!( + NonFungibles::clear_metadata(signed(owner), collection, item), + NftsError::NoPermission + ); + nfts::create_collection_mint(owner, owner, item); + assert_ok!(Nfts::set_metadata(signed(owner), collection, item, metadata.clone())); + // Successfully clear the metadata. + assert_ok!(NonFungibles::clear_metadata(signed(owner), collection, item)); + assert!(Nfts::item_metadata(collection, item).is_none()); + }); +} + +#[test] +fn set_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".into()); + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + let value = BoundedVec::truncate_from("some value".into()); + + // Check error works for `Nfts::set_attribute()`. + assert_noop!( + NonFungibles::set_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + value.clone() + ), + NftsError::UnknownCollection + ); + nfts::create_collection_mint(owner, owner, item); + // Successfully set attribute. + assert_ok!(NonFungibles::set_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + value.clone() + )); + assert_eq!( + nfts::get_attribute( + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute + ), + Some(value.into()) + ); + }); +} + +#[test] +fn clear_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + + nfts::create_collection_mint(owner, owner, item); + assert_ok!(Nfts::set_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + value.clone() + )); + // Successfully clear an attribute. + assert_ok!(NonFungibles::clear_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + )); + assert!(nfts::get_attribute( + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute + ) + .is_none()); + }); +} + +#[test] +fn approve_item_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let collection = COLLECTION; + let delegate = BOB; + let item = ITEM; + let owner = ALICE; + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + + nfts::create_collection_mint(owner, owner, item); + // Successfully approve delegate to set attributes. + assert_ok!(NonFungibles::approve_item_attributes( + signed(owner), + collection, + item, + delegate + )); + assert_ok!(Nfts::set_attribute( + signed(delegate), + collection, + Some(item), + AttributeNamespace::Account(delegate), + attribute.clone(), + value.clone() + )); + }); +} + +#[test] +fn cancel_item_attribute_approval_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let collection = COLLECTION; + let delegate = BOB; + let item = ITEM; + let owner = ALICE; + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + + nfts::create_collection_mint(owner, owner, item); + assert_ok!(Nfts::approve_item_attributes(signed(owner), collection, item, delegate)); + // Successfully cancel item attribute approval. + assert_ok!(Nfts::cancel_item_attributes_approval( + signed(owner), + collection, + item, + delegate, + CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert_noop!( + Nfts::set_attribute( + signed(delegate), + collection, + Some(item), + AttributeNamespace::Account(delegate), + attribute, + value + ), + NftsError::NoPermission + ); + }); +} + +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let admin = ALICE; + let config = CollectionConfig { + max_supply: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), + }; + let collection = COLLECTION; + let creator = ALICE; + + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::create(origin, admin, config.clone()), BadOrigin); + } + // Successfully create a collection. + assert_ok!(NonFungibles::create(signed(creator), admin, config)); + assert_eq!(Nfts::collection_owner(collection), Some(creator)); + System::assert_last_event(Event::Created { id: collection, creator, admin }.into()); + }); +} + +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let witness = DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 }; + + // Check error works for `Nfts::destroy()`. + assert_noop!( + NonFungibles::destroy(signed(ALICE), collection, witness), + NftsError::UnknownCollection + ); + nfts::create_collection(ALICE); + assert_ok!(NonFungibles::destroy(signed(ALICE), collection, witness)); + assert_eq!(Nfts::collection_owner(collection), None); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + + assert_eq!(NonFungibles::read(TotalSupply(collection)), ReadResult::TotalSupply(0)); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + assert_eq!( + NonFungibles::read(TotalSupply(collection)), + ReadResult::TotalSupply((i + 1).into()) + ); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() + ); + }); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner }), + ReadResult::BalanceOf(Default::default()) + ); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner }), + ReadResult::BalanceOf(i + 1) + ); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner }).encode(), + nfts::balance_of(collection, &owner).encode() + ); + }); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + nfts::create_collection_mint_and_approve(owner, owner, item, operator); + assert_eq!( + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }), + ReadResult::Allowance(true) + ); + assert_eq!( + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) + .encode(), + Nfts::check_approval(&collection, &Some(item), &owner, &operator) + .is_ok() + .encode() + ); + }); +} + +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + + assert_eq!(NonFungibles::read(OwnerOf { collection, item }), ReadResult::OwnerOf(None)); + nfts::create_collection_mint(owner, owner, item); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }), + ReadResult::OwnerOf(Some(owner)) + ); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }).encode(), + Nfts::owner(collection, item).encode() + ); + }); +} + +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".into()); + let collection = COLLECTION; + let item = ITEM; + let metadata = "some value".as_bytes().to_vec(); + let owner = ALICE; + + nfts::create_collection_mint(owner, owner, item); + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(None) + ); + // Successfully get an existing attribute. + assert_ok!(Nfts::set_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + BoundedVec::truncate_from(metadata.clone()), + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(Some(metadata)) + ); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + nfts::get_attribute( + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute + ) + .encode() + ); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + + assert_eq!(NonFungibles::read(Collection(collection)), ReadResult::Collection(None),); + nfts::create_collection_mint(owner, owner, item); + assert_eq!( + NonFungibles::read(Collection(collection)), + ReadResult::Collection(CollectionOf::::get(collection)), + ); + assert_eq!( + NonFungibles::read(Collection(collection)).encode(), + CollectionOf::::get(collection).encode(), + ); + }); +} + +#[test] +fn item_metadata_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let metadata = "some metadata".as_bytes().to_vec(); + let owner = ALICE; + + // Read item metadata of an unknown collection. + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }), + ReadResult::ItemMetadata(None) + ); + nfts::create_collection_mint(owner, owner, item); + // Successfully set the metadata of an item. + assert_ok!(NonFungibles::set_metadata( + signed(owner), + collection, + item, + BoundedVec::truncate_from(metadata.clone()) + )); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }), + ReadResult::ItemMetadata(Some(metadata)) + ); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + Nfts::item_metadata(collection, item).encode() + ); + }); +} + +// TODO: Depends on #406, this test can be removed. +#[test] +fn next_collection_id_works() { + new_test_ext().execute_with(|| { + assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(0))); + nfts::create_collection_mint(ALICE, ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(1))); + assert_eq!( + NonFungibles::read(NextCollectionId).encode(), + Some(NextCollectionIdOf::::get().unwrap_or_default()).encode(), + ); + }); +} + +fn signed(account_id: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account_id) +} + +fn root() -> RuntimeOrigin { + RuntimeOrigin::root() +} + +fn none() -> RuntimeOrigin { + RuntimeOrigin::none() +} + +// Helper functions for interacting with pallet-nfts. +mod nfts { + use super::*; + use crate::nonfungibles::AttributeNamespaceOf; + + pub(super) fn balance_of(collection: CollectionIdOf, owner: &AccountId) -> u32 { + AccountBalanceOf::::get(collection, &owner) + .map(|(balance, _)| balance) + .unwrap_or_default() + } + + pub(super) fn create_collection_mint_and_approve( + owner: AccountId, + mint_to: AccountId, + item: ItemIdOf, + operator: AccountId, + ) { + let (collection, item) = create_collection_mint(owner, mint_to, item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, operator, None)); + } + + pub(super) fn create_collection_mint( + owner: AccountId, + mint_to: AccountId, + item: ItemIdOf, + ) -> (u32, u32) { + let collection = create_collection(owner); + assert_ok!(Nfts::mint(signed(owner), collection, item, mint_to, None)); + (collection, item) + } + + pub(super) fn create_collection(owner: AccountId) -> u32 { + let next_id = NextCollectionIdOf::::get().unwrap_or_default(); + assert_ok!(Nfts::create( + signed(owner), + owner, + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } + } + + pub(super) fn get_attribute( + collection: CollectionIdOf, + maybe_item: Option>, + namespace: AttributeNamespaceOf, + key: BoundedVec>>::KeyLimit>, + ) -> Option> { + AttributeOf::::get((collection, maybe_item, namespace, key)) + .map(|attribute| attribute.0.into()) + } +} + +mod read_weights { + use frame_support::weights::Weight; + + use super::*; + use crate::nonfungibles::{weights::WeightInfo, Config}; + + struct ReadWeightInfo { + total_supply: Weight, + balance_of: Weight, + allowance: Weight, + owner_of: Weight, + get_attribute: Weight, + collection: Weight, + next_collection_id: Weight, + item_metadata: Weight, + } + + impl ReadWeightInfo { + fn new() -> Self { + Self { + total_supply: NonFungibles::weight(&TotalSupply(COLLECTION)), + balance_of: NonFungibles::weight(&BalanceOf { + collection: COLLECTION, + owner: ALICE, + }), + allowance: NonFungibles::weight(&Allowance { + collection: COLLECTION, + item: Some(ITEM), + owner: ALICE, + operator: BOB, + }), + owner_of: NonFungibles::weight(&OwnerOf { collection: COLLECTION, item: ITEM }), + get_attribute: NonFungibles::weight(&GetAttribute { + collection: COLLECTION, + item: ITEM, + namespace: AttributeNamespace::CollectionOwner, + key: BoundedVec::default(), + }), + collection: NonFungibles::weight(&Collection(COLLECTION)), + next_collection_id: NonFungibles::weight(&NextCollectionId), + item_metadata: NonFungibles::weight(&ItemMetadata { + collection: COLLECTION, + item: ITEM, + }), + } + } + } + + #[test] + fn ensure_read_matches_benchmarks() { + let ReadWeightInfo { + allowance, + balance_of, + collection, + get_attribute, + item_metadata, + next_collection_id, + owner_of, + total_supply, + } = ReadWeightInfo::new(); + + assert_eq!(total_supply, ::WeightInfo::total_supply()); + assert_eq!(balance_of, ::WeightInfo::balance_of()); + assert_eq!(allowance, ::WeightInfo::allowance()); + assert_eq!(owner_of, ::WeightInfo::owner_of()); + assert_eq!(get_attribute, ::WeightInfo::get_attribute()); + assert_eq!(collection, ::WeightInfo::collection()); + assert_eq!(next_collection_id, ::WeightInfo::next_collection_id()); + assert_eq!(item_metadata, ::WeightInfo::item_metadata()); + } + + // These types read from the `Collection` storage. + #[test] + fn ensure_collection_variants_match() { + let ReadWeightInfo { total_supply, collection, .. } = ReadWeightInfo::new(); + + assert_eq!(total_supply, collection); + } + + // Proof size is based on `MaxEncodedLen`, not hardware. + // This test ensures that the data structure sizes do not change with upgrades. + #[test] + fn ensure_expected_proof_size_does_not_change() { + let ReadWeightInfo { + allowance, + balance_of, + collection, + get_attribute, + item_metadata, + next_collection_id, + owner_of, + total_supply, + } = ReadWeightInfo::new(); + + // These values come from `weights.rs`. + assert_eq!(total_supply.proof_size(), 3557); + assert_eq!(balance_of.proof_size(), 3529); + assert_eq!(allowance.proof_size(), 4326); + assert_eq!(owner_of.proof_size(), 4326); + assert_eq!(get_attribute.proof_size(), 3944); + assert_eq!(collection.proof_size(), 3557); + assert_eq!(next_collection_id.proof_size(), 1489); + assert_eq!(item_metadata.proof_size(), 3812); + } +} + +mod ensure_codec_indexes { + use super::{Encode, *}; + use crate::{mock::RuntimeCall::NonFungibles, nonfungibles}; + + #[test] + fn ensure_read_variant_indexes() { + [ + (TotalSupply::(Default::default()), 0u8, "TotalSupply"), + ( + BalanceOf:: { collection: Default::default(), owner: Default::default() }, + 1, + "BalanceOf", + ), + ( + Allowance:: { + collection: Default::default(), + item: Default::default(), + owner: Default::default(), + operator: Default::default(), + }, + 2, + "Allowance", + ), + ( + OwnerOf:: { collection: Default::default(), item: Default::default() }, + 5, + "OwnerOf", + ), + ( + GetAttribute:: { + collection: Default::default(), + item: Default::default(), + namespace: AttributeNamespace::CollectionOwner, + key: Default::default(), + }, + 6, + "GetAttribute", + ), + (Collection::(Default::default()), 9, "Collection"), + (NextCollectionId, 10, "NextCollectionId"), + ( + ItemMetadata { collection: Default::default(), item: Default::default() }, + 11, + "ItemMetadata", + ), + ] + .iter() + .for_each(|(variant, expected_index, name)| { + assert_eq!(variant.encode()[0], *expected_index, "{name} variant index changed"); + }) + } + + #[test] + fn ensure_dispatchable_indexes() { + use nonfungibles::Call::*; + + [ + ( + transfer { + collection: Default::default(), + item: Default::default(), + to: Default::default(), + }, + 3u8, + "transfer", + ), + ( + approve { + collection: Default::default(), + item: Default::default(), + operator: Default::default(), + approved: Default::default(), + }, + 4, + "approve", + ), + ( + clear_all_transfer_approvals { + collection: Default::default(), + item: Default::default(), + }, + 5, + "clear_all_transfer_approvals", + ), + ( + clear_collection_approvals { + collection: Default::default(), + limit: Default::default(), + }, + 6, + "clear_collection_approvals", + ), + (create { admin: Default::default(), config: Default::default() }, 7, "create"), + ( + destroy { + collection: Default::default(), + witness: DestroyWitness { + item_metadatas: Default::default(), + item_configs: Default::default(), + attributes: Default::default(), + }, + }, + 8, + "destroy", + ), + ( + set_attribute { + collection: Default::default(), + item: Default::default(), + namespace: AttributeNamespace::CollectionOwner, + key: Default::default(), + value: Default::default(), + }, + 12, + "set_attribute", + ), + ( + clear_attribute { + collection: Default::default(), + item: Default::default(), + namespace: AttributeNamespace::CollectionOwner, + key: Default::default(), + }, + 13, + "clear_attribute", + ), + ( + set_metadata { + collection: Default::default(), + item: Default::default(), + data: Default::default(), + }, + 14, + "set_metadata", + ), + ( + clear_metadata { collection: Default::default(), item: Default::default() }, + 15, + "clear_metadata", + ), + ( + approve_item_attributes { + collection: Default::default(), + item: Default::default(), + delegate: Default::default(), + }, + 16, + "approve_item_attributes", + ), + ( + cancel_item_attributes_approval { + collection: Default::default(), + item: Default::default(), + delegate: Default::default(), + witness: CancelAttributesApprovalWitness { + account_attributes: Default::default(), + }, + }, + 17, + "cancel_item_attributes_approval", + ), + ( + set_max_supply { collection: Default::default(), max_supply: Default::default() }, + 18, + "set_max_supply", + ), + ( + mint { + to: Default::default(), + collection: Default::default(), + item: Default::default(), + witness: MintWitness { + owned_item: Default::default(), + mint_price: Default::default(), + }, + }, + 19, + "mint", + ), + (burn { collection: Default::default(), item: Default::default() }, 20, "burn"), + ] + .iter() + .for_each(|(variant, expected_index, name)| { + assert_eq!( + NonFungibles(variant.to_owned()).encode()[1], + *expected_index, + "{name} dispatchable index changed" + ); + }) + } +} diff --git a/pallets/api/src/nonfungibles/weights.rs b/pallets/api/src/nonfungibles/weights.rs new file mode 100644 index 000000000..f0b8fa833 --- /dev/null +++ b/pallets/api/src/nonfungibles/weights.rs @@ -0,0 +1,217 @@ + +//! Autogenerated weights for `nonfungibles` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 +//! DATE: 2024-11-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `R0GUE`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/pop-node +// benchmark +// pallet +// --chain=dev +// --wasm-execution=compiled +// --pallet=nonfungibles +// --steps=50 +// --repeat=20 +// --json +// --template +// ./scripts/pallet-weights-template.hbs +// --output=./pallets/api/src/nonfungibles/weights.rs +// --extrinsic= + +#![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 `nonfungibles`. +pub trait WeightInfo { + fn total_supply() -> Weight; + fn balance_of() -> Weight; + fn allowance() -> Weight; + fn owner_of() -> Weight; + fn get_attribute() -> Weight; + fn collection() -> Weight; + fn next_collection_id() -> Weight; + fn item_metadata() -> Weight; +} + +/// Weights for `nonfungibles` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + fn total_supply() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3557` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 3557) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + fn balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3529` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 3529) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Allowances` (r:1 w:0) + /// Proof: `Nfts::Allowances` (`max_values`: None, `max_size`: Some(109), added: 2584, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn allowance() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `4326` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(7_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn owner_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `4326` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn get_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3944` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(5_000_000, 3944) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + fn collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3557` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 3557) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::NextCollectionId` (r:1 w:0) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn next_collection_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `1489` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + fn item_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3812` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 3812) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + fn total_supply() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3557` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 3557) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + fn balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3529` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 3529) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Allowances` (r:1 w:0) + /// Proof: `Nfts::Allowances` (`max_values`: None, `max_size`: Some(109), added: 2584, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn allowance() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `4326` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(7_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: `Nfts::Item` (r:1 w:0) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + fn owner_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `4326` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + fn get_attribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3944` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(5_000_000, 3944) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + fn collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3557` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 3557) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::NextCollectionId` (r:1 w:0) + /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn next_collection_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `1489` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) + /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + fn item_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `3812` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 3812) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } +} + From b5857412d1f2f5d8459ef3fe9ae23cbaa51a75af Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:57:20 +0700 Subject: [PATCH 02/25] chore: rebase nonfungibles pallet to pallet-nfts --- pallets/api/src/mock.rs | 2 +- pallets/api/src/nonfungibles/mod.rs | 3 ++- pallets/api/src/nonfungibles/tests.rs | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index f435fbfb9..af9ba39c3 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -166,8 +166,8 @@ type NftsInstance = pallet_nfts::Instance1; impl pallet_nfts::Config for Test { type ApprovalsLimit = ConstU32<10>; type AttributeDepositBase = ConstU128<1>; - type BalanceDeposit = ConstU128<1>; type CollectionApprovalDeposit = ConstU128<1>; + type CollectionBalanceDeposit = ConstU128<1>; type CollectionDeposit = ConstU128<2>; type CollectionId = u32; type CreateOrigin = AsEnsureOriginWithArg>; diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 19c031983..5cecdbdfe 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -591,7 +591,8 @@ pub mod pallet { .unwrap_or_default(), ), Allowance { collection, owner, operator, item } => ReadResult::Allowance( - NftsOf::::check_approval(&collection, &item, &owner, &operator).is_ok(), + NftsOf::::check_approval_permission(&collection, &item, &owner, &operator) + .is_ok(), ), OwnerOf { collection, item } => ReadResult::OwnerOf(NftsOf::::owner(collection, item)), diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index aec7c9dcb..cf1f91414 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -232,7 +232,7 @@ fn approve_works() { NonFungibles::approve(signed(owner), collection, Some(item), operator, true), Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) ); - assert_ok!(Nfts::check_approval(&collection, &Some(item), &owner, &operator)); + assert_ok!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator)); System::assert_last_event( Event::Approval { collection, item: Some(item), owner, operator, approved: true } .into(), @@ -242,7 +242,7 @@ fn approve_works() { NonFungibles::approve(signed(owner), collection, Some(item), operator, true), Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) ); - assert_ok!(Nfts::check_approval(&collection, &Some(item), &owner, &operator)); + assert_ok!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator)); System::assert_last_event( Event::Approval { collection, item: Some(item), owner, operator, approved: true } .into(), @@ -282,14 +282,14 @@ fn approve_collection_works() { ); let reserved_balance_after_approve = Balances::reserved_balance(&owner); assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); - assert_ok!(Nfts::check_approval(&collection, &None, &owner, &operator)); + assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); // Re-approving the transfer of `collection` does not require reserving additional funds. assert_eq!( NonFungibles::approve(signed(owner), collection, None, operator, true), Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) ); assert_eq!(Balances::reserved_balance(&owner), reserved_balance_after_approve); - assert_ok!(Nfts::check_approval(&collection, &None, &owner, &operator)); + assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); System::assert_last_event( Event::Approval { collection, item: None, owner, operator, approved: true }.into(), ); @@ -325,7 +325,7 @@ fn cancel_approval_works() { Ok(Some(NftsWeightInfoOf::::cancel_approval()).into()) ); assert_eq!( - Nfts::check_approval(&collection, &Some(item), &owner, &operator), + Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator), Err(NftsError::NoPermission.into()) ); }); @@ -349,7 +349,7 @@ fn cancel_collection_approval_works() { // Check error works for `Nfts::cancel_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, false), - NftsError::Unapproved + NftsError::NotDelegate .with_weight(NftsWeightInfoOf::::cancel_collection_approval()) ); // Successfully cancel the transfer collection approval of `operator` by `owner`. @@ -360,7 +360,7 @@ fn cancel_collection_approval_works() { Ok(Some(NftsWeightInfoOf::::cancel_collection_approval()).into()) ); assert_eq!( - Nfts::check_approval(&collection, &None, &owner, &operator), + Nfts::check_approval_permission(&collection, &None, &owner, &operator), Err(NftsError::NoPermission.into()) ); // Failed to transfer the item by `operator` without permission. @@ -392,7 +392,8 @@ fn clear_all_transfer_approvals_works() { // Successfully clear all transfer approvals. assert_ok!(NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item)); delegates.for_each(|delegate| { - assert!(Nfts::check_approval(&collection, &Some(item), &owner, &delegate).is_err()); + assert!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &delegate) + .is_err()); }); }); } @@ -740,7 +741,7 @@ fn allowance_works() { assert_eq!( NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) .encode(), - Nfts::check_approval(&collection, &Some(item), &owner, &operator) + Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator) .is_ok() .encode() ); From 5a272f9555fc79a9b48f4815df7341dc29677cf2 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:48:33 +0700 Subject: [PATCH 03/25] chore: update nonfungibles tests --- pallets/api/src/nonfungibles/mod.rs | 25 +- pallets/api/src/nonfungibles/tests.rs | 320 +++++++++++++------------- 2 files changed, 166 insertions(+), 179 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 5cecdbdfe..0af9a42dd 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -362,7 +362,7 @@ pub mod pallet { /// /// # Parameters /// - `collection` - The collection whose item's metadata to set. - /// - `maybe_item` - The item whose metadata to set. + /// - `item` - 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. @@ -383,7 +383,7 @@ pub mod pallet { /// /// # Parameters /// - `collection` - The collection whose item's metadata to clear. - /// - `maybe_item` - The item whose metadata to clear. + /// - `item` - The item whose metadata to clear. /// - `namespace` - Attribute's namespace. /// - `key` - The key of the attribute. #[pallet::call_index(13)] @@ -415,6 +415,11 @@ pub mod pallet { NftsOf::::set_metadata(origin, collection, item, data) } + /// Clear the metadata for an item. + /// + /// # Parameters + /// - `collection` - The collection whose item's metadata to clear. + /// - `item` - The item whose metadata to clear. #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( @@ -508,13 +513,7 @@ pub mod pallet { ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; let mint_price = witness.mint_price; - NftsOf::::mint( - origin, - collection, - item, - T::Lookup::unlookup(to.clone()), - Some(witness), - )?; + NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to), Some(witness))?; Self::deposit_event(Event::Transfer { collection, item, @@ -570,8 +569,8 @@ pub mod pallet { OwnerOf { .. } => ::WeightInfo::owner_of(), GetAttribute { .. } => ::WeightInfo::get_attribute(), Collection(_) => ::WeightInfo::collection(), - ItemMetadata { .. } => ::WeightInfo::item_metadata(), NextCollectionId => ::WeightInfo::next_collection_id(), + ItemMetadata { .. } => ::WeightInfo::item_metadata(), } } @@ -602,12 +601,12 @@ pub mod pallet { ), Collection(collection) => ReadResult::Collection(CollectionOf::::get(collection)), - ItemMetadata { collection, item } => ReadResult::ItemMetadata( - NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), - ), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), ), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), + ), } } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index cf1f91414..ea756d321 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -109,11 +109,14 @@ fn transfer_works() { ); // Successfully transfer a collection item. nfts::create_collection_mint(owner, owner, item); - let balance_before_transfer = nfts::balance_of(collection, &dest); + let from_balance_before_transfer = nfts::balance_of(collection, &owner); + let to_balance_before_transfer = nfts::balance_of(collection, &dest); assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); - let balance_after_transfer = nfts::balance_of(collection, &dest); - assert!(nfts::balance_of(collection, &owner).is_zero()); - assert_eq!(balance_after_transfer - balance_before_transfer, 1); + let from_balance_after_transfer = nfts::balance_of(collection, &owner); + let to_balance_after_transfer = nfts::balance_of(collection, &dest); + // Check that `to` has received the `value` tokens from `from`. + assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); + assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); System::assert_last_event( Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } .into(), @@ -206,169 +209,166 @@ fn burn_works() { }); } -#[test] -fn approve_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; - let operator = BOB; - let owner = ALICE; +mod approve { + use super::*; - // Origin checks for `approve`. - for origin in vec![root(), none()] { + #[test] + fn ensure_origin_works() { + new_test_ext().execute_with(|| { + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::approve(origin, COLLECTION, Some(ITEM), ALICE, false), + BadOrigin + ); + } + }); + } + + #[test] + fn approve_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Check error works for `Nfts::approve_transfer()`. assert_noop!( - NonFungibles::approve(origin, collection, Some(item), operator, true), - BadOrigin + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::approve_transfer()) ); - } - // Check error works for `Nfts::approve_transfer()`. - assert_noop!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::approve_transfer()) - ); - nfts::create_collection_mint(owner, owner, item); - // Successfully approves `operator` to transfer the collection item. - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) - ); - assert_ok!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator)); - System::assert_last_event( - Event::Approval { collection, item: Some(item), owner, operator, approved: true } - .into(), - ); - // Re-approves `operator` to transfer the collection item. - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) - ); - assert_ok!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator)); - System::assert_last_event( - Event::Approval { collection, item: Some(item), owner, operator, approved: true } - .into(), - ); - // Successfully transfers the item by the delegated account `operator`. - assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); - }); -} - -#[test] -fn approve_collection_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; - let operator = BOB; - let owner = ALICE; + nfts::create_collection_mint(owner, owner, item); + // Successfully approves `operator` to transfer the collection item. + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + ); + assert_ok!(Nfts::check_approval_permission( + &collection, + &Some(item), + &owner, + &operator + )); + System::assert_last_event( + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), + ); + // Re-approves `operator` to transfer the collection item. + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, true), + Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + ); + assert_ok!(Nfts::check_approval_permission( + &collection, + &Some(item), + &owner, + &operator + )); + System::assert_last_event( + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), + ); + // Successfully transfers the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); + }); + } - // Origin checks. - for origin in vec![root(), none()] { + #[test] + fn approve_collection_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Check error works for `Nfts::approve_collection_transfer()`. assert_noop!( - NonFungibles::approve(origin, collection, None, operator, true), - BadOrigin + NonFungibles::approve(signed(owner), collection, None, operator, true), + NftsError::NoItemOwned + .with_weight(NftsWeightInfoOf::::approve_collection_transfer()) ); - } - // Check error works for `Nfts::approve_collection_transfer()`. - assert_noop!( - NonFungibles::approve(signed(owner), collection, None, operator, true), - NftsError::NoItemOwned - .with_weight(NftsWeightInfoOf::::approve_collection_transfer()) - ); - // Approving to transfer `collection` reserves funds from the `operator`. - nfts::create_collection_mint(owner, owner, item); - let reserved_balance_before_approve = Balances::reserved_balance(&owner); - assert_eq!( - NonFungibles::approve(signed(owner), collection, None, operator, true), - Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) - ); - let reserved_balance_after_approve = Balances::reserved_balance(&owner); - assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); - assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); - // Re-approving the transfer of `collection` does not require reserving additional funds. - assert_eq!( - NonFungibles::approve(signed(owner), collection, None, operator, true), - Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) - ); - assert_eq!(Balances::reserved_balance(&owner), reserved_balance_after_approve); - assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); - System::assert_last_event( - Event::Approval { collection, item: None, owner, operator, approved: true }.into(), - ); - // Successfully transfer the item by the delegated account `operator`. - assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); - }); -} - -#[test] -fn cancel_approval_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; - let operator = BOB; - let owner = ALICE; + // Approving to transfer `collection` reserves funds from the `operator`. + nfts::create_collection_mint(owner, owner, item); + let reserved_balance_before_approve = Balances::reserved_balance(&owner); + assert_eq!( + NonFungibles::approve(signed(owner), collection, None, operator, true), + Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) + ); + let reserved_balance_after_approve = Balances::reserved_balance(&owner); + assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); + assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); + System::assert_last_event( + Event::Approval { collection, item: None, owner, operator, approved: true }.into(), + ); + // Successfully transfer the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); + }); + } - // Origin checks. - for origin in vec![root(), none()] { + #[test] + fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Check error works for `Nfts::cancel_approval()`. assert_noop!( - NonFungibles::approve(origin, collection, Some(item), operator, false), - BadOrigin + NonFungibles::approve(signed(owner), collection, Some(item), operator, false), + NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::cancel_approval()) ); - } - // Check error works for `Nfts::cancel_approval()`. - assert_noop!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::cancel_approval()) - ); - // Successfully cancels the transfer approval of `operator` by `owner`. - nfts::create_collection_mint_and_approve(owner, owner, item, operator); - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - Ok(Some(NftsWeightInfoOf::::cancel_approval()).into()) - ); - assert_eq!( - Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator), - Err(NftsError::NoPermission.into()) - ); - }); -} + // Successfully cancels the transfer approval of `operator` by `owner`. + nfts::create_collection_mint_and_approve(owner, owner, item, operator); + assert_eq!( + NonFungibles::approve(signed(owner), collection, Some(item), operator, false), + Ok(Some(NftsWeightInfoOf::::cancel_approval()).into()) + ); + assert_eq!( + Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator), + Err(NftsError::NoPermission.into()) + ); + }); + } -#[test] -fn cancel_collection_approval_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; - let operator = BOB; - let owner = ALICE; + #[test] + fn cancel_collection_approval_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let operator = BOB; + let owner = ALICE; + + // Check error works for `Nfts::cancel_approval()`. + assert_noop!( + NonFungibles::approve(signed(owner), collection, None, operator, false), + NftsError::NotDelegate + .with_weight(NftsWeightInfoOf::::cancel_collection_approval()) + ); - // Origin checks. - for origin in vec![root(), none()] { + nfts::create_collection_mint(owner, owner, item); + assert_ok!(Nfts::approve_collection_transfer( + signed(owner), + collection, + operator, + None + )); + // Successfully cancel the transfer collection approval of `operator` by `owner`. + assert_eq!( + NonFungibles::approve(signed(owner), collection, None, operator, false), + Ok(Some(NftsWeightInfoOf::::cancel_collection_approval()).into()) + ); + assert_eq!( + Nfts::check_approval_permission(&collection, &None, &owner, &operator), + Err(NftsError::NoPermission.into()) + ); + // Failed to transfer the item by `operator` without permission. assert_noop!( - NonFungibles::approve(origin, collection, None, operator, false), - BadOrigin + Nfts::transfer(signed(operator), collection, item, operator), + NftsError::NoPermission ); - } - // Check error works for `Nfts::cancel_approval()`. - assert_noop!( - NonFungibles::approve(signed(owner), collection, None, operator, false), - NftsError::NotDelegate - .with_weight(NftsWeightInfoOf::::cancel_collection_approval()) - ); - // Successfully cancel the transfer collection approval of `operator` by `owner`. - nfts::create_collection_mint(owner, owner, item); - assert_ok!(Nfts::approve_collection_transfer(signed(owner), collection, operator, None)); - assert_eq!( - NonFungibles::approve(signed(owner), collection, None, operator, false), - Ok(Some(NftsWeightInfoOf::::cancel_collection_approval()).into()) - ); - assert_eq!( - Nfts::check_approval_permission(&collection, &None, &owner, &operator), - Err(NftsError::NoPermission.into()) - ); - // Failed to transfer the item by `operator` without permission. - assert_noop!( - Nfts::transfer(signed(operator), collection, item, operator), - NftsError::NoPermission - ); - }); + }); + } } #[test] @@ -891,18 +891,6 @@ fn next_collection_id_works() { }); } -fn signed(account_id: AccountId) -> RuntimeOrigin { - RuntimeOrigin::signed(account_id) -} - -fn root() -> RuntimeOrigin { - RuntimeOrigin::root() -} - -fn none() -> RuntimeOrigin { - RuntimeOrigin::none() -} - // Helper functions for interacting with pallet-nfts. mod nfts { use super::*; From 2fccbcb344b0f77df348ab1a0e000165e3e43c78 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:00:11 +0700 Subject: [PATCH 04/25] refactor: do_approve and do_cancel_approval --- pallets/api/src/nonfungibles/impls.rs | 47 +++++++++------------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index 550872972..a7d1b04ac 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -6,8 +6,8 @@ use sp_runtime::traits::StaticLookup; use super::{pallet::*, AccountIdOf, CollectionIdOf, ItemIdOf, NftsOf, NftsWeightInfoOf}; impl Pallet { - /// Approves the transfer of a specific item or all collection items owned by the origin to - /// an operator. + /// Approves the transfer of a specific item or all collection items owned by the `owner` to an + /// `operator`. /// /// # Parameters /// - `owner` - The owner of the specified collection item(s). @@ -22,26 +22,18 @@ impl Pallet { maybe_item: Option>, operator: &AccountIdOf, ) -> DispatchResultWithPostInfo { + let operator = T::Lookup::unlookup(operator.clone()); Ok(Some(match maybe_item { Some(item) => { - NftsOf::::approve_transfer( - owner, - collection, - item, - T::Lookup::unlookup(operator.clone()), - None, - ) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_transfer()))?; + NftsOf::::approve_transfer(owner, collection, item, operator, None) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_transfer()))?; NftsWeightInfoOf::::approve_transfer() }, None => { - NftsOf::::approve_collection_transfer( - owner, - collection, - T::Lookup::unlookup(operator.clone()), - None, - ) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_collection_transfer()))?; + NftsOf::::approve_collection_transfer(owner, collection, operator, None) + .map_err(|e| { + e.with_weight(NftsWeightInfoOf::::approve_collection_transfer()) + })?; NftsWeightInfoOf::::approve_collection_transfer() }, }) @@ -49,7 +41,7 @@ impl Pallet { } /// Cancel an approval to transfer a specific item or all items within a collection owned by - /// the origin. + /// the `owner`. /// /// # Parameters /// - `owner` - The owner of the specified collection item(s). @@ -65,24 +57,17 @@ impl Pallet { maybe_item: Option>, operator: &AccountIdOf, ) -> DispatchResultWithPostInfo { + let operator = T::Lookup::unlookup(operator.clone()); Ok(Some(match maybe_item { Some(item) => { - NftsOf::::cancel_approval( - owner, - collection, - item, - T::Lookup::unlookup(operator.clone()), - ) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_approval()))?; + NftsOf::::cancel_approval(owner, collection, item, operator) + .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_approval()))?; NftsWeightInfoOf::::cancel_approval() }, None => { - NftsOf::::cancel_collection_approval( - owner, - collection, - T::Lookup::unlookup(operator.clone()), - ) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_collection_approval()))?; + NftsOf::::cancel_collection_approval(owner, collection, operator).map_err( + |e| e.with_weight(NftsWeightInfoOf::::cancel_collection_approval()), + )?; NftsWeightInfoOf::::cancel_collection_approval() }, }) From 357170909bdb75ca52c9b9464cef653eb2089fdf Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:14:03 +0700 Subject: [PATCH 05/25] chore: naming --- pallets/api/src/nonfungibles/impls.rs | 8 ++++---- pallets/api/src/nonfungibles/mod.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index a7d1b04ac..012bb2fa2 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -14,8 +14,8 @@ impl Pallet { /// - `collection` - The identifier of the collection. /// - `maybe_item` - The optional item of the collection to be approved for delegated transfer. /// If `None`, the approval applies to all `owner`'s collection items. - /// - `operator`: The account that will be allowed to take control of the specified item or all - /// owner's collection items. + /// - `operator`: The account to delegate permission to transfer a specified collection item or + /// all collection items owned by the `owner`. pub(crate) fn do_approve( owner: OriginFor, collection: CollectionIdOf, @@ -49,8 +49,8 @@ impl Pallet { /// - `maybe_item` - The optional item of the collection that the operator has an approval to /// transfer. If not provided, an approval to transfer all `owner`'s collection items will be /// cancelled. - /// - `operator` - The account that had permission to transfer the sepcified item or all owner's - /// collection items. + /// - `operator`: The account that had permission to transfer a specified collection item or all + /// collection items owned by the `owner`. pub(crate) fn do_cancel_approval( owner: OriginFor, collection: CollectionIdOf, diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 0af9a42dd..ebe4e613b 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -259,13 +259,13 @@ pub mod pallet { /// collection item(s). /// - `approved` - A boolean indicating the desired approval status: /// - `true` to approve the `operator`. - /// - `false` to cancel the approval granted to the `operator`. + /// - `false` to cancel the approval delegated to the `operator`. #[pallet::call_index(4)] #[pallet::weight( NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::approve_collection_transfer() + NftsWeightInfoOf::::cancel_collection_approval() + - NftsWeightInfoOf::::cancel_approval() + NftsWeightInfoOf::::cancel_approval() )] pub fn approve( origin: OriginFor, @@ -578,9 +578,9 @@ pub mod pallet { /// /// # Parameters /// - `request` - The read request. - fn read(value: Self::Read) -> Self::Result { + fn read(request: Self::Read) -> Self::Result { use Read::*; - match value { + match request { TotalSupply(collection) => ReadResult::TotalSupply( NftsOf::::collection_items(collection).unwrap_or_default() as u128, ), From 67774402d8a0d7719bce794f422cac7f25adf3c1 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:18:47 +0700 Subject: [PATCH 06/25] chore: resolve review comments --- pallets/api/src/nonfungibles/benchmarking.rs | 67 +++++++++++++++++++- pallets/api/src/nonfungibles/mod.rs | 44 +++++-------- pallets/api/src/nonfungibles/tests.rs | 16 ++--- 3 files changed, 85 insertions(+), 42 deletions(-) diff --git a/pallets/api/src/nonfungibles/benchmarking.rs b/pallets/api/src/nonfungibles/benchmarking.rs index 45d9d5bd2..b282241ec 100644 --- a/pallets/api/src/nonfungibles/benchmarking.rs +++ b/pallets/api/src/nonfungibles/benchmarking.rs @@ -1,11 +1,24 @@ //! Benchmarking setup for pallet_api::nonfungibles use frame_benchmarking::{account, v2::*}; -use frame_support::{traits::nonfungibles_v2::Inspect, BoundedVec}; +use frame_support::{ + assert_ok, + traits::tokens::nonfungibles_v2::{Create, Mutate}, + BoundedVec, +}; +use frame_system::RawOrigin; use sp_runtime::traits::Zero; -use super::{AttributeNamespace, CollectionIdOf, Config, ItemIdOf, NftsInstanceOf, Pallet, Read}; -use crate::Read as _; +use super::{ + AttributeNamespace, CollectionIdOf, Config, ItemIdOf, NftsInstanceOf, NftsOf, Pallet, Read, +}; +use crate::{ + nonfungibles::{ + AccountIdOf, Call, CollectionConfig, CollectionConfigFor, CollectionSettings, Inspect, + ItemConfig, ItemSettings, MintSettings, + }, + Read as _, +}; const SEED: u32 = 1; @@ -17,6 +30,54 @@ const SEED: u32 = 1; mod benchmarks { use super::*; + // Parameter: + // - 'a': whether `approved` is true or false. + // - 'i': whether `item` is provided. + #[benchmark] + fn approve(a: Linear<0, 1>, i: Linear<0, 1>) -> Result<(), BenchmarkError> { + let item_id = ItemIdOf::::zero(); + let collection_id = CollectionIdOf::::zero(); + let owner: AccountIdOf = account("Alice", 0, SEED); + let operator: AccountIdOf = account("Bob", 0, SEED); + + assert_ok!( + as Create, CollectionConfigFor>>::create_collection( + &owner, + &owner, + &CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } + ) + ); + assert_ok!( as Mutate, ItemConfig>>::mint_into( + &collection_id, + &item_id, + &owner, + &ItemConfig { settings: ItemSettings::all_enabled() }, + false + )); + + let approved = a == 0; + let maybe_item = if i == 0 { None } else { Some(item_id) }; + + #[extrinsic_call] + _(RawOrigin::Signed(owner.clone()), collection_id, maybe_item, operator.clone(), approved); + + assert!( + NftsOf::::check_approval_permission( + &collection_id, + &Some(item_id), + &owner, + &operator + ) + .is_ok() == approved + ); + + Ok(()) + } + #[benchmark] // Storage: `Collection` fn total_supply() { diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index ebe4e613b..74f21d1a9 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -8,8 +8,8 @@ pub use pallet::*; use pallet_nfts::WeightInfo as NftsWeightInfoTrait; pub use pallet_nfts::{ AttributeNamespace, CancelAttributesApprovalWitness, CollectionConfig, CollectionDetails, - CollectionSetting, CollectionSettings, DestroyWitness, ItemDeposit, ItemDetails, ItemMetadata, - ItemSetting, MintSettings, MintType, MintWitness, + CollectionSetting, CollectionSettings, DestroyWitness, ItemConfig, ItemDeposit, ItemDetails, + ItemMetadata, ItemSetting, ItemSettings, MintSettings, MintType, MintWitness, }; use sp_runtime::traits::StaticLookup; use weights::WeightInfo; @@ -22,10 +22,6 @@ mod tests; pub mod weights; type AccountIdOf = ::AccountId; -type NftsOf = pallet_nfts::Pallet>; -type NftsErrorOf = pallet_nfts::Error>; -type NftsWeightInfoOf = >>::WeightInfo; -type NftsInstanceOf = ::NftsInstance; type BalanceOf = <>>::Currency as Currency< ::AccountId, >>::Balance; @@ -33,10 +29,15 @@ type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; type ItemIdOf = as Inspect<::AccountId>>::ItemId; type ItemPriceOf = BalanceOf; -type CollectionDetailsFor = CollectionDetails, BalanceOf>; +type CollectionDetailsOf = CollectionDetails, BalanceOf>; type AttributeNamespaceOf = AttributeNamespace>; type CollectionConfigFor = CollectionConfig, BlockNumberFor, CollectionIdOf>; + +type NftsErrorOf = pallet_nfts::Error>; +type NftsWeightInfoOf = >>::WeightInfo; +pub(super) type NftsOf = pallet_nfts::Pallet>; +pub(super) type NftsInstanceOf = ::NftsInstance; // Type aliases for pallet-nfts storage items. pub(super) type AccountBalanceOf = pallet_nfts::AccountBalance>; pub(super) type AttributeOf = pallet_nfts::Attribute>; @@ -137,7 +138,7 @@ pub mod pallet { /// Attribute value of a specified collection item. GetAttribute(Option>), /// Details of a specified collection. - Collection(Option>), + Collection(Option>), /// Next collection ID. NextCollectionId(Option>), /// Metadata of a specified collection item. @@ -204,8 +205,6 @@ pub mod pallet { from: Option>, /// The recipient of the transfer. `None` when burning. to: Option>, - /// The price of the collection item. - price: Option>, }, /// Event emitted when a collection is created. Created { @@ -234,7 +233,6 @@ pub mod pallet { item: ItemIdOf, to: AccountIdOf, ) -> DispatchResult { - ensure_signed(origin.clone())?; let owner = NftsOf::::owner(collection, item).ok_or(NftsErrorOf::::UnknownItem)?; NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; @@ -243,7 +241,6 @@ pub mod pallet { item, from: Some(owner), to: Some(to), - price: None, }); Ok(()) } @@ -328,8 +325,6 @@ pub mod pallet { config: CollectionConfigFor, ) -> DispatchResult { let creator = ensure_signed(origin.clone())?; - // TODO: re-evaluate next collection id in nfts pallet. The `Incrementable` trait causes - // issues for setting it to xcm's `Location`. This can easily be done differently. let id = NextCollectionIdOf::::get() .or(T::CollectionId::initial_value()) .ok_or(NftsErrorOf::::UnknownCollection)?; @@ -512,15 +507,14 @@ pub mod pallet { witness: MintWitness, ItemPriceOf>, ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; - let mint_price = witness.mint_price; - NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to), Some(witness))?; - Self::deposit_event(Event::Transfer { + NftsOf::::mint( + origin, collection, item, - from: None, - to: Some(owner), - price: mint_price, - }); + T::Lookup::unlookup(to), + Some(witness.clone()), + )?; + Self::deposit_event(Event::Transfer { collection, item, from: None, to: Some(owner) }); Ok(()) } @@ -538,13 +532,7 @@ pub mod pallet { ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Transfer { - collection, - item, - from: Some(owner), - to: None, - price: None, - }); + Self::deposit_event(Event::Transfer { collection, item, from: Some(owner), to: None }); Ok(()) } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index ea756d321..a08670808 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -98,17 +98,13 @@ fn transfer_works() { let item = ITEM; let owner = ALICE; - // Origin checks. - for origin in vec![root(), none()] { - assert_noop!(NonFungibles::transfer(origin, collection, item, dest), BadOrigin); - } // Check error works for `Nfts::transfer`. assert_noop!( NonFungibles::transfer(signed(owner), collection, item, dest), NftsError::UnknownItem ); - // Successfully transfer a collection item. nfts::create_collection_mint(owner, owner, item); + // Successfully transfer a collection item. let from_balance_before_transfer = nfts::balance_of(collection, &owner); let to_balance_before_transfer = nfts::balance_of(collection, &dest); assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); @@ -118,8 +114,7 @@ fn transfer_works() { assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } - .into(), + Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), ); }); } @@ -146,8 +141,7 @@ fn approved_transfer_works() { assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } - .into(), + Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), ); }); } @@ -179,7 +173,7 @@ fn mint_works() { let balance_after_mint = nfts::balance_of(collection, &owner); assert_eq!(balance_after_mint, balance_before_mint + 1); System::assert_last_event( - Event::Transfer { collection, item, from: None, to: Some(owner), price: None }.into(), + Event::Transfer { collection, item, from: None, to: Some(owner) }.into(), ); }); } @@ -204,7 +198,7 @@ fn burn_works() { let balance_after_burn = nfts::balance_of(collection, &owner); assert_eq!(balance_after_burn, balance_before_burn - 1); System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), + Event::Transfer { collection, item, from: Some(owner), to: None }.into(), ); }); } From 9cf85021014963ff16c3027c8f09180f4b367629 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:26:00 +0700 Subject: [PATCH 07/25] chore: resolve review comments & update weights --- pallets/api/src/nonfungibles/benchmarking.rs | 93 ++++++++++--- pallets/api/src/nonfungibles/impls.rs | 24 ++-- pallets/api/src/nonfungibles/mod.rs | 16 +-- pallets/api/src/nonfungibles/tests.rs | 60 ++++---- pallets/api/src/nonfungibles/weights.rs | 136 +++++++++++++------ runtime/devnet/src/config/api/mod.rs | 83 +++++++++-- runtime/devnet/src/config/assets.rs | 8 +- runtime/devnet/src/lib.rs | 8 +- 8 files changed, 290 insertions(+), 138 deletions(-) diff --git a/pallets/api/src/nonfungibles/benchmarking.rs b/pallets/api/src/nonfungibles/benchmarking.rs index b282241ec..340939591 100644 --- a/pallets/api/src/nonfungibles/benchmarking.rs +++ b/pallets/api/src/nonfungibles/benchmarking.rs @@ -3,25 +3,31 @@ use frame_benchmarking::{account, v2::*}; use frame_support::{ assert_ok, - traits::tokens::nonfungibles_v2::{Create, Mutate}, + traits::{ + tokens::nonfungibles_v2::{Create, Mutate}, + Currency, + }, BoundedVec, }; use frame_system::RawOrigin; -use sp_runtime::traits::Zero; +use sp_runtime::traits::{Bounded, StaticLookup, Zero}; use super::{ - AttributeNamespace, CollectionIdOf, Config, ItemIdOf, NftsInstanceOf, NftsOf, Pallet, Read, -}; -use crate::{ - nonfungibles::{ - AccountIdOf, Call, CollectionConfig, CollectionConfigFor, CollectionSettings, Inspect, - ItemConfig, ItemSettings, MintSettings, - }, - Read as _, + AccountIdOf, AttributeNamespace, BalanceOf, Call, CollectionConfig, CollectionConfigFor, + CollectionIdOf, CollectionSettings, Config, Inspect, ItemConfig, ItemIdOf, ItemSettings, + MintSettings, NftsInstanceOf, NftsOf, Pallet, Read, }; +use crate::Read as _; const SEED: u32 = 1; +// See if `generic_event` has been emitted. +fn assert_has_event( + generic_event: >>::RuntimeEvent, +) { + frame_system::Pallet::::assert_has_event(generic_event.into()); +} + #[benchmarks( where > as Inspect<::AccountId>>::ItemId: Zero, @@ -35,11 +41,14 @@ mod benchmarks { // - 'i': whether `item` is provided. #[benchmark] fn approve(a: Linear<0, 1>, i: Linear<0, 1>) -> Result<(), BenchmarkError> { - let item_id = ItemIdOf::::zero(); let collection_id = CollectionIdOf::::zero(); + let item_id = ItemIdOf::::zero(); let owner: AccountIdOf = account("Alice", 0, SEED); let operator: AccountIdOf = account("Bob", 0, SEED); + let operator_lookup = T::Lookup::unlookup(operator.clone()); + let origin = RawOrigin::Signed(owner.clone()); + T::Currency::make_free_balance_be(&owner, BalanceOf::::max_value()); assert_ok!( as Create, CollectionConfigFor>>::create_collection( &owner, @@ -59,22 +68,62 @@ mod benchmarks { false )); - let approved = a == 0; - let maybe_item = if i == 0 { None } else { Some(item_id) }; + let (approved, maybe_item) = match (a, i) { + (0, 0) => { + NftsOf::::approve_collection_transfer( + origin.clone().into(), + collection_id, + operator_lookup, + None, + )?; + (false, None) + }, + (0, 1) => { + NftsOf::::approve_transfer( + origin.clone().into(), + collection_id, + item_id, + operator_lookup, + None, + )?; + (false, Some(item_id)) + }, + (1, 0) => (true, None), + (1, 1) => (true, Some(item_id)), + _ => unreachable!("values can only be 0 or 1"), + }; #[extrinsic_call] - _(RawOrigin::Signed(owner.clone()), collection_id, maybe_item, operator.clone(), approved); + _(origin, collection_id, maybe_item, operator.clone(), approved); assert!( - NftsOf::::check_approval_permission( - &collection_id, - &Some(item_id), - &owner, - &operator - ) - .is_ok() == approved + NftsOf::::check_approval_permission(&collection_id, &maybe_item, &owner, &operator) + .is_ok() == approved ); + if approved { + assert_has_event::( + pallet_nfts::Event::TransferApproved { + collection: collection_id, + item: maybe_item, + owner, + delegate: operator, + deadline: None, + } + .into(), + ); + } else { + assert_has_event::( + pallet_nfts::Event::ApprovalCancelled { + collection: collection_id, + item: maybe_item, + owner, + delegate: operator, + } + .into(), + ); + } + Ok(()) } @@ -168,4 +217,6 @@ mod benchmarks { }); } } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index 012bb2fa2..07a831a08 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -1,9 +1,8 @@ use frame_support::dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}; use frame_system::pallet_prelude::*; -use pallet_nfts::WeightInfo as NftsWeightInfoTrait; use sp_runtime::traits::StaticLookup; -use super::{pallet::*, AccountIdOf, CollectionIdOf, ItemIdOf, NftsOf, NftsWeightInfoOf}; +use super::{pallet::*, AccountIdOf, CollectionIdOf, ItemIdOf, NftsOf, WeightInfo}; impl Pallet { /// Approves the transfer of a specific item or all collection items owned by the `owner` to an @@ -26,15 +25,13 @@ impl Pallet { Ok(Some(match maybe_item { Some(item) => { NftsOf::::approve_transfer(owner, collection, item, operator, None) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::approve_transfer()))?; - NftsWeightInfoOf::::approve_transfer() + .map_err(|e| e.with_weight(::WeightInfo::approve(1, 1)))?; + ::WeightInfo::approve(1, 1) }, None => { NftsOf::::approve_collection_transfer(owner, collection, operator, None) - .map_err(|e| { - e.with_weight(NftsWeightInfoOf::::approve_collection_transfer()) - })?; - NftsWeightInfoOf::::approve_collection_transfer() + .map_err(|e| e.with_weight(::WeightInfo::approve(1, 0)))?; + ::WeightInfo::approve(1, 0) }, }) .into()) @@ -61,14 +58,13 @@ impl Pallet { Ok(Some(match maybe_item { Some(item) => { NftsOf::::cancel_approval(owner, collection, item, operator) - .map_err(|e| e.with_weight(NftsWeightInfoOf::::cancel_approval()))?; - NftsWeightInfoOf::::cancel_approval() + .map_err(|e| e.with_weight(::WeightInfo::approve(0, 1)))?; + ::WeightInfo::approve(0, 1) }, None => { - NftsOf::::cancel_collection_approval(owner, collection, operator).map_err( - |e| e.with_weight(NftsWeightInfoOf::::cancel_collection_approval()), - )?; - NftsWeightInfoOf::::cancel_collection_approval() + NftsOf::::cancel_collection_approval(owner, collection, operator) + .map_err(|e| e.with_weight(::WeightInfo::approve(0, 0)))?; + ::WeightInfo::approve(0, 0) }, }) .into()) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 74f21d1a9..5d2c8685d 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -12,7 +12,7 @@ pub use pallet_nfts::{ ItemMetadata, ItemSetting, ItemSettings, MintSettings, MintType, MintWitness, }; use sp_runtime::traits::StaticLookup; -use weights::WeightInfo; +pub(crate) use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -22,9 +22,6 @@ mod tests; pub mod weights; type AccountIdOf = ::AccountId; -type BalanceOf = <>>::Currency as Currency< - ::AccountId, ->>::Balance; type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; type ItemIdOf = as Inspect<::AccountId>>::ItemId; @@ -36,6 +33,10 @@ type CollectionConfigFor = type NftsErrorOf = pallet_nfts::Error>; type NftsWeightInfoOf = >>::WeightInfo; +pub(super) type BalanceOf = + <>>::Currency as Currency< + ::AccountId, + >>::Balance; pub(super) type NftsOf = pallet_nfts::Pallet>; pub(super) type NftsInstanceOf = ::NftsInstance; // Type aliases for pallet-nfts storage items. @@ -258,12 +259,7 @@ pub mod pallet { /// - `true` to approve the `operator`. /// - `false` to cancel the approval delegated to the `operator`. #[pallet::call_index(4)] - #[pallet::weight( - NftsWeightInfoOf::::approve_transfer() + - NftsWeightInfoOf::::approve_collection_transfer() + - NftsWeightInfoOf::::cancel_collection_approval() + - NftsWeightInfoOf::::cancel_approval() - )] + #[pallet::weight(::WeightInfo::approve(*approved as u32, item.is_some() as u32))] pub fn approve( origin: OriginFor, collection: CollectionIdOf, diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index a08670808..bb0a9eb59 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -4,15 +4,15 @@ use frame_support::{ dispatch::WithPostDispatchInfo, sp_runtime::{traits::Zero, BoundedVec, DispatchError::BadOrigin}, }; -use pallet_nfts::{Config, WeightInfo as NftsWeightInfoTrait}; +use pallet_nfts::WeightInfo as NftsWeightInfoTrait; use crate::{ mock::*, nonfungibles::{ - AccountBalanceOf, AttributeNamespace, AttributeOf, BlockNumberFor, - CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, CollectionOf, - CollectionSettings, DestroyWitness, ItemIdOf, MintSettings, MintWitness, - NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, + weights::WeightInfo as WeightInfoTrait, AccountBalanceOf, AttributeNamespace, AttributeOf, + BlockNumberFor, CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, + CollectionOf, CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, + MintWitness, NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, }, Read, }; @@ -20,9 +20,10 @@ use crate::{ const COLLECTION: u32 = 0; const ITEM: u32 = 1; -type NftsError = pallet_nfts::Error>; -type Event = crate::nonfungibles::Event; type CollectionApprovals = pallet_nfts::CollectionApprovals>; +type Event = crate::nonfungibles::Event; +type NftsError = pallet_nfts::Error>; +type WeightInfo = ::WeightInfo; mod encoding_read_result { use super::*; @@ -229,13 +230,13 @@ mod approve { // Check error works for `Nfts::approve_transfer()`. assert_noop!( NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::approve_transfer()) + NftsError::UnknownItem.with_weight(WeightInfo::approve(1, 1)) ); nfts::create_collection_mint(owner, owner, item); // Successfully approves `operator` to transfer the collection item. assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + Ok(Some(WeightInfo::approve(1, 1)).into()) ); assert_ok!(Nfts::check_approval_permission( &collection, @@ -250,7 +251,7 @@ mod approve { // Re-approves `operator` to transfer the collection item. assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(NftsWeightInfoOf::::approve_transfer()).into()) + Ok(Some(WeightInfo::approve(1, 1)).into()) ); assert_ok!(Nfts::check_approval_permission( &collection, @@ -278,15 +279,14 @@ mod approve { // Check error works for `Nfts::approve_collection_transfer()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, true), - NftsError::NoItemOwned - .with_weight(NftsWeightInfoOf::::approve_collection_transfer()) + NftsError::NoItemOwned.with_weight(WeightInfo::approve(1, 0)) ); // Approving to transfer `collection` reserves funds from the `operator`. nfts::create_collection_mint(owner, owner, item); let reserved_balance_before_approve = Balances::reserved_balance(&owner); assert_eq!( NonFungibles::approve(signed(owner), collection, None, operator, true), - Ok(Some(NftsWeightInfoOf::::approve_collection_transfer()).into()) + Ok(Some(WeightInfo::approve(1, 0)).into()) ); let reserved_balance_after_approve = Balances::reserved_balance(&owner); assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); @@ -310,13 +310,13 @@ mod approve { // Check error works for `Nfts::cancel_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - NftsError::UnknownItem.with_weight(NftsWeightInfoOf::::cancel_approval()) + NftsError::UnknownItem.with_weight(WeightInfo::approve(0, 1)) ); // Successfully cancels the transfer approval of `operator` by `owner`. nfts::create_collection_mint_and_approve(owner, owner, item, operator); assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - Ok(Some(NftsWeightInfoOf::::cancel_approval()).into()) + Ok(Some(WeightInfo::approve(0, 1)).into()) ); assert_eq!( Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator), @@ -336,8 +336,7 @@ mod approve { // Check error works for `Nfts::cancel_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, false), - NftsError::NotDelegate - .with_weight(NftsWeightInfoOf::::cancel_collection_approval()) + NftsError::NotDelegate.with_weight(WeightInfo::approve(0, 0)) ); nfts::create_collection_mint(owner, owner, item); @@ -350,7 +349,7 @@ mod approve { // Successfully cancel the transfer collection approval of `operator` by `owner`. assert_eq!( NonFungibles::approve(signed(owner), collection, None, operator, false), - Ok(Some(NftsWeightInfoOf::::cancel_collection_approval()).into()) + Ok(Some(WeightInfo::approve(0, 0)).into()) ); assert_eq!( Nfts::check_approval_permission(&collection, &None, &owner, &operator), @@ -939,7 +938,7 @@ mod nfts { collection: CollectionIdOf, maybe_item: Option>, namespace: AttributeNamespaceOf, - key: BoundedVec>>::KeyLimit>, + key: BoundedVec>>::KeyLimit>, ) -> Option> { AttributeOf::::get((collection, maybe_item, namespace, key)) .map(|attribute| attribute.0.into()) @@ -950,7 +949,6 @@ mod read_weights { use frame_support::weights::Weight; use super::*; - use crate::nonfungibles::{weights::WeightInfo, Config}; struct ReadWeightInfo { total_supply: Weight, @@ -1007,14 +1005,14 @@ mod read_weights { total_supply, } = ReadWeightInfo::new(); - assert_eq!(total_supply, ::WeightInfo::total_supply()); - assert_eq!(balance_of, ::WeightInfo::balance_of()); - assert_eq!(allowance, ::WeightInfo::allowance()); - assert_eq!(owner_of, ::WeightInfo::owner_of()); - assert_eq!(get_attribute, ::WeightInfo::get_attribute()); - assert_eq!(collection, ::WeightInfo::collection()); - assert_eq!(next_collection_id, ::WeightInfo::next_collection_id()); - assert_eq!(item_metadata, ::WeightInfo::item_metadata()); + assert_eq!(total_supply, WeightInfo::total_supply()); + assert_eq!(balance_of, WeightInfo::balance_of()); + assert_eq!(allowance, WeightInfo::allowance()); + assert_eq!(owner_of, WeightInfo::owner_of()); + assert_eq!(get_attribute, WeightInfo::get_attribute()); + assert_eq!(collection, WeightInfo::collection()); + assert_eq!(next_collection_id, WeightInfo::next_collection_id()); + assert_eq!(item_metadata, WeightInfo::item_metadata()); } // These types read from the `Collection` storage. @@ -1041,12 +1039,12 @@ mod read_weights { } = ReadWeightInfo::new(); // These values come from `weights.rs`. - assert_eq!(total_supply.proof_size(), 3557); - assert_eq!(balance_of.proof_size(), 3529); + assert_eq!(total_supply.proof_size(), 3549); + assert_eq!(balance_of.proof_size(), 3585); assert_eq!(allowance.proof_size(), 4326); assert_eq!(owner_of.proof_size(), 4326); assert_eq!(get_attribute.proof_size(), 3944); - assert_eq!(collection.proof_size(), 3557); + assert_eq!(collection.proof_size(), 3549); assert_eq!(next_collection_id.proof_size(), 1489); assert_eq!(item_metadata.proof_size(), 3812); } diff --git a/pallets/api/src/nonfungibles/weights.rs b/pallets/api/src/nonfungibles/weights.rs index f0b8fa833..d5f874503 100644 --- a/pallets/api/src/nonfungibles/weights.rs +++ b/pallets/api/src/nonfungibles/weights.rs @@ -2,13 +2,13 @@ //! Autogenerated weights for `nonfungibles` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 -//! DATE: 2024-11-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `R0GUE`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/pop-node +// ./target/production/pop-node // benchmark // pallet // --chain=dev @@ -32,6 +32,7 @@ use core::marker::PhantomData; /// Weight functions needed for `nonfungibles`. pub trait WeightInfo { + fn approve(a: u32, i: u32, ) -> Weight; fn total_supply() -> Weight; fn balance_of() -> Weight; fn allowance() -> Weight; @@ -45,36 +46,61 @@ pub trait WeightInfo { /// Weights for `nonfungibles` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// 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`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, 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 `a` is `[0, 1]`. + /// The range of component `i` is `[0, 1]`. + fn approve(a: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `606` + // Estimated: `68341938741594600 + a * (3168 ±44_956_152_011_428) + i * (2663 ±729_819_634_434_010)` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(25_055_102, 68341938741594600) + // Standard Error: 195_002 + .saturating_add(Weight::from_parts(324_489, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 3168).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 2663).saturating_mul(i.into())) + } /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn total_supply() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3557` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 3557) + // Estimated: `3549` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Nfts::AccountBalance` (r:1 w:0) - /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) fn balance_of() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3529` + // Estimated: `3585` // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(3_000_000, 3529) + Weight::from_parts(2_000_000, 3585) .saturating_add(T::DbWeight::get().reads(1_u64)) } - /// Storage: `Nfts::Allowances` (r:1 w:0) - /// Proof: `Nfts::Allowances` (`max_values`: None, `max_size`: Some(109), added: 2584, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn allowance() -> Weight { // Proof Size summary in bytes: // Measured: `3` // Estimated: `4326` - // Minimum execution time: 7_000_000 picoseconds. - Weight::from_parts(7_000_000, 4326) + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(5_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) @@ -83,7 +109,7 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3` // Estimated: `4326` - // Minimum execution time: 2_000_000 picoseconds. + // Minimum execution time: 1_000_000 picoseconds. Weight::from_parts(2_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) } @@ -93,18 +119,18 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3` // Estimated: `3944` - // Minimum execution time: 5_000_000 picoseconds. - Weight::from_parts(5_000_000, 3944) + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(4_000_000, 3944) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn collection() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3557` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 3557) + // Estimated: `3549` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Nfts::NextCollectionId` (r:1 w:0) @@ -123,44 +149,69 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3` // Estimated: `3812` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(3_000_000, 3812) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3812) .saturating_add(T::DbWeight::get().reads(1_u64)) } } // For backwards compatibility and tests. impl WeightInfo for () { + /// 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`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, 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 `a` is `[0, 1]`. + /// The range of component `i` is `[0, 1]`. + fn approve(a: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `606` + // Estimated: `68341938741594600 + a * (3168 ±44_956_152_011_428) + i * (2663 ±729_819_634_434_010)` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(25_055_102, 68341938741594600) + // Standard Error: 195_002 + .saturating_add(Weight::from_parts(324_489, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 3168).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 2663).saturating_mul(i.into())) + } /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn total_supply() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3557` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 3557) + // Estimated: `3549` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Nfts::AccountBalance` (r:1 w:0) - /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) fn balance_of() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3529` + // Estimated: `3585` // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(3_000_000, 3529) + Weight::from_parts(2_000_000, 3585) .saturating_add(RocksDbWeight::get().reads(1_u64)) } - /// Storage: `Nfts::Allowances` (r:1 w:0) - /// Proof: `Nfts::Allowances` (`max_values`: None, `max_size`: Some(109), added: 2584, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn allowance() -> Weight { // Proof Size summary in bytes: // Measured: `3` // Estimated: `4326` - // Minimum execution time: 7_000_000 picoseconds. - Weight::from_parts(7_000_000, 4326) + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(5_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) @@ -169,7 +220,7 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3` // Estimated: `4326` - // Minimum execution time: 2_000_000 picoseconds. + // Minimum execution time: 1_000_000 picoseconds. Weight::from_parts(2_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) } @@ -179,18 +230,18 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3` // Estimated: `3944` - // Minimum execution time: 5_000_000 picoseconds. - Weight::from_parts(5_000_000, 3944) + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(4_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn collection() -> Weight { // Proof Size summary in bytes: // Measured: `3` - // Estimated: `3557` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 3557) + // Estimated: `3549` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Nfts::NextCollectionId` (r:1 w:0) @@ -209,9 +260,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3` // Estimated: `3812` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(3_000_000, 3812) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(1_u64)) } } - diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 8d635b0c5..6998a42fd 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -11,7 +11,8 @@ use sp_runtime::DispatchError; use versioning::*; use crate::{ - config::assets::TrustBackedAssetsInstance, fungibles, Runtime, RuntimeCall, RuntimeEvent, + config::assets::{TrustBackedAssetsInstance, TrustBackedNftsInstance}, + fungibles, nonfungibles, Runtime, RuntimeCall, RuntimeEvent, }; mod versioning; @@ -32,6 +33,9 @@ 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,6 +55,8 @@ 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)), } } } @@ -60,6 +67,8 @@ impl Readable for RuntimeRead { 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,12 @@ impl fungibles::Config for Runtime { type WeightInfo = fungibles::weights::SubstrateWeight; } +impl nonfungibles::Config for Runtime { + type NftsInstance = TrustBackedNftsInstance; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + #[derive(Default)] pub struct Config; impl pallet_api::extension::Config for Config { @@ -130,8 +146,8 @@ pub struct Filter(PhantomData); impl> Contains for Filter { fn contains(c: &RuntimeCall) -> bool { - use fungibles::Call::*; - T::BaseCallFilter::contains(c) && + let contain_fungibles: bool = { + use fungibles::Call::*; matches!( c, RuntimeCall::Fungibles( @@ -145,23 +161,62 @@ impl> Contains f mint { .. } | burn { .. } ) ) + }; + + let contain_nonfungibles: bool = + { + use nonfungibles::Call::*; + matches!( + c, + RuntimeCall::NonFungibles( + transfer { .. } | + approve { .. } | clear_all_transfer_approvals { .. } | + clear_collection_approvals { .. } | + 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) && (contain_fungibles | contain_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 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 { .. }, + ) ) - ) + }; + + contain_fungibles | contain_nonfungibles } } diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index 6a54f368c..39059a27a 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -41,7 +41,9 @@ parameter_types! { pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } -impl pallet_nfts::Config for Runtime { +pub(crate) type TrustBackedNftsInstance = pallet_nfts::Instance1; +pub type TrustBackedNftsCall = pallet_nfts::Call; +impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; type AttributeDepositBase = NftsAttributeDepositBase; @@ -92,8 +94,8 @@ impl pallet_nft_fractionalization::Config for Runtime { type Deposit = AssetDeposit; type NewAssetName = NewAssetName; type NewAssetSymbol = NewAssetSymbol; - type NftCollectionId = ::CollectionId; - type NftId = ::ItemId; + type NftCollectionId = >::CollectionId; + type NftId = >::ItemId; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; type RuntimeEvent = RuntimeEvent; diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index b51d120fe..d7270fdf9 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -43,7 +43,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::offchain::{Leaf, Proof, ProofKeys}; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; @@ -643,7 +643,7 @@ mod runtime { // Assets #[runtime::pallet_index(50)] - pub type Nfts = pallet_nfts::Pallet; + pub type Nfts = pallet_nfts::Pallet; #[runtime::pallet_index(51)] pub type NftFractionalization = pallet_nft_fractionalization::Pallet; #[runtime::pallet_index(52)] @@ -652,6 +652,9 @@ mod runtime { // Pop API #[runtime::pallet_index(150)] pub type Fungibles = fungibles::Pallet; + /// Non-fungible token queries. + #[runtime::pallet_index(151)] + pub type NonFungibles = nonfungibles::Pallet; } #[cfg(feature = "runtime-benchmarks")] @@ -659,6 +662,7 @@ mod benches { frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [fungibles, Fungibles] + [nonfungibles, NonFungibles] [pallet_balances, Balances] [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] From 9f17d66e2a673cc355f8745a1458fbb9b9ead215 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:38:07 +0700 Subject: [PATCH 08/25] chore: add tests for nonfungibles api in runtime devnet --- runtime/devnet/src/config/api/mod.rs | 107 ++++++++++++++++++++++++++- runtime/devnet/src/config/assets.rs | 31 +++++--- runtime/devnet/src/config/proxy.rs | 38 +++++----- 3 files changed, 145 insertions(+), 31 deletions(-) diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 6998a42fd..c7926a7f7 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -47,7 +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)), + RuntimeRead::NonFungibles(key) => nonfungibles::Pallet::weight(key), } } @@ -224,8 +224,9 @@ impl Contains for Filter { mod tests { use codec::Encode; use pallet_api::fungibles::Call::*; - use sp_core::crypto::AccountId32; - use RuntimeCall::{Balances, Fungibles}; + use pallet_nfts::MintWitness; + use sp_core::{bounded_vec, crypto::AccountId32}; + use RuntimeCall::{Balances, Fungibles, NonFungibles}; use super::*; @@ -236,6 +237,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] @@ -300,4 +305,100 @@ mod tests { assert!(Filter::::contains(&read)) } } + + #[test] + fn filter_allows_nonfungibles_calls() { + use pallet_api::nonfungibles::{ + Call::*, CollectionConfig, CollectionSettings, MintSettings, + }; + 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(clear_all_transfer_approvals { collection: 0, item: 0 }), + NonFungibles(clear_collection_approvals { collection: 0, limit: 0 }), + NonFungibles(create { + admin: ACCOUNT, + config: CollectionConfig { + max_supply: Some(0), + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), + }, + }), + 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, + witness: MintWitness { mint_price: None, owned_item: None }, + }), + NonFungibles(burn { collection: 0, item: 0 }), + ] + .iter() + { + assert!(Filter::::contains(call)) + } + } + + #[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: 1, + item: 1, + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: bounded_vec![], + }), + NonFungibles(Collection(1)), + NonFungibles(NextCollectionId), + NonFungibles(ItemMetadata { collection: 1, item: 1 }), + ] + .iter() + { + assert!(Filter::::contains(read)) + } + } } diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index 39059a27a..9c2dc88ad 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -1,4 +1,5 @@ use frame_support::{ + pallet_prelude::Get, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32}, BoundedVec, PalletId, @@ -41,6 +42,16 @@ 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 + } +} + pub(crate) type TrustBackedNftsInstance = pallet_nfts::Instance1; pub type TrustBackedNftsCall = pallet_nfts::Call; impl pallet_nfts::Config for Runtime { @@ -64,7 +75,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; @@ -137,19 +148,21 @@ mod tests { #[test] fn ensure_account_balance_deposit() { - let max_size = pallet_nfts::AccountBalance::::storage_info() - .first() - .and_then(|info| info.max_size) - .unwrap_or_default(); + let max_size = + pallet_nfts::AccountBalance::::storage_info() + .first() + .and_then(|info| info.max_size) + .unwrap_or_default(); assert_eq!(deposit(1, max_size), NftsCollectionBalanceDeposit::get()); } #[test] fn ensure_collection_approval_deposit() { - let max_size = pallet_nfts::CollectionApprovals::::storage_info() - .first() - .and_then(|info| info.max_size) - .unwrap_or_default(); + let max_size = + pallet_nfts::CollectionApprovals::::storage_info() + .first() + .and_then(|info| info.max_size) + .unwrap_or_default(); assert_eq!(deposit(1, max_size), NftsCollectionApprovalDeposit::get()); } } diff --git a/runtime/devnet/src/config/proxy.rs b/runtime/devnet/src/config/proxy.rs index ff70240e7..486530276 100644 --- a/runtime/devnet/src/config/proxy.rs +++ b/runtime/devnet/src/config/proxy.rs @@ -5,7 +5,7 @@ use pop_runtime_common::proxy::{ }; use sp_runtime::traits::BlakeTwo256; -use super::assets::TrustBackedAssetsCall; +use super::assets::{TrustBackedAssetsCall, TrustBackedNftsCall}; use crate::{Balances, Runtime, RuntimeCall, RuntimeEvent}; impl InstanceFilter for ProxyType { @@ -45,13 +45,13 @@ impl InstanceFilter for ProxyType { RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::transfer_ownership { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_team { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_max_supply { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_collection { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::create { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::destroy { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::redeposit { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::transfer_ownership { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_team { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_collection_max_supply { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_collection { .. }) | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), @@ -66,17 +66,17 @@ impl InstanceFilter for ProxyType { RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_attributes_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::unlock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_properties { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_collection_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::force_mint { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::update_mint_settings { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::mint_pre_signed { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_attributes_pre_signed { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_item_transfer { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::unlock_item_transfer { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_item_properties { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::clear_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_collection_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::clear_collection_metadata { .. }) | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), From 010b9cfbde6726e0faecd0d0a662d5780817593d Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:14:05 +0700 Subject: [PATCH 09/25] fix: integration test & return post dispatch weight for transfer() on error --- pallets/api/src/nonfungibles/mod.rs | 16 ++++++++++------ pallets/api/src/nonfungibles/tests.rs | 3 ++- pop-api/integration-tests/Cargo.lock | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 5d2c8685d..48ba1439f 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -2,7 +2,10 @@ //! 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::Inspect, Currency}; +use frame_support::{ + dispatch::WithPostDispatchInfo, + traits::{nonfungibles_v2::Inspect, Currency}, +}; use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; use pallet_nfts::WeightInfo as NftsWeightInfoTrait; @@ -227,15 +230,16 @@ pub mod pallet { /// - `item` - The item to transfer. /// - `to` - The recipient account. #[pallet::call_index(3)] - #[pallet::weight(NftsWeightInfoOf::::transfer() + T::DbWeight::get().reads_writes(1, 0))] + #[pallet::weight(NftsWeightInfoOf::::transfer() + T::DbWeight::get().reads(1))] pub fn transfer( origin: OriginFor, collection: CollectionIdOf, item: ItemIdOf, to: AccountIdOf, - ) -> DispatchResult { - let owner = - NftsOf::::owner(collection, item).ok_or(NftsErrorOf::::UnknownItem)?; + ) -> DispatchResultWithPostInfo { + let owner = NftsOf::::owner(collection, item) + .ok_or(NftsErrorOf::::UnknownItem) + .map_err(|e| e.with_weight(T::DbWeight::get().reads(1)))?; NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; Self::deposit_event(Event::Transfer { collection, @@ -243,7 +247,7 @@ pub mod pallet { from: Some(owner), to: Some(to), }); - Ok(()) + Ok(().into()) } /// Either approve or cancel approval for an `operator` to perform transfers of a specific diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index bb0a9eb59..69239042c 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -3,6 +3,7 @@ use frame_support::{ assert_noop, assert_ok, dispatch::WithPostDispatchInfo, sp_runtime::{traits::Zero, BoundedVec, DispatchError::BadOrigin}, + weights::Weight, }; use pallet_nfts::WeightInfo as NftsWeightInfoTrait; @@ -102,7 +103,7 @@ fn transfer_works() { // Check error works for `Nfts::transfer`. assert_noop!( NonFungibles::transfer(signed(owner), collection, item, dest), - NftsError::UnknownItem + NftsError::UnknownItem.with_weight(Weight::from_parts(0, 0)) ); nfts::create_collection_mint(owner, owner, item); // Successfully transfer a collection item. diff --git a/pop-api/integration-tests/Cargo.lock b/pop-api/integration-tests/Cargo.lock index a69358a20..b6c4eeae6 100644 --- a/pop-api/integration-tests/Cargo.lock +++ b/pop-api/integration-tests/Cargo.lock @@ -3473,6 +3473,7 @@ dependencies = [ "frame-system", "log", "pallet-assets", + "pallet-nfts 31.0.0", "parity-scale-codec", "pop-chain-extension", "scale-info", From c185d36819198af6b4c331b0bda81190f71d8a39 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:48:09 +0700 Subject: [PATCH 10/25] refactor: reorder `Read` impl in nonfungibles --- pallets/api/src/nonfungibles/mod.rs | 208 ++++++++++++++-------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 48ba1439f..8ae136c52 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -62,110 +62,6 @@ pub mod pallet { use super::*; - /// 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 { - /// Total item supply of a specified `collection`. - #[codec(index = 0)] - TotalSupply(CollectionIdOf), - /// Account balance for a specified `collection`. - #[codec(index = 1)] - BalanceOf { - /// The collection. - collection: CollectionIdOf, - /// The owner of the collection . - owner: AccountIdOf, - }, - /// Allowance for an `operator` approved by an `owner`, for a specified collection or item. - #[codec(index = 2)] - Allowance { - /// The collection. - collection: CollectionIdOf, - /// The collection item. - item: Option>, - /// The owner of the collection item. - owner: AccountIdOf, - /// The delegated operator of collection item. - operator: AccountIdOf, - }, - /// Owner of a specified collection item. - #[codec(index = 5)] - OwnerOf { - /// The collection. - collection: CollectionIdOf, - /// The collection item. - item: ItemIdOf, - }, - /// Attribute value of a specified collection item. - #[codec(index = 6)] - GetAttribute { - /// The collection. - collection: CollectionIdOf, - /// The collection item. - item: ItemIdOf, - /// The namespace of the attribute. - namespace: AttributeNamespaceOf, - /// The key of the attribute. - key: BoundedVec, - }, - /// Details of a specified collection. - #[codec(index = 9)] - Collection(CollectionIdOf), - /// Next collection ID. - #[codec(index = 10)] - NextCollectionId, - /// Metadata of a specified collection item. - #[codec(index = 11)] - ItemMetadata { - /// The collection. - collection: CollectionIdOf, - /// The collection item. - item: ItemIdOf, - }, - } - - /// 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(u128), - /// 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 specified collection item. - GetAttribute(Option>), - /// Details of a specified collection. - Collection(Option>), - /// Next collection ID. - NextCollectionId(Option>), - /// Metadata of a specified collection item. - ItemMetadata(Option>), - } - - impl ReadResult { - /// Encodes the result. - pub fn encode(&self) -> Vec { - use ReadResult::*; - match self { - OwnerOf(result) => result.encode(), - TotalSupply(result) => result.encode(), - BalanceOf(result) => result.encode(), - Collection(result) => result.encode(), - Allowance(result) => result.encode(), - GetAttribute(result) => result.encode(), - NextCollectionId(result) => result.encode(), - ItemMetadata(result) => result.encode(), - } - } - } - /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config + pallet_nfts::Config { @@ -537,6 +433,110 @@ 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))] + #[repr(u8)] + #[allow(clippy::unnecessary_cast)] + pub enum Read { + /// Total item supply of a specified `collection`. + #[codec(index = 0)] + TotalSupply(CollectionIdOf), + /// Account balance for a specified `collection`. + #[codec(index = 1)] + BalanceOf { + /// The collection. + collection: CollectionIdOf, + /// The owner of the collection . + owner: AccountIdOf, + }, + /// Allowance for an `operator` approved by an `owner`, for a specified collection or item. + #[codec(index = 2)] + Allowance { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: Option>, + /// The owner of the collection item. + owner: AccountIdOf, + /// The delegated operator of collection item. + operator: AccountIdOf, + }, + /// Owner of a specified collection item. + #[codec(index = 5)] + OwnerOf { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + }, + /// Attribute value of a specified collection item. + #[codec(index = 6)] + GetAttribute { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + /// The namespace of the attribute. + namespace: AttributeNamespaceOf, + /// The key of the attribute. + key: BoundedVec, + }, + /// Details of a specified collection. + #[codec(index = 9)] + Collection(CollectionIdOf), + /// Next collection ID. + #[codec(index = 10)] + NextCollectionId, + /// Metadata of a specified collection item. + #[codec(index = 11)] + ItemMetadata { + /// The collection. + collection: CollectionIdOf, + /// The collection item. + item: ItemIdOf, + }, + } + + /// 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(u128), + /// 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 specified collection item. + GetAttribute(Option>), + /// Details of a specified collection. + Collection(Option>), + /// Next collection ID. + NextCollectionId(Option>), + /// Metadata of a specified collection item. + ItemMetadata(Option>), + } + + impl ReadResult { + /// Encodes the result. + pub fn encode(&self) -> Vec { + use ReadResult::*; + match self { + OwnerOf(result) => result.encode(), + TotalSupply(result) => result.encode(), + BalanceOf(result) => result.encode(), + Collection(result) => result.encode(), + Allowance(result) => result.encode(), + GetAttribute(result) => result.encode(), + NextCollectionId(result) => result.encode(), + ItemMetadata(result) => result.encode(), + } + } + } + impl crate::Read for Pallet { /// The type of read requested. type Read = Read; From 665feea2e7e8694c060ccda28557430aa323acab Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:01:08 +0700 Subject: [PATCH 11/25] chore: remove unnecessary clone() --- pallets/api/src/nonfungibles/mod.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 8ae136c52..cb48b96a9 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -339,7 +339,7 @@ pub mod pallet { origin, collection, item, - T::Lookup::unlookup(delegate.clone()), + T::Lookup::unlookup(delegate), ) } @@ -364,7 +364,7 @@ pub mod pallet { origin, collection, item, - T::Lookup::unlookup(delegate.clone()), + T::Lookup::unlookup(delegate), witness, ) } @@ -390,9 +390,9 @@ pub mod pallet { /// - `to` - Account into which the item will be minted. /// - `collection` - The collection of the item to mint. /// - `item` - An identifier of the new item. - /// - `witness_data` - When the mint type is `HolderOf(collection_id)`, then the owned - /// item_id from that collection needs to be provided within the witness data object. If - /// the mint price is set, then it should be additionally confirmed in the `witness_data`. + /// - `witness` - 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`. #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::mint())] pub fn mint( @@ -403,13 +403,7 @@ pub mod pallet { witness: MintWitness, ItemPriceOf>, ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; - NftsOf::::mint( - origin, - collection, - item, - T::Lookup::unlookup(to), - Some(witness.clone()), - )?; + NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to), Some(witness))?; Self::deposit_event(Event::Transfer { collection, item, from: None, to: Some(owner) }); Ok(()) } From f65dc980565d50ee3d0fc17aa52938c6fb090e60 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:14:14 +0700 Subject: [PATCH 12/25] chore: remove description for pallet configure --- 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 d7270fdf9..aca3c865a 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -652,7 +652,6 @@ mod runtime { // Pop API #[runtime::pallet_index(150)] pub type Fungibles = fungibles::Pallet; - /// Non-fungible token queries. #[runtime::pallet_index(151)] pub type NonFungibles = nonfungibles::Pallet; } From 15df8b32b0481ea293762aef33874c45b0ebb013 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:05:31 +0700 Subject: [PATCH 13/25] refactor: approve() tests --- pallets/api/src/nonfungibles/impls.rs | 6 ++-- pallets/api/src/nonfungibles/tests.rs | 41 ++++++--------------------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index 07a831a08..0189621c8 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -13,7 +13,7 @@ impl Pallet { /// - `collection` - The identifier of the collection. /// - `maybe_item` - The optional item of the collection to be approved for delegated transfer. /// If `None`, the approval applies to all `owner`'s collection items. - /// - `operator`: The account to delegate permission to transfer a specified collection item or + /// - `operator` - The account to delegate permission to transfer a specified collection item or /// all collection items owned by the `owner`. pub(crate) fn do_approve( owner: OriginFor, @@ -46,8 +46,8 @@ impl Pallet { /// - `maybe_item` - The optional item of the collection that the operator has an approval to /// transfer. If not provided, an approval to transfer all `owner`'s collection items will be /// cancelled. - /// - `operator`: The account that had permission to transfer a specified collection item or all - /// collection items owned by the `owner`. + /// - `operator` - The account that had permission to transfer a specified collection item or + /// all collection items owned by the `owner`. pub(crate) fn do_cancel_approval( owner: OriginFor, collection: CollectionIdOf, diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 69239042c..0b3c52b39 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -233,6 +233,7 @@ mod approve { NonFungibles::approve(signed(owner), collection, Some(item), operator, true), NftsError::UnknownItem.with_weight(WeightInfo::approve(1, 1)) ); + nfts::create_collection_mint(owner, owner, item); // Successfully approves `operator` to transfer the collection item. assert_eq!( @@ -249,23 +250,6 @@ mod approve { Event::Approval { collection, item: Some(item), owner, operator, approved: true } .into(), ); - // Re-approves `operator` to transfer the collection item. - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(WeightInfo::approve(1, 1)).into()) - ); - assert_ok!(Nfts::check_approval_permission( - &collection, - &Some(item), - &owner, - &operator - )); - System::assert_last_event( - Event::Approval { collection, item: Some(item), owner, operator, approved: true } - .into(), - ); - // Successfully transfers the item by the delegated account `operator`. - assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); }); } @@ -282,21 +266,17 @@ mod approve { NonFungibles::approve(signed(owner), collection, None, operator, true), NftsError::NoItemOwned.with_weight(WeightInfo::approve(1, 0)) ); - // Approving to transfer `collection` reserves funds from the `operator`. + nfts::create_collection_mint(owner, owner, item); - let reserved_balance_before_approve = Balances::reserved_balance(&owner); + // Successfully approves `operator` to transfer all collection items owned by `owner`. assert_eq!( NonFungibles::approve(signed(owner), collection, None, operator, true), Ok(Some(WeightInfo::approve(1, 0)).into()) ); - let reserved_balance_after_approve = Balances::reserved_balance(&owner); - assert_eq!(reserved_balance_after_approve - reserved_balance_before_approve, 1); assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); System::assert_last_event( Event::Approval { collection, item: None, owner, operator, approved: true }.into(), ); - // Successfully transfer the item by the delegated account `operator`. - assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); }); } @@ -313,8 +293,9 @@ mod approve { NonFungibles::approve(signed(owner), collection, Some(item), operator, false), NftsError::UnknownItem.with_weight(WeightInfo::approve(0, 1)) ); - // Successfully cancels the transfer approval of `operator` by `owner`. + nfts::create_collection_mint_and_approve(owner, owner, item, operator); + // Successfully cancels the transfer approval of `operator` by `owner`. assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, false), Ok(Some(WeightInfo::approve(0, 1)).into()) @@ -356,11 +337,6 @@ mod approve { Nfts::check_approval_permission(&collection, &None, &owner, &operator), Err(NftsError::NoPermission.into()) ); - // Failed to transfer the item by `operator` without permission. - assert_noop!( - Nfts::transfer(signed(operator), collection, item, operator), - NftsError::NoPermission - ); }); } } @@ -485,7 +461,7 @@ fn clear_metadata_works() { NftsError::NoPermission ); nfts::create_collection_mint(owner, owner, item); - assert_ok!(Nfts::set_metadata(signed(owner), collection, item, metadata.clone())); + assert_ok!(Nfts::set_metadata(signed(owner), collection, item, metadata)); // Successfully clear the metadata. assert_ok!(NonFungibles::clear_metadata(signed(owner), collection, item)); assert!(Nfts::item_metadata(collection, item).is_none()); @@ -594,8 +570,8 @@ fn approve_item_attribute_works() { collection, Some(item), AttributeNamespace::Account(delegate), - attribute.clone(), - value.clone() + attribute, + value )); }); } @@ -871,7 +847,6 @@ fn item_metadata_works() { }); } -// TODO: Depends on #406, this test can be removed. #[test] fn next_collection_id_works() { new_test_ext().execute_with(|| { From 0396644e6a0ec05a870a520cbfe0d31127a2f0e5 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:44:01 +0700 Subject: [PATCH 14/25] chore: resolve review comments --- pallets/api/src/nonfungibles/benchmarking.rs | 4 ++-- pallets/api/src/nonfungibles/mod.rs | 25 ++++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pallets/api/src/nonfungibles/benchmarking.rs b/pallets/api/src/nonfungibles/benchmarking.rs index 340939591..ac99667fd 100644 --- a/pallets/api/src/nonfungibles/benchmarking.rs +++ b/pallets/api/src/nonfungibles/benchmarking.rs @@ -13,7 +13,7 @@ use frame_system::RawOrigin; use sp_runtime::traits::{Bounded, StaticLookup, Zero}; use super::{ - AccountIdOf, AttributeNamespace, BalanceOf, Call, CollectionConfig, CollectionConfigFor, + AccountIdOf, AttributeNamespace, BalanceOf, Call, CollectionConfig, CollectionConfigOf, CollectionIdOf, CollectionSettings, Config, Inspect, ItemConfig, ItemIdOf, ItemSettings, MintSettings, NftsInstanceOf, NftsOf, Pallet, Read, }; @@ -50,7 +50,7 @@ mod benchmarks { T::Currency::make_free_balance_be(&owner, BalanceOf::::max_value()); assert_ok!( - as Create, CollectionConfigFor>>::create_collection( + as Create, CollectionConfigOf>>::create_collection( &owner, &owner, &CollectionConfig { diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index cb48b96a9..e6600e3fb 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -1,5 +1,5 @@ //! 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 +//! tokens. The goal is to provide a simplified, consistent API that adheres to standards in the //! smart contract space. use frame_support::{ @@ -31,9 +31,7 @@ type ItemIdOf = as Inspect<::AccountId> type ItemPriceOf = BalanceOf; type CollectionDetailsOf = CollectionDetails, BalanceOf>; type AttributeNamespaceOf = AttributeNamespace>; -type CollectionConfigFor = - CollectionConfig, BlockNumberFor, CollectionIdOf>; - +type CollectionConfigOf = CollectionConfig, BlockNumberFor, CollectionIdOf>; type NftsErrorOf = pallet_nfts::Error>; type NftsWeightInfoOf = >>::WeightInfo; pub(super) type BalanceOf = @@ -81,8 +79,9 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event emitted when allowance by `owner` to `operator` changes. + // Differing style: event name abides by the PSP34 standard. Approval { - /// The identifier of the collection. + /// The collection identifier. collection: CollectionIdOf, /// The item which is (dis)approved. `None` for all collection items owned by the /// `owner`. @@ -95,11 +94,11 @@ pub mod pallet { approved: bool, }, /// Event emitted when a token transfer occurs. - // Differing style: event name abides by the PSP22 standard. + // Differing style: event name abides by the PSP34 standard. Transfer { - /// The collection ID. + /// The collection identifier. collection: CollectionIdOf, - /// The collection item ID. + /// The item which is transferred. item: ItemIdOf, /// The source of the transfer. `None` when minting. from: Option>, @@ -150,7 +149,7 @@ pub mod pallet { /// collection item or all collection items owned by the `origin`. /// /// # Parameters - /// - `collection` - The identifier of the collection. + /// - `collection` - The collection identifier. /// - `item` - An optional parameter specifying the item to approve for the delegated /// transfer. If `None`, all owner's collection items will be approved. /// - `operator` - The account being granted or revoked approval to transfer the specified @@ -218,7 +217,7 @@ pub mod pallet { pub fn create( origin: OriginFor, admin: AccountIdOf, - config: CollectionConfigFor, + config: CollectionConfigOf, ) -> DispatchResult { let creator = ensure_signed(origin.clone())?; let id = NextCollectionIdOf::::get() @@ -372,7 +371,7 @@ pub mod pallet { /// Set the maximum number of items a collection could have. /// /// # Parameters - /// - `collection` - The identifier of the collection to change. + /// - `collection` - The collection identifier. /// - `max_supply` - The maximum number of items a collection could have. #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] @@ -479,7 +478,7 @@ pub mod pallet { /// Details of a specified collection. #[codec(index = 9)] Collection(CollectionIdOf), - /// Next collection ID. + /// Next collection identifier. #[codec(index = 10)] NextCollectionId, /// Metadata of a specified collection item. @@ -508,7 +507,7 @@ pub mod pallet { GetAttribute(Option>), /// Details of a specified collection. Collection(Option>), - /// Next collection ID. + /// Next collection identifier. NextCollectionId(Option>), /// Metadata of a specified collection item. ItemMetadata(Option>), From ea59ba3e3a06abb9106ed9a4cf26dc37f6761001 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:33:45 +0700 Subject: [PATCH 15/25] feat: add AttributeSet event --- pallets/api/src/nonfungibles/mod.rs | 30 +++++++++++++++++++++++++-- pallets/api/src/nonfungibles/tests.rs | 9 ++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index e6600e3fb..8c5a93fda 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -114,6 +114,17 @@ pub mod pallet { /// The administrator of the collection. admin: AccountIdOf, }, + /// Event emitted when an attribute is set for a token. + AttributeSet { + /// The collection identifier. + collection: CollectionIdOf, + /// The item which attribute is set. + item: Option>, + /// The key for the attribute. + key: Vec, + /// The data for the attribute. + data: Vec, + }, } #[pallet::call] @@ -252,7 +263,8 @@ pub mod pallet { /// /// # Parameters /// - `collection` - The collection whose item's metadata to set. - /// - `item` - The item whose metadata to set. + /// - `item` - The item whose metadata to set. If not provided, the collection's attribute + /// is set. /// - `namespace` - Attribute's namespace. /// - `key` - The key of the attribute. /// - `value` - The value to which to set the attribute. @@ -266,7 +278,21 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { - NftsOf::::set_attribute(origin, collection, item, namespace, key, value) + NftsOf::::set_attribute( + origin, + collection, + item, + namespace, + key.clone(), + value.clone(), + )?; + Self::deposit_event(Event::AttributeSet { + collection, + item, + key: key.to_vec(), + data: value.to_vec(), + }); + Ok(()) } /// Clear an attribute for the collection or item. diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 0b3c52b39..02d875070 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -499,6 +499,15 @@ fn set_attribute_works() { attribute.clone(), value.clone() )); + System::assert_last_event( + Event::AttributeSet { + collection, + item: Some(item), + key: attribute.to_vec(), + data: value.to_vec(), + } + .into(), + ); assert_eq!( nfts::get_attribute( collection, From 728c1df19d4878f68987fa286de2ee67112caebc Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:09:33 +0700 Subject: [PATCH 16/25] chore: comments and add test for Collection read --- pallets/api/src/nonfungibles/mod.rs | 77 ++-- pallets/api/src/nonfungibles/tests.rs | 493 ++++++++++++++------------ pallets/nfts/src/lib.rs | 2 +- 3 files changed, 302 insertions(+), 270 deletions(-) diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 8c5a93fda..4f16d96d8 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -25,13 +25,13 @@ mod tests; pub mod weights; type AccountIdOf = ::AccountId; +type AttributeNamespaceOf = AttributeNamespace>; +type CollectionConfigOf = CollectionConfig, BlockNumberFor, CollectionIdOf>; +type CollectionDetailsOf = CollectionDetails, BalanceOf>; type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; type ItemIdOf = as Inspect<::AccountId>>::ItemId; type ItemPriceOf = BalanceOf; -type CollectionDetailsOf = CollectionDetails, BalanceOf>; -type AttributeNamespaceOf = AttributeNamespace>; -type CollectionConfigOf = CollectionConfig, BlockNumberFor, CollectionIdOf>; type NftsErrorOf = pallet_nfts::Error>; type NftsWeightInfoOf = >>::WeightInfo; pub(super) type BalanceOf = @@ -118,7 +118,7 @@ pub mod pallet { AttributeSet { /// The collection identifier. collection: CollectionIdOf, - /// The item which attribute is set. + /// The item whose attribute is set. item: Option>, /// The key for the attribute. key: Vec, @@ -162,7 +162,7 @@ pub mod pallet { /// # Parameters /// - `collection` - The collection identifier. /// - `item` - An optional parameter specifying the item to approve for the delegated - /// transfer. If `None`, all owner's collection items will be approved. + /// transfer. If `None`, all `origin`'s collection items will be approved. /// - `operator` - The account being granted or revoked approval to transfer the specified /// collection item(s). /// - `approved` - A boolean indicating the desired approval status: @@ -217,11 +217,10 @@ pub mod pallet { NftsOf::::clear_collection_approvals(origin, collection, limit) } - /// Issue a new collection of non-fungible items from a public origin. + /// Issue a new collection of non-fungible items. /// /// # Parameters - /// - `admin` - The admin of this collection. The admin is the initial address of each - /// member of the collection's admin team. + /// - `admin` - The admin of this collection. /// - `config` - The configuration of the collection. #[pallet::call_index(7)] #[pallet::weight(NftsWeightInfoOf::::create())] @@ -239,7 +238,7 @@ pub mod pallet { Ok(()) } - /// Destroy a collection of fungible items. + /// Destroy a collection of items. /// /// # Parameters /// - `collection` - The collection to destroy. @@ -461,40 +460,41 @@ pub mod pallet { /// Total item supply of a specified `collection`. #[codec(index = 0)] TotalSupply(CollectionIdOf), - /// Account balance for a specified `collection`. + /// Amount of items the `owner` has within a `collection`. #[codec(index = 1)] BalanceOf { - /// The collection. + /// The collection identifier. collection: CollectionIdOf, - /// The owner of the collection . + /// The account whose balance is being queried. owner: AccountIdOf, }, - /// Allowance for an `operator` approved by an `owner`, for a specified collection or item. + /// Allowance for an `operator` approved by an `owner` to transfer a specified `item` or + /// all collection items owned by the `owner`. #[codec(index = 2)] Allowance { - /// The collection. + /// The collection identifier. collection: CollectionIdOf, - /// The collection item. + /// The item whose attribute is set. item: Option>, - /// The owner of the collection item. + /// The owner of the collection item(s). owner: AccountIdOf, - /// The delegated operator of collection item. + /// The account that is allowed to transfer the collection item(s). operator: AccountIdOf, }, - /// Owner of a specified collection item. + /// Owner of an `item` within a specified `collection`, if any. #[codec(index = 5)] OwnerOf { - /// The collection. + /// The collection identifier. collection: CollectionIdOf, - /// The collection item. + /// The collection item identifier. item: ItemIdOf, }, - /// Attribute value of a specified collection item. + /// Attribute value of a specified collection item for a given `key`, if any. #[codec(index = 6)] GetAttribute { - /// The collection. + /// The collection identifier. collection: CollectionIdOf, - /// The collection item. + /// The collection item identifier. item: ItemIdOf, /// The namespace of the attribute. namespace: AttributeNamespaceOf, @@ -510,9 +510,9 @@ pub mod pallet { /// Metadata of a specified collection item. #[codec(index = 11)] ItemMetadata { - /// The collection. + /// The collection identifier. collection: CollectionIdOf, - /// The collection item. + /// The collection item identifier. item: ItemIdOf, }, } @@ -521,17 +521,18 @@ pub mod pallet { #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - /// Total item supply of a collection. + /// Total item supply of a specified `collection`. TotalSupply(u128), - /// Account balance for a specified collection. + /// Amount of items the `owner` has within a `collection`. BalanceOf(u32), - /// Allowance for an operator approved by an owner, for a specified collection or item. + /// Allowance for an `operator` approved by an `owner` to transfer a specified `item` or + /// all collection items owned by the `owner`. Allowance(bool), - /// Owner of a specified collection owner. + /// Owner of an `item` within a specified `collection`, if any. OwnerOf(Option>), - /// Attribute value of a specified collection item. + /// Attribute value of a specified collection item for a given `key`, if any. GetAttribute(Option>), - /// Details of a specified collection. + /// Details of a specified collection, if any. Collection(Option>), /// Next collection identifier. NextCollectionId(Option>), @@ -544,14 +545,14 @@ pub mod pallet { pub fn encode(&self) -> Vec { use ReadResult::*; match self { - OwnerOf(result) => result.encode(), TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), - Collection(result) => result.encode(), Allowance(result) => result.encode(), + OwnerOf(result) => result.encode(), GetAttribute(result) => result.encode(), - NextCollectionId(result) => result.encode(), ItemMetadata(result) => result.encode(), + Collection(result) => result.encode(), + NextCollectionId(result) => result.encode(), } } } @@ -575,9 +576,9 @@ pub mod pallet { Allowance { .. } => ::WeightInfo::allowance(), OwnerOf { .. } => ::WeightInfo::owner_of(), GetAttribute { .. } => ::WeightInfo::get_attribute(), + ItemMetadata { .. } => ::WeightInfo::item_metadata(), Collection(_) => ::WeightInfo::collection(), NextCollectionId => ::WeightInfo::next_collection_id(), - ItemMetadata { .. } => ::WeightInfo::item_metadata(), } } @@ -606,14 +607,14 @@ pub mod pallet { AttributeOf::::get((collection, Some(item), namespace, key)) .map(|attribute| attribute.0.into()), ), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), + ), Collection(collection) => ReadResult::Collection(CollectionOf::::get(collection)), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), ), - ItemMetadata { collection, item } => ReadResult::ItemMetadata( - NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), - ), } } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 02d875070..2bc146649 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,4 +1,4 @@ -use codec::Encode; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_noop, assert_ok, dispatch::WithPostDispatchInfo, @@ -6,14 +6,16 @@ use frame_support::{ weights::Weight, }; use pallet_nfts::WeightInfo as NftsWeightInfoTrait; +use scale_info::TypeInfo; use crate::{ mock::*, nonfungibles::{ - weights::WeightInfo as WeightInfoTrait, AccountBalanceOf, AttributeNamespace, AttributeOf, - BlockNumberFor, CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, - CollectionOf, CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, - MintWitness, NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, + weights::WeightInfo as WeightInfoTrait, AccountBalanceOf, AccountIdOf, AttributeNamespace, + AttributeOf, BalanceOf as DepositBalanceOf, BlockNumberFor, + CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, CollectionOf, + CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, MintWitness, + NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, }, Read, }; @@ -26,70 +28,15 @@ type Event = crate::nonfungibles::Event; type NftsError = pallet_nfts::Error>; type WeightInfo = ::WeightInfo; -mod encoding_read_result { - use super::*; - - #[test] - fn total_supply() { - let total_supply: u128 = 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(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("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 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] - fn item_metadata_works() { - 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()); - } +/// Information about a collection. +#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +struct CollectionDetails { + pub(super) owner: AccountIdOf, + pub(super) owner_deposit: DepositBalanceOf, + pub(super) items: u32, + pub(super) item_metadatas: u32, + pub(super) item_configs: u32, + pub(super) attributes: u32, } #[test] @@ -112,7 +59,7 @@ fn transfer_works() { assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); let from_balance_after_transfer = nfts::balance_of(collection, &owner); let to_balance_after_transfer = nfts::balance_of(collection, &dest); - // Check that `to` has received the `value` tokens from `from`. + // Check that `to` has received the collection item from `from`. assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); System::assert_last_event( @@ -133,13 +80,13 @@ fn approved_transfer_works() { nfts::create_collection_mint(owner, owner, item); // Approve `operator` to transfer `collection` items owned by the `owner`. assert_ok!(Nfts::approve_collection_transfer(signed(owner), collection, operator, None)); - // Successfully transfers a collection item. + // Successfully transfer a collection item. let from_balance_before_transfer = nfts::balance_of(collection, &owner); let to_balance_before_transfer = nfts::balance_of(collection, &dest); assert_ok!(NonFungibles::transfer(signed(operator), collection, item, dest)); let from_balance_after_transfer = nfts::balance_of(collection, &owner); let to_balance_after_transfer = nfts::balance_of(collection, &dest); - // Check that `to` has received the `value` tokens from `from`. + // Check that `to` has received the collection item from `from`. assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); System::assert_last_event( @@ -168,7 +115,7 @@ fn mint_works() { NonFungibles::mint(signed(owner), owner, collection, item, witness.clone()), NftsError::NoConfig ); - // Successfully mints a new collection item. + // Successfully mint a new collection item. nfts::create_collection(owner); let balance_before_mint = nfts::balance_of(collection, &owner); assert_ok!(NonFungibles::mint(signed(owner), owner, collection, item, witness)); @@ -191,10 +138,10 @@ fn burn_works() { for origin in vec![root(), none()] { assert_noop!(NonFungibles::burn(origin, collection, item), BadOrigin); } - // Check error works for `Nfts::burn()`. + // Check error works for `Nfts::burn()`. assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); - // Successfully burns an existing new collection item. nfts::create_collection_mint(owner, owner, ITEM); + // Successfully burn a collection item. let balance_before_burn = nfts::balance_of(collection, &owner); assert_ok!(NonFungibles::burn(signed(owner), collection, item)); let balance_after_burn = nfts::balance_of(collection, &owner); @@ -235,7 +182,7 @@ mod approve { ); nfts::create_collection_mint(owner, owner, item); - // Successfully approves `operator` to transfer the collection item. + // Successfully approve `operator` to transfer the collection item. assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, true), Ok(Some(WeightInfo::approve(1, 1)).into()) @@ -268,7 +215,7 @@ mod approve { ); nfts::create_collection_mint(owner, owner, item); - // Successfully approves `operator` to transfer all collection items owned by `owner`. + // Successfully approve `operator` to transfer all collection items owned by `owner`. assert_eq!( NonFungibles::approve(signed(owner), collection, None, operator, true), Ok(Some(WeightInfo::approve(1, 0)).into()) @@ -295,7 +242,7 @@ mod approve { ); nfts::create_collection_mint_and_approve(owner, owner, item, operator); - // Successfully cancels the transfer approval of `operator` by `owner`. + // Successfully cancel the transfer approval of `operator` by `owner`. assert_eq!( NonFungibles::approve(signed(owner), collection, Some(item), operator, false), Ok(Some(WeightInfo::approve(0, 1)).into()) @@ -930,163 +877,10 @@ mod nfts { } } -mod read_weights { - use frame_support::weights::Weight; - - use super::*; - - struct ReadWeightInfo { - total_supply: Weight, - balance_of: Weight, - allowance: Weight, - owner_of: Weight, - get_attribute: Weight, - collection: Weight, - next_collection_id: Weight, - item_metadata: Weight, - } - - impl ReadWeightInfo { - fn new() -> Self { - Self { - total_supply: NonFungibles::weight(&TotalSupply(COLLECTION)), - balance_of: NonFungibles::weight(&BalanceOf { - collection: COLLECTION, - owner: ALICE, - }), - allowance: NonFungibles::weight(&Allowance { - collection: COLLECTION, - item: Some(ITEM), - owner: ALICE, - operator: BOB, - }), - owner_of: NonFungibles::weight(&OwnerOf { collection: COLLECTION, item: ITEM }), - get_attribute: NonFungibles::weight(&GetAttribute { - collection: COLLECTION, - item: ITEM, - namespace: AttributeNamespace::CollectionOwner, - key: BoundedVec::default(), - }), - collection: NonFungibles::weight(&Collection(COLLECTION)), - next_collection_id: NonFungibles::weight(&NextCollectionId), - item_metadata: NonFungibles::weight(&ItemMetadata { - collection: COLLECTION, - item: ITEM, - }), - } - } - } - - #[test] - fn ensure_read_matches_benchmarks() { - let ReadWeightInfo { - allowance, - balance_of, - collection, - get_attribute, - item_metadata, - next_collection_id, - owner_of, - total_supply, - } = ReadWeightInfo::new(); - - assert_eq!(total_supply, WeightInfo::total_supply()); - assert_eq!(balance_of, WeightInfo::balance_of()); - assert_eq!(allowance, WeightInfo::allowance()); - assert_eq!(owner_of, WeightInfo::owner_of()); - assert_eq!(get_attribute, WeightInfo::get_attribute()); - assert_eq!(collection, WeightInfo::collection()); - assert_eq!(next_collection_id, WeightInfo::next_collection_id()); - assert_eq!(item_metadata, WeightInfo::item_metadata()); - } - - // These types read from the `Collection` storage. - #[test] - fn ensure_collection_variants_match() { - let ReadWeightInfo { total_supply, collection, .. } = ReadWeightInfo::new(); - - assert_eq!(total_supply, collection); - } - - // Proof size is based on `MaxEncodedLen`, not hardware. - // This test ensures that the data structure sizes do not change with upgrades. - #[test] - fn ensure_expected_proof_size_does_not_change() { - let ReadWeightInfo { - allowance, - balance_of, - collection, - get_attribute, - item_metadata, - next_collection_id, - owner_of, - total_supply, - } = ReadWeightInfo::new(); - - // These values come from `weights.rs`. - assert_eq!(total_supply.proof_size(), 3549); - assert_eq!(balance_of.proof_size(), 3585); - assert_eq!(allowance.proof_size(), 4326); - assert_eq!(owner_of.proof_size(), 4326); - assert_eq!(get_attribute.proof_size(), 3944); - assert_eq!(collection.proof_size(), 3549); - assert_eq!(next_collection_id.proof_size(), 1489); - assert_eq!(item_metadata.proof_size(), 3812); - } -} - mod ensure_codec_indexes { use super::{Encode, *}; use crate::{mock::RuntimeCall::NonFungibles, nonfungibles}; - #[test] - fn ensure_read_variant_indexes() { - [ - (TotalSupply::(Default::default()), 0u8, "TotalSupply"), - ( - BalanceOf:: { collection: Default::default(), owner: Default::default() }, - 1, - "BalanceOf", - ), - ( - Allowance:: { - collection: Default::default(), - item: Default::default(), - owner: Default::default(), - operator: Default::default(), - }, - 2, - "Allowance", - ), - ( - OwnerOf:: { collection: Default::default(), item: Default::default() }, - 5, - "OwnerOf", - ), - ( - GetAttribute:: { - collection: Default::default(), - item: Default::default(), - namespace: AttributeNamespace::CollectionOwner, - key: Default::default(), - }, - 6, - "GetAttribute", - ), - (Collection::(Default::default()), 9, "Collection"), - (NextCollectionId, 10, "NextCollectionId"), - ( - ItemMetadata { collection: Default::default(), item: Default::default() }, - 11, - "ItemMetadata", - ), - ] - .iter() - .for_each(|(variant, expected_index, name)| { - assert_eq!(variant.encode()[0], *expected_index, "{name} variant index changed"); - }) - } - #[test] fn ensure_dispatchable_indexes() { use nonfungibles::Call::*; @@ -1225,4 +1019,241 @@ mod ensure_codec_indexes { ); }) } + + #[test] + fn ensure_read_variant_indexes() { + [ + (TotalSupply::(Default::default()), 0u8, "TotalSupply"), + ( + BalanceOf:: { collection: Default::default(), owner: Default::default() }, + 1, + "BalanceOf", + ), + ( + Allowance:: { + collection: Default::default(), + item: Default::default(), + owner: Default::default(), + operator: Default::default(), + }, + 2, + "Allowance", + ), + ( + OwnerOf:: { collection: Default::default(), item: Default::default() }, + 5, + "OwnerOf", + ), + ( + GetAttribute:: { + collection: Default::default(), + item: Default::default(), + namespace: AttributeNamespace::CollectionOwner, + key: Default::default(), + }, + 6, + "GetAttribute", + ), + (Collection::(Default::default()), 9, "Collection"), + (NextCollectionId, 10, "NextCollectionId"), + ( + ItemMetadata { collection: Default::default(), item: Default::default() }, + 11, + "ItemMetadata", + ), + ] + .iter() + .for_each(|(variant, expected_index, name)| { + assert_eq!(variant.encode()[0], *expected_index, "{name} variant index changed"); + }) + } +} + +mod read_weights { + use frame_support::weights::Weight; + + use super::*; + + struct ReadWeightInfo { + total_supply: Weight, + balance_of: Weight, + allowance: Weight, + owner_of: Weight, + get_attribute: Weight, + collection: Weight, + next_collection_id: Weight, + item_metadata: Weight, + } + + impl ReadWeightInfo { + fn new() -> Self { + Self { + total_supply: NonFungibles::weight(&TotalSupply(COLLECTION)), + balance_of: NonFungibles::weight(&BalanceOf { + collection: COLLECTION, + owner: ALICE, + }), + allowance: NonFungibles::weight(&Allowance { + collection: COLLECTION, + item: Some(ITEM), + owner: ALICE, + operator: BOB, + }), + owner_of: NonFungibles::weight(&OwnerOf { collection: COLLECTION, item: ITEM }), + get_attribute: NonFungibles::weight(&GetAttribute { + collection: COLLECTION, + item: ITEM, + namespace: AttributeNamespace::CollectionOwner, + key: BoundedVec::default(), + }), + collection: NonFungibles::weight(&Collection(COLLECTION)), + next_collection_id: NonFungibles::weight(&NextCollectionId), + item_metadata: NonFungibles::weight(&ItemMetadata { + collection: COLLECTION, + item: ITEM, + }), + } + } + } + + #[test] + fn ensure_read_matches_benchmarks() { + let ReadWeightInfo { + allowance, + balance_of, + collection, + get_attribute, + item_metadata, + next_collection_id, + owner_of, + total_supply, + } = ReadWeightInfo::new(); + + assert_eq!(total_supply, WeightInfo::total_supply()); + assert_eq!(balance_of, WeightInfo::balance_of()); + assert_eq!(allowance, WeightInfo::allowance()); + assert_eq!(owner_of, WeightInfo::owner_of()); + assert_eq!(get_attribute, WeightInfo::get_attribute()); + assert_eq!(collection, WeightInfo::collection()); + assert_eq!(next_collection_id, WeightInfo::next_collection_id()); + assert_eq!(item_metadata, WeightInfo::item_metadata()); + } + + // These types read from the `Collection` storage. + #[test] + fn ensure_collection_variants_match() { + let ReadWeightInfo { total_supply, collection, .. } = ReadWeightInfo::new(); + + assert_eq!(total_supply, collection); + } + + // Proof size is based on `MaxEncodedLen`, not hardware. + // This test ensures that the data structure sizes do not change with upgrades. + #[test] + fn ensure_expected_proof_size_does_not_change() { + let ReadWeightInfo { + allowance, + balance_of, + collection, + get_attribute, + item_metadata, + next_collection_id, + owner_of, + total_supply, + } = ReadWeightInfo::new(); + + // These values come from `weights.rs`. + assert_eq!(total_supply.proof_size(), 3549); + assert_eq!(balance_of.proof_size(), 3585); + assert_eq!(allowance.proof_size(), 4326); + assert_eq!(owner_of.proof_size(), 4326); + assert_eq!(get_attribute.proof_size(), 3944); + assert_eq!(collection.proof_size(), 3549); + assert_eq!(next_collection_id.proof_size(), 1489); + assert_eq!(item_metadata.proof_size(), 3812); + } +} + +mod encoding_read_result { + use super::*; + use crate::nonfungibles::CollectionDetailsOf; + + #[test] + fn total_supply() { + let total_supply: u128 = 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(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("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() { + let bytes = CollectionDetails { + owner: ALICE, + owner_deposit: 0, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + } + .encode(); + let mut details = Some(CollectionDetailsOf::::decode(&mut &bytes[..]).unwrap()); + assert_eq!(ReadResult::Collection::(details.clone()).encode(), details.encode()); + details = None; + assert_eq!(ReadResult::Collection::(details.clone()).encode(), 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] + fn item_metadata_works() { + 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()); + } } diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 2526d8366..ac5c8fe1a 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::{ use frame_system::Config as SystemConfig; pub use pallet::*; use sp_runtime::{ - traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, + traits::{IdentifyAccount, Saturating, StaticLookup, Verify}, RuntimeDebug, }; pub use types::*; From bec6b25d6f169dc586baf34fb8959f0e7bdf9204 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:22:56 +0700 Subject: [PATCH 17/25] chore: revert changes made to pallet nfts --- pallets/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index ac5c8fe1a..2526d8366 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::{ use frame_system::Config as SystemConfig; pub use pallet::*; use sp_runtime::{ - traits::{IdentifyAccount, Saturating, StaticLookup, Verify}, + traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; pub use types::*; From 6a8adaa646caa56bd408dd088b24a9e8eabd754e Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:40:00 +0700 Subject: [PATCH 18/25] chore: fix comment --- pallets/api/src/nonfungibles/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 2bc146649..f005cf966 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -140,7 +140,7 @@ fn burn_works() { } // Check error works for `Nfts::burn()`. assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); - nfts::create_collection_mint(owner, owner, ITEM); + nfts::create_collection_mint(owner, owner, item); // Successfully burn a collection item. let balance_before_burn = nfts::balance_of(collection, &owner); assert_ok!(NonFungibles::burn(signed(owner), collection, item)); @@ -262,7 +262,7 @@ mod approve { let operator = BOB; let owner = ALICE; - // Check error works for `Nfts::cancel_approval()`. + // Check error works for `Nfts::cancel_collection_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, false), NftsError::NotDelegate.with_weight(WeightInfo::approve(0, 0)) @@ -758,7 +758,7 @@ fn collection_works() { let item = ITEM; let owner = ALICE; - assert_eq!(NonFungibles::read(Collection(collection)), ReadResult::Collection(None),); + assert_eq!(NonFungibles::read(Collection(collection)), ReadResult::Collection(None)); nfts::create_collection_mint(owner, owner, item); assert_eq!( NonFungibles::read(Collection(collection)), From f70e00547e4074db74b170d384a71bbf9e9f4fa7 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:21:06 +0700 Subject: [PATCH 19/25] chore: resolve review comments --- pallets/api/src/mock.rs | 5 +- pallets/api/src/nonfungibles/benchmarking.rs | 7 +- pallets/api/src/nonfungibles/impls.rs | 86 +- pallets/api/src/nonfungibles/mod.rs | 268 +++--- pallets/api/src/nonfungibles/tests.rs | 842 +++++++++++-------- pallets/nfts/src/benchmarking.rs | 2 +- pallets/nfts/src/lib.rs | 2 +- runtime/devnet/src/config/api/mod.rs | 40 +- 8 files changed, 677 insertions(+), 575 deletions(-) diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index af9ba39c3..6b1a051a6 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -2,6 +2,7 @@ use codec::{Decode, Encode}; use frame_support::{ derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Everything}, + weights::constants::RocksDbWeight, }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_nfts::PalletFeatures; @@ -51,7 +52,7 @@ impl frame_system::Config for Test { type BlockHashCount = BlockHashCount; type BlockLength = (); type BlockWeights = (); - type DbWeight = (); + type DbWeight = RocksDbWeight; type Hash = H256; type Hashing = BlakeTwo256; type Lookup = IdentityLookup; @@ -157,7 +158,7 @@ impl pallet_nfts::pallet::BenchmarkHelper for () { unimplemented!() } - fn sign(signer: &Noop, message: &[u8]) -> Noop { + fn sign(_signer: &Noop, _message: &[u8]) -> Noop { unimplemented!() } } diff --git a/pallets/api/src/nonfungibles/benchmarking.rs b/pallets/api/src/nonfungibles/benchmarking.rs index ac99667fd..1fcd538b9 100644 --- a/pallets/api/src/nonfungibles/benchmarking.rs +++ b/pallets/api/src/nonfungibles/benchmarking.rs @@ -96,9 +96,10 @@ mod benchmarks { #[extrinsic_call] _(origin, collection_id, maybe_item, operator.clone(), approved); - assert!( + assert_eq!( NftsOf::::check_approval_permission(&collection_id, &maybe_item, &owner, &operator) - .is_ok() == approved + .is_ok(), + approved ); if approved { @@ -182,7 +183,7 @@ mod benchmarks { Pallet::::read(Read::GetAttribute { key: BoundedVec::default(), collection: CollectionIdOf::::zero(), - item: ItemIdOf::::zero(), + item: Some(ItemIdOf::::zero()), namespace: AttributeNamespace::CollectionOwner, }); } diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index 0189621c8..6429e0343 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -1,72 +1,50 @@ -use frame_support::dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}; +use frame_support::dispatch::DispatchResult; use frame_system::pallet_prelude::*; -use sp_runtime::traits::StaticLookup; -use super::{pallet::*, AccountIdOf, CollectionIdOf, ItemIdOf, NftsOf, WeightInfo}; +use super::{pallet::*, AccountIdLookupOf, CollectionIdOf, ItemIdOf, NftsOf}; impl Pallet { - /// Approves the transfer of a specific item or all collection items owned by the `owner` to an - /// `operator`. + // Approves the transfer of a specific item or all collection items owned by the `owner` to an + // `operator`. /// - /// # Parameters - /// - `owner` - The owner of the specified collection item(s). - /// - `collection` - The identifier of the collection. - /// - `maybe_item` - The optional item of the collection to be approved for delegated transfer. - /// If `None`, the approval applies to all `owner`'s collection items. - /// - `operator` - The account to delegate permission to transfer a specified collection item or - /// all collection items owned by the `owner`. + // # Parameters + // - `owner` - The owner of the specified collection item(s). + // - `collection` - The collection identifier. + // - `maybe_item` - The optional item of the collection to be approved for delegated transfer. + // If `None`, the approval applies to all `owner`'s collection items. + // - `operator` - The account to delegate permission to. pub(crate) fn do_approve( owner: OriginFor, collection: CollectionIdOf, maybe_item: Option>, - operator: &AccountIdOf, - ) -> DispatchResultWithPostInfo { - let operator = T::Lookup::unlookup(operator.clone()); - Ok(Some(match maybe_item { - Some(item) => { - NftsOf::::approve_transfer(owner, collection, item, operator, None) - .map_err(|e| e.with_weight(::WeightInfo::approve(1, 1)))?; - ::WeightInfo::approve(1, 1) - }, - None => { - NftsOf::::approve_collection_transfer(owner, collection, operator, None) - .map_err(|e| e.with_weight(::WeightInfo::approve(1, 0)))?; - ::WeightInfo::approve(1, 0) - }, - }) - .into()) + operator: AccountIdLookupOf, + ) -> DispatchResult { + match maybe_item { + Some(item) => NftsOf::::approve_transfer(owner, collection, item, operator, None), + None => NftsOf::::approve_collection_transfer(owner, collection, operator, None), + } } - /// Cancel an approval to transfer a specific item or all items within a collection owned by - /// the `owner`. + // Cancel an approval to transfer a specific item or all items within a collection owned by + // the `owner`. /// - /// # Parameters - /// - `owner` - The owner of the specified collection item(s). - /// - `collection` - The identifier of the collection. - /// - `maybe_item` - The optional item of the collection that the operator has an approval to - /// transfer. If not provided, an approval to transfer all `owner`'s collection items will be - /// cancelled. - /// - `operator` - The account that had permission to transfer a specified collection item or - /// all collection items owned by the `owner`. + // # Parameters + // - `owner` - The owner of the specified collection item(s). + // - `collection` - The collection identifier. + // - `maybe_item` - The optional item of the collection that the operator has an approval to + // transfer. If not provided, an approval to transfer all `owner`'s collection items will be + // cancelled. + // - `operator` - The account that had permission to transfer a specified collection item or all + // collection items owned by the `owner`. pub(crate) fn do_cancel_approval( owner: OriginFor, collection: CollectionIdOf, maybe_item: Option>, - operator: &AccountIdOf, - ) -> DispatchResultWithPostInfo { - let operator = T::Lookup::unlookup(operator.clone()); - Ok(Some(match maybe_item { - Some(item) => { - NftsOf::::cancel_approval(owner, collection, item, operator) - .map_err(|e| e.with_weight(::WeightInfo::approve(0, 1)))?; - ::WeightInfo::approve(0, 1) - }, - None => { - NftsOf::::cancel_collection_approval(owner, collection, operator) - .map_err(|e| e.with_weight(::WeightInfo::approve(0, 0)))?; - ::WeightInfo::approve(0, 0) - }, - }) - .into()) + operator: AccountIdLookupOf, + ) -> DispatchResult { + match maybe_item { + Some(item) => NftsOf::::cancel_approval(owner, collection, item, operator), + None => NftsOf::::cancel_collection_approval(owner, collection, operator), + } } } diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 4f16d96d8..e2ed7c4be 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -2,6 +2,8 @@ //! tokens. The goal is to provide a simplified, consistent API that adheres to standards in the //! smart contract space. +extern crate alloc; + use frame_support::{ dispatch::WithPostDispatchInfo, traits::{nonfungibles_v2::Inspect, Currency}, @@ -34,29 +36,32 @@ type ItemIdOf = as Inspect<::AccountId> type ItemPriceOf = BalanceOf; type NftsErrorOf = pallet_nfts::Error>; type NftsWeightInfoOf = >>::WeightInfo; -pub(super) type BalanceOf = +pub(crate) type AccountIdLookupOf = + <::Lookup as StaticLookup>::Source; +pub(crate) type BalanceOf = <>>::Currency as Currency< ::AccountId, >>::Balance; -pub(super) type NftsOf = pallet_nfts::Pallet>; -pub(super) type NftsInstanceOf = ::NftsInstance; +pub(crate) type NftsOf = pallet_nfts::Pallet>; +pub(crate) type NftsInstanceOf = ::NftsInstance; +pub(crate) type WeightOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. -pub(super) type AccountBalanceOf = pallet_nfts::AccountBalance>; -pub(super) type AttributeOf = pallet_nfts::Attribute>; -pub(super) type NextCollectionIdOf = pallet_nfts::NextCollectionId>; -pub(super) type CollectionOf = pallet_nfts::Collection>; +pub(crate) type AccountBalanceOf = pallet_nfts::AccountBalance>; +pub(crate) type AttributeOf = pallet_nfts::Attribute>; +pub(crate) type NextCollectionIdOf = pallet_nfts::NextCollectionId>; +pub(crate) type CollectionOf = pallet_nfts::Collection>; #[frame_support::pallet] pub mod pallet { + use alloc::vec::Vec; + use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, pallet_prelude::*, traits::Incrementable, }; use frame_system::pallet_prelude::*; - use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness, MintWitness}; use sp_runtime::BoundedVec; - use sp_std::vec::Vec; use super::*; @@ -83,12 +88,11 @@ pub mod pallet { Approval { /// The collection identifier. collection: CollectionIdOf, - /// The item which is (dis)approved. `None` for all collection items owned by the - /// `owner`. + /// The item. If `None`, it is regarding all owner's items in collection. item: Option>, - /// The owner providing the allowance. + /// The account that owns the item(s). owner: AccountIdOf, - /// The beneficiary of the allowance. + /// The account that is allowed to withdraw the item(s). operator: AccountIdOf, /// Whether allowance is set or removed. approved: bool, @@ -105,16 +109,8 @@ pub mod pallet { /// The recipient of the transfer. `None` when burning. to: 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, - }, /// Event emitted when an attribute is set for a token. + // Differing style: event name abides by the PSP34 standard. AttributeSet { /// The collection identifier. collection: CollectionIdOf, @@ -125,15 +121,27 @@ pub mod pallet { /// The data for the attribute. data: Vec, }, + /// 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] impl Pallet { - /// Transfers the collection item from the `origin` to account `to`. + /// Transfers an owned or approved item to the specified recipient. + /// + /// Origin must be either the item's owner or an account approved by the owner to + /// transfer the item. /// /// # Parameters - /// - `collection` - The collection of the item to be transferred. - /// - `item` - The item to transfer. + /// - `collection` - The collection identifier. + /// - `item` - The item. /// - `to` - The recipient account. #[pallet::call_index(3)] #[pallet::weight(NftsWeightInfoOf::::transfer() + T::DbWeight::get().reads(1))] @@ -156,65 +164,79 @@ pub mod pallet { Ok(().into()) } - /// Either approve or cancel approval for an `operator` to perform transfers of a specific - /// collection item or all collection items owned by the `origin`. + /// Approves operator to withdraw item(s) from the contract's account. /// /// # Parameters /// - `collection` - The collection identifier. - /// - `item` - An optional parameter specifying the item to approve for the delegated - /// transfer. If `None`, all `origin`'s collection items will be approved. - /// - `operator` - The account being granted or revoked approval to transfer the specified - /// collection item(s). - /// - `approved` - A boolean indicating the desired approval status: - /// - `true` to approve the `operator`. - /// - `false` to cancel the approval delegated to the `operator`. + /// - `item` - Optional item. `None` means all items owned in the specified collection. + /// - `operator` - The account that is allowed to withdraw the item. + /// - `approved` - Whether the operator is given or removed the right to withdraw the + /// item(s). #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::approve(*approved as u32, item.is_some() as u32))] + #[pallet::weight(WeightOf::::approve(*approved as u32, item.is_some() as u32))] pub fn approve( origin: OriginFor, collection: CollectionIdOf, item: Option>, operator: AccountIdOf, approved: bool, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let owner = ensure_signed(origin.clone())?; + let operator_lookup = T::Lookup::unlookup(operator.clone()); let result = if approved { - Self::do_approve(origin, collection, item, &operator) + Self::do_approve(origin, collection, item, operator_lookup) } else { - Self::do_cancel_approval(origin, collection, item, &operator) + Self::do_cancel_approval(origin, collection, item, operator_lookup) }; Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); result } - /// Cancel all the approvals of a specific item. + /// Mints an item to the specified address. /// /// # Parameters - /// - `collection` - The collection of the item of whose approvals will be cleared. - /// - `item` - The item of the collection of whose approvals will be cleared. - #[pallet::call_index(5)] - #[pallet::weight(NftsWeightInfoOf::::clear_all_transfer_approvals())] - pub fn clear_all_transfer_approvals( + /// - `collection` - The collection of the item to mint. + /// - `item` - An identifier of the new item. + /// - `to` - Account into which the item will be minted. + /// - `witness` - 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`. + #[pallet::call_index(7)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( origin: OriginFor, + to: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf, + witness: MintWitness, ItemPriceOf>, ) -> DispatchResult { - NftsOf::::clear_all_transfer_approvals(origin, collection, item) + NftsOf::::mint( + origin, + collection, + item, + T::Lookup::unlookup(to.clone()), + Some(witness), + )?; + Self::deposit_event(Event::Transfer { collection, item, from: None, to: Some(to) }); + Ok(()) } - /// Cancel approvals to transfer all owner's collection items. + /// Destroy a single collection item. Clearing the corresponding approvals. /// /// # Parameters - /// - `collection` - The collection whose approvals will be cleared. - /// - `limit` - The amount of collection approvals that will be cleared. - #[pallet::call_index(6)] - #[pallet::weight(NftsWeightInfoOf::::clear_collection_approvals(*limit))] - pub fn clear_collection_approvals( + /// - `collection` - The collection identifier. + /// - `item` - The item to burn. + #[pallet::call_index(8)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( origin: OriginFor, collection: CollectionIdOf, - limit: u32, - ) -> DispatchResultWithPostInfo { - NftsOf::::clear_collection_approvals(origin, collection, limit) + item: ItemIdOf, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Transfer { collection, item, from: Some(owner), to: None }); + Ok(()) } /// Issue a new collection of non-fungible items. @@ -222,20 +244,21 @@ pub mod pallet { /// # Parameters /// - `admin` - The admin of this collection. /// - `config` - The configuration of the collection. - #[pallet::call_index(7)] - #[pallet::weight(NftsWeightInfoOf::::create())] + #[pallet::call_index(12)] + #[pallet::weight(NftsWeightInfoOf::::create() + T::DbWeight::get().reads(1))] pub fn create( origin: OriginFor, admin: AccountIdOf, config: CollectionConfigOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let creator = ensure_signed(origin.clone())?; let id = NextCollectionIdOf::::get() .or(T::CollectionId::initial_value()) - .ok_or(NftsErrorOf::::UnknownCollection)?; + .ok_or(NftsErrorOf::::UnknownCollection) + .map_err(|e| e.with_weight(T::DbWeight::get().reads(1)))?; NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), config)?; Self::deposit_event(Event::Created { id, creator, admin }); - Ok(()) + Ok(().into()) } /// Destroy a collection of items. @@ -244,7 +267,7 @@ pub mod pallet { /// - `collection` - The collection to destroy. /// - `witness` - Information on the items minted in the collection. This must be /// correct. - #[pallet::call_index(8)] + #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::destroy( witness.item_metadatas, witness.item_configs, @@ -261,13 +284,13 @@ pub mod pallet { /// Set an attribute for a collection or item. /// /// # Parameters - /// - `collection` - The collection whose item's metadata to set. - /// - `item` - The item whose metadata to set. If not provided, the collection's attribute - /// is set. + /// - `collection` - The collection identifier. + /// - `item` - Optional item whose attribute to set. If not provided, the `collection`'s + /// attribute is set. /// - `namespace` - Attribute's namespace. /// - `key` - The key of the attribute. /// - `value` - The value to which to set the attribute. - #[pallet::call_index(12)] + #[pallet::call_index(14)] #[pallet::weight(NftsWeightInfoOf::::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -297,11 +320,12 @@ pub mod pallet { /// Clear an attribute for the collection or item. /// /// # Parameters - /// - `collection` - The collection whose item's metadata to clear. - /// - `item` - The item whose metadata to clear. + /// - `collection` - The collection identifier. + /// - `item` - Optional item whose metadata to clear. If `None`, metadata of the + /// `collection` will be cleared. /// - `namespace` - Attribute's namespace. /// - `key` - The key of the attribute. - #[pallet::call_index(13)] + #[pallet::call_index(15)] #[pallet::weight(NftsWeightInfoOf::::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -316,10 +340,10 @@ pub mod pallet { /// Set the metadata for an item. /// /// # Parameters - /// - `collection` - The collection whose item's metadata to set. + /// - `collection` - The collection identifier. /// - `item` - The item whose metadata to set. /// - `data` - The general information of this item. Limited in length by `StringLimit`. - #[pallet::call_index(14)] + #[pallet::call_index(16)] #[pallet::weight(NftsWeightInfoOf::::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -333,9 +357,9 @@ pub mod pallet { /// Clear the metadata for an item. /// /// # Parameters - /// - `collection` - The collection whose item's metadata to clear. + /// - `collection` - The collection identifier. /// - `item` - The item whose metadata to clear. - #[pallet::call_index(15)] + #[pallet::call_index(17)] #[pallet::weight(NftsWeightInfoOf::::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -348,10 +372,10 @@ pub mod pallet { /// Approve item's attributes to be changed by a delegated third-party account. /// /// # Parameters - /// - `collection` - The collection of the item. + /// - `collection` - The collection identifier. /// - `item` - The item that holds attributes. /// - `delegate` - The account to delegate permission to change attributes of the item. - #[pallet::call_index(16)] + #[pallet::call_index(18)] #[pallet::weight(NftsWeightInfoOf::::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, @@ -371,11 +395,11 @@ pub mod pallet { /// All the previously set attributes by the `delegate` will be removed. /// /// # Parameters - /// - `collection` - The collection that the item is contained within. + /// - `collection` - The collection identifier. /// - `item` - The item that holds attributes. /// - `delegate` - The previously approved account to remove. /// - `witness` - A witness data to cancel attributes approval operation. - #[pallet::call_index(17)] + #[pallet::call_index(19)] #[pallet::weight(NftsWeightInfoOf::::cancel_item_attributes_approval(witness.account_attributes))] pub fn cancel_item_attributes_approval( origin: OriginFor, @@ -398,7 +422,7 @@ pub mod pallet { /// # Parameters /// - `collection` - The collection identifier. /// - `max_supply` - The maximum number of items a collection could have. - #[pallet::call_index(18)] + #[pallet::call_index(20)] #[pallet::weight(NftsWeightInfoOf::::set_collection_max_supply())] pub fn set_max_supply( origin: OriginFor, @@ -408,46 +432,34 @@ pub mod pallet { NftsOf::::set_collection_max_supply(origin, collection, max_supply) } - /// Mint an item of a particular collection. + /// Cancel all the approvals of a specific item. /// /// # Parameters - /// - `to` - Account into which the item will be minted. - /// - `collection` - The collection of the item to mint. - /// - `item` - An identifier of the new item. - /// - `witness` - 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`. - #[pallet::call_index(19)] - #[pallet::weight(NftsWeightInfoOf::::mint())] - pub fn mint( + /// - `collection` - The collection identifier. + /// - `item` - The item of the collection of whose approvals will be cleared. + #[pallet::call_index(21)] + #[pallet::weight(NftsWeightInfoOf::::clear_all_transfer_approvals())] + pub fn clear_all_transfer_approvals( origin: OriginFor, - to: AccountIdOf, collection: CollectionIdOf, item: ItemIdOf, - witness: MintWitness, ItemPriceOf>, ) -> DispatchResult { - let owner = ensure_signed(origin.clone())?; - NftsOf::::mint(origin, collection, item, T::Lookup::unlookup(to), Some(witness))?; - Self::deposit_event(Event::Transfer { collection, item, from: None, to: Some(owner) }); - Ok(()) + NftsOf::::clear_all_transfer_approvals(origin, collection, item) } - /// Destroy a single collection item. + /// Cancel approvals to transfer all owner's collection items. /// /// # Parameters - /// - `collection` - The collection of the item to burn. - /// - `item` - The item to burn. - #[pallet::call_index(20)] - #[pallet::weight(NftsWeightInfoOf::::burn())] - pub fn burn( + /// - `collection` - The collection identifier. + /// - `limit` - The amount of collection approvals that will be cleared. + #[pallet::call_index(22)] + #[pallet::weight(NftsWeightInfoOf::::clear_collection_approvals(*limit))] + pub fn clear_collection_approvals( origin: OriginFor, collection: CollectionIdOf, - item: ItemIdOf, - ) -> DispatchResult { - let owner = ensure_signed(origin.clone())?; - NftsOf::::burn(origin, collection, item)?; - Self::deposit_event(Event::Transfer { collection, item, from: Some(owner), to: None }); - Ok(()) + limit: u32, + ) -> DispatchResultWithPostInfo { + NftsOf::::clear_collection_approvals(origin, collection, limit) } } @@ -457,10 +469,10 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// Total item supply of a specified `collection`. + /// The total supply of a collection. #[codec(index = 0)] TotalSupply(CollectionIdOf), - /// Amount of items the `owner` has within a `collection`. + /// Amount of items the owner has within a collection. #[codec(index = 1)] BalanceOf { /// The collection identifier. @@ -468,34 +480,32 @@ pub mod pallet { /// The account whose balance is being queried. owner: AccountIdOf, }, - /// Allowance for an `operator` approved by an `owner` to transfer a specified `item` or - /// all collection items owned by the `owner`. #[codec(index = 2)] Allowance { /// The collection identifier. collection: CollectionIdOf, - /// The item whose attribute is set. + /// The item. If not provided, it is regarding all owner's collection items. item: Option>, - /// The owner of the collection item(s). + /// The account that owns the item(s). owner: AccountIdOf, /// The account that is allowed to transfer the collection item(s). operator: AccountIdOf, }, - /// Owner of an `item` within a specified `collection`, if any. + /// Owner of an item within a specified collection, if any. #[codec(index = 5)] OwnerOf { /// The collection identifier. collection: CollectionIdOf, - /// The collection item identifier. + /// The item. item: ItemIdOf, }, - /// Attribute value of a specified collection item for a given `key`, if any. + /// Attribute value of a specified collection item for a given key, if any. #[codec(index = 6)] GetAttribute { /// The collection identifier. collection: CollectionIdOf, - /// The collection item identifier. - item: ItemIdOf, + /// The item. If not provided, the attributes for the collection are queried. + item: Option>, /// The namespace of the attribute. namespace: AttributeNamespaceOf, /// The key of the attribute. @@ -512,7 +522,7 @@ pub mod pallet { ItemMetadata { /// The collection identifier. collection: CollectionIdOf, - /// The collection item identifier. + /// The item. item: ItemIdOf, }, } @@ -521,7 +531,7 @@ pub mod pallet { #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - /// Total item supply of a specified `collection`. + /// The total supply of a collection. TotalSupply(u128), /// Amount of items the `owner` has within a `collection`. BalanceOf(u32), @@ -550,8 +560,8 @@ pub mod pallet { Allowance(result) => result.encode(), OwnerOf(result) => result.encode(), GetAttribute(result) => result.encode(), - ItemMetadata(result) => result.encode(), Collection(result) => result.encode(), + ItemMetadata(result) => result.encode(), NextCollectionId(result) => result.encode(), } } @@ -571,14 +581,14 @@ pub mod pallet { fn weight(request: &Self::Read) -> Weight { use Read::*; match request { - TotalSupply(_) => ::WeightInfo::total_supply(), - BalanceOf { .. } => ::WeightInfo::balance_of(), - Allowance { .. } => ::WeightInfo::allowance(), - OwnerOf { .. } => ::WeightInfo::owner_of(), - GetAttribute { .. } => ::WeightInfo::get_attribute(), - ItemMetadata { .. } => ::WeightInfo::item_metadata(), - Collection(_) => ::WeightInfo::collection(), - NextCollectionId => ::WeightInfo::next_collection_id(), + TotalSupply(_) => WeightOf::::total_supply(), + BalanceOf { .. } => WeightOf::::balance_of(), + Allowance { .. } => WeightOf::::allowance(), + OwnerOf { .. } => WeightOf::::owner_of(), + GetAttribute { .. } => WeightOf::::get_attribute(), + Collection(_) => WeightOf::::collection(), + ItemMetadata { .. } => WeightOf::::item_metadata(), + NextCollectionId => WeightOf::::next_collection_id(), } } @@ -604,14 +614,14 @@ pub mod pallet { OwnerOf { collection, item } => ReadResult::OwnerOf(NftsOf::::owner(collection, item)), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( - AttributeOf::::get((collection, Some(item), namespace, key)) + AttributeOf::::get((collection, item, namespace, key)) .map(|attribute| attribute.0.into()), ), + Collection(collection) => + ReadResult::Collection(CollectionOf::::get(collection)), ItemMetadata { collection, item } => ReadResult::ItemMetadata( NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), ), - Collection(collection) => - ReadResult::Collection(CollectionOf::::get(collection)), 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 f005cf966..38d42d8b4 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -3,9 +3,8 @@ use frame_support::{ assert_noop, assert_ok, dispatch::WithPostDispatchInfo, sp_runtime::{traits::Zero, BoundedVec, DispatchError::BadOrigin}, - weights::Weight, }; -use pallet_nfts::WeightInfo as NftsWeightInfoTrait; +use pallet_nfts::{CollectionSetting, WeightInfo as NftsWeightInfoTrait}; use scale_info::TypeInfo; use crate::{ @@ -15,7 +14,7 @@ use crate::{ AttributeOf, BalanceOf as DepositBalanceOf, BlockNumberFor, CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, CollectionOf, CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, MintWitness, - NextCollectionIdOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, + NextCollectionIdOf, NftsErrorOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, }, Read, }; @@ -24,8 +23,9 @@ const COLLECTION: u32 = 0; const ITEM: u32 = 1; type CollectionApprovals = pallet_nfts::CollectionApprovals>; +type DbWeight = ::DbWeight; type Event = crate::nonfungibles::Event; -type NftsError = pallet_nfts::Error>; +type NftsError = NftsErrorOf; type WeightInfo = ::WeightInfo; /// Information about a collection. @@ -39,117 +39,74 @@ struct CollectionDetails { pub(super) attributes: u32, } -#[test] -fn transfer_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let dest = BOB; - let item = ITEM; - let owner = ALICE; - - // Check error works for `Nfts::transfer`. - assert_noop!( - NonFungibles::transfer(signed(owner), collection, item, dest), - NftsError::UnknownItem.with_weight(Weight::from_parts(0, 0)) - ); - nfts::create_collection_mint(owner, owner, item); - // Successfully transfer a collection item. - let from_balance_before_transfer = nfts::balance_of(collection, &owner); - let to_balance_before_transfer = nfts::balance_of(collection, &dest); - assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); - let from_balance_after_transfer = nfts::balance_of(collection, &owner); - let to_balance_after_transfer = nfts::balance_of(collection, &dest); - // Check that `to` has received the collection item from `from`. - assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); - assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); - System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), - ); - }); -} - -#[test] -fn approved_transfer_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let dest = CHARLIE; - let item = ITEM; - let operator = BOB; - let owner = ALICE; - - nfts::create_collection_mint(owner, owner, item); - // Approve `operator` to transfer `collection` items owned by the `owner`. - assert_ok!(Nfts::approve_collection_transfer(signed(owner), collection, operator, None)); - // Successfully transfer a collection item. - let from_balance_before_transfer = nfts::balance_of(collection, &owner); - let to_balance_before_transfer = nfts::balance_of(collection, &dest); - assert_ok!(NonFungibles::transfer(signed(operator), collection, item, dest)); - let from_balance_after_transfer = nfts::balance_of(collection, &owner); - let to_balance_after_transfer = nfts::balance_of(collection, &dest); - // Check that `to` has received the collection item from `from`. - assert_eq!(to_balance_after_transfer, to_balance_before_transfer + 1); - assert_eq!(from_balance_after_transfer, from_balance_before_transfer - 1); - System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), - ); - }); -} +mod transfer { + use super::*; -#[test] -fn mint_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; - let owner = ALICE; - let witness = MintWitness { mint_price: None, owned_item: None }; + #[test] + fn transfer_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let dest = BOB; + let item = ITEM; + let owner = ALICE; - // Origin checks. - for origin in vec![root(), none()] { + // Throw `NftsError::UnknownItem` if no item found. assert_noop!( - NonFungibles::mint(origin, owner, collection, item, witness.clone()), - BadOrigin + NonFungibles::transfer(signed(owner), collection, item, dest), + NftsError::UnknownItem.with_weight(DbWeight::get().reads(1)) ); - } - // Check error works for `Nfts::mint()`. - assert_noop!( - NonFungibles::mint(signed(owner), owner, collection, item, witness.clone()), - NftsError::NoConfig - ); - // Successfully mint a new collection item. - nfts::create_collection(owner); - let balance_before_mint = nfts::balance_of(collection, &owner); - assert_ok!(NonFungibles::mint(signed(owner), owner, collection, item, witness)); - let balance_after_mint = nfts::balance_of(collection, &owner); - assert_eq!(balance_after_mint, balance_before_mint + 1); - System::assert_last_event( - Event::Transfer { collection, item, from: None, to: Some(owner) }.into(), - ); - }); -} + nfts::create_collection_and_mint(owner, owner, item); + // Check error works for `Nfts::transfer()`. + assert_noop!( + NonFungibles::transfer(signed(dest), collection, item, dest), + NftsError::NoPermission + ); + // Successfully transfer a collection item. + let owner_balance_before_transfer = nfts::balance_of(collection, &owner); + let dest_balance_before_transfer = nfts::balance_of(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(owner), collection, item, dest)); + let owner_balance_after_transfer = nfts::balance_of(collection, &owner); + let dest_balance_after_transfer = nfts::balance_of(collection, &dest); + // Check that `to` has received the collection item from `from`. + assert_eq!(dest_balance_after_transfer, dest_balance_before_transfer + 1); + assert_eq!(owner_balance_after_transfer, owner_balance_before_transfer - 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), + ); + }); + } -#[test] -fn burn_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let owner = ALICE; - let item = ITEM; + #[test] + fn approved_transfer_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let dest = CHARLIE; + let item = ITEM; + let operator = BOB; + let owner = ALICE; - // Origin checks. - for origin in vec![root(), none()] { - assert_noop!(NonFungibles::burn(origin, collection, item), BadOrigin); - } - // Check error works for `Nfts::burn()`. - assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); - nfts::create_collection_mint(owner, owner, item); - // Successfully burn a collection item. - let balance_before_burn = nfts::balance_of(collection, &owner); - assert_ok!(NonFungibles::burn(signed(owner), collection, item)); - let balance_after_burn = nfts::balance_of(collection, &owner); - assert_eq!(balance_after_burn, balance_before_burn - 1); - System::assert_last_event( - Event::Transfer { collection, item, from: Some(owner), to: None }.into(), - ); - }); + nfts::create_collection_and_mint(owner, owner, item); + // Approve `operator` to transfer all `collection` items owned by the `owner`. + assert_ok!(Nfts::approve_collection_transfer( + signed(owner), + collection, + operator, + None + )); + // Successfully transfer a collection item. + let owner_balance_before_transfer = nfts::balance_of(collection, &owner); + let dest_balance_before_transfer = nfts::balance_of(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(operator), collection, item, dest)); + let owner_balance_after_transfer = nfts::balance_of(collection, &owner); + let dest_balance_after_transfer = nfts::balance_of(collection, &dest); + // Check that `to` has received the collection item from `from`. + assert_eq!(dest_balance_after_transfer, dest_balance_before_transfer + 1); + assert_eq!(owner_balance_after_transfer, owner_balance_before_transfer - 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest) }.into(), + ); + }); + } } mod approve { @@ -178,15 +135,18 @@ mod approve { // Check error works for `Nfts::approve_transfer()`. assert_noop!( NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - NftsError::UnknownItem.with_weight(WeightInfo::approve(1, 1)) + NftsError::UnknownItem ); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); // Successfully approve `operator` to transfer the collection item. - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, true), - Ok(Some(WeightInfo::approve(1, 1)).into()) - ); + assert_ok!(NonFungibles::approve( + signed(owner), + collection, + Some(item), + operator, + true + )); assert_ok!(Nfts::check_approval_permission( &collection, &Some(item), @@ -211,15 +171,12 @@ mod approve { // Check error works for `Nfts::approve_collection_transfer()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, true), - NftsError::NoItemOwned.with_weight(WeightInfo::approve(1, 0)) + NftsError::NoItemOwned ); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); // Successfully approve `operator` to transfer all collection items owned by `owner`. - assert_eq!( - NonFungibles::approve(signed(owner), collection, None, operator, true), - Ok(Some(WeightInfo::approve(1, 0)).into()) - ); + assert_ok!(NonFungibles::approve(signed(owner), collection, None, operator, true)); assert_ok!(Nfts::check_approval_permission(&collection, &None, &owner, &operator)); System::assert_last_event( Event::Approval { collection, item: None, owner, operator, approved: true }.into(), @@ -238,15 +195,18 @@ mod approve { // Check error works for `Nfts::cancel_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - NftsError::UnknownItem.with_weight(WeightInfo::approve(0, 1)) + NftsError::UnknownItem ); nfts::create_collection_mint_and_approve(owner, owner, item, operator); // Successfully cancel the transfer approval of `operator` by `owner`. - assert_eq!( - NonFungibles::approve(signed(owner), collection, Some(item), operator, false), - Ok(Some(WeightInfo::approve(0, 1)).into()) - ); + assert_ok!(NonFungibles::approve( + signed(owner), + collection, + Some(item), + operator, + false + )); assert_eq!( Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator), Err(NftsError::NoPermission.into()) @@ -265,10 +225,10 @@ mod approve { // Check error works for `Nfts::cancel_collection_approval()`. assert_noop!( NonFungibles::approve(signed(owner), collection, None, operator, false), - NftsError::NotDelegate.with_weight(WeightInfo::approve(0, 0)) + NftsError::NotDelegate ); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); assert_ok!(Nfts::approve_collection_transfer( signed(owner), collection, @@ -276,10 +236,7 @@ mod approve { None )); // Successfully cancel the transfer collection approval of `operator` by `owner`. - assert_eq!( - NonFungibles::approve(signed(owner), collection, None, operator, false), - Ok(Some(WeightInfo::approve(0, 0)).into()) - ); + assert_ok!(NonFungibles::approve(signed(owner), collection, None, operator, false)); assert_eq!( Nfts::check_approval_permission(&collection, &None, &owner, &operator), Err(NftsError::NoPermission.into()) @@ -289,129 +246,105 @@ mod approve { } #[test] -fn clear_all_transfer_approvals_works() { +fn mint_works() { new_test_ext().execute_with(|| { let collection = COLLECTION; - let delegates = 10..20; let item = ITEM; let owner = ALICE; + let witness = MintWitness { mint_price: None, owned_item: None }; - // Check error works for `Nfts::clear_all_transfer_approvals()`. + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!( + NonFungibles::mint(origin, owner, collection, item, witness.clone()), + BadOrigin + ); + } + // Check error works for `Nfts::mint()`. assert_noop!( - NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item), - NftsError::UnknownCollection - ); - - nfts::create_collection_mint(owner, owner, item); - delegates.clone().for_each(|delegate| { - assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, delegate, None)); - }); - // Successfully clear all transfer approvals. - assert_ok!(NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item)); - delegates.for_each(|delegate| { - assert!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &delegate) - .is_err()); - }); - }); -} - -#[test] -fn clear_collection_approvals_works() { - new_test_ext().execute_with(|| { - let collection = COLLECTION; - let delegates = 10..20; - let owner = ALICE; - let approvals = (delegates.end - delegates.start) as u32; - - nfts::create_collection_mint(owner, owner, ITEM); - delegates.clone().for_each(|delegate| { - assert_ok!(Nfts::approve_collection_transfer( - signed(owner), - collection, - delegate, - None - )); - }); - // Partially clear collection approvals. - assert_eq!( - NonFungibles::clear_collection_approvals(signed(owner), collection, 1), - Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(1)).into()) - ); - assert_eq!( - CollectionApprovals::iter_prefix((collection, owner,)).count(), - (approvals - 1) as usize + NonFungibles::mint(signed(owner), owner, collection, item, witness.clone()), + NftsError::NoConfig ); - // Successfully clear all collection approvals. - assert_eq!( - NonFungibles::clear_collection_approvals(signed(owner), collection, approvals), - Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(approvals - 1)).into()) + // Successfully mint a new collection item. + nfts::create_collection(owner); + let balance_before_mint = nfts::balance_of(collection, &owner); + assert_ok!(NonFungibles::mint(signed(owner), owner, collection, item, witness)); + let balance_after_mint = nfts::balance_of(collection, &owner); + assert_eq!(balance_after_mint, balance_before_mint + 1); + System::assert_last_event( + Event::Transfer { collection, item, from: None, to: Some(owner) }.into(), ); - assert!(CollectionApprovals::iter_prefix((collection, owner,)).count().is_zero()); }); } #[test] -fn set_max_supply_works() { +fn burn_works() { new_test_ext().execute_with(|| { let collection = COLLECTION; let owner = ALICE; - let max_supply = 10; + let item = ITEM; - nfts::create_collection(owner); - // Successfully set the max supply for the collection. - assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply)); - (0..max_supply).into_iter().for_each(|i| { - assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); - }); - // Throws `MaxSupplyReached` error if number of minted items is over the max supply. - assert_noop!( - Nfts::mint(signed(owner), collection, 42, owner, None), - NftsError::MaxSupplyReached + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::burn(origin, collection, item), BadOrigin); + } + // Check error works for `Nfts::burn()`. + assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); + nfts::create_collection_and_mint(owner, owner, item); + // Successfully burn a collection item. + let balance_before_burn = nfts::balance_of(collection, &owner); + assert_ok!(NonFungibles::burn(signed(owner), collection, item)); + let balance_after_burn = nfts::balance_of(collection, &owner); + assert_eq!(balance_after_burn, balance_before_burn - 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: None }.into(), ); - // Override the max supply. - assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply * 2)); - assert_ok!(Nfts::mint(signed(owner), collection, 42, owner, None)); }); } #[test] -fn set_metadata_works() { +fn create_works() { new_test_ext().execute_with(|| { + let admin = ALICE; + let mut config = CollectionConfig { + max_supply: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), + }; let collection = COLLECTION; - let item = ITEM; - let metadata = BoundedVec::truncate_from("some metadata".into()); - let owner = ALICE; + let creator = ALICE; - // Check error works for `Nfts::set_metadata()`. - assert_noop!( - NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone()), - NftsError::NoPermission - ); - nfts::create_collection_mint(owner, owner, item); - // Successfully set the metadata. - assert_ok!(NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone())); - assert_eq!(Nfts::item_metadata(collection, item), Some(metadata)); + // Origin checks. + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::create(origin, admin, config), BadOrigin); + } + + // Check error works for `Nfts::create()`. + config.disable_setting(CollectionSetting::DepositRequired); + assert_noop!(NonFungibles::create(signed(creator), admin, config), NftsError::WrongSetting); + config.enable_setting(CollectionSetting::DepositRequired); + // Successfully create a collection. + assert_ok!(NonFungibles::create(signed(creator), admin, config)); + assert_eq!(Nfts::collection_owner(collection), Some(creator)); + System::assert_last_event(Event::Created { id: collection, creator, admin }.into()); }); } #[test] -fn clear_metadata_works() { +fn destroy_works() { new_test_ext().execute_with(|| { let collection = COLLECTION; - let item = ITEM; - let metadata = BoundedVec::truncate_from("some metadata".into()); - let owner = ALICE; + let witness = DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 }; - // Check error works for `Nfts::clear_metadata()`. + // Check error works for `Nfts::destroy()`. assert_noop!( - NonFungibles::clear_metadata(signed(owner), collection, item), - NftsError::NoPermission + NonFungibles::destroy(signed(ALICE), collection, witness), + NftsError::UnknownCollection ); - nfts::create_collection_mint(owner, owner, item); - assert_ok!(Nfts::set_metadata(signed(owner), collection, item, metadata)); - // Successfully clear the metadata. - assert_ok!(NonFungibles::clear_metadata(signed(owner), collection, item)); - assert!(Nfts::item_metadata(collection, item).is_none()); + nfts::create_collection(ALICE); + // Successfully destroy a collection. + assert_ok!(NonFungibles::destroy(signed(ALICE), collection, witness)); + assert_eq!(Nfts::collection_owner(collection), None); }); } @@ -436,7 +369,7 @@ fn set_attribute_works() { ), NftsError::UnknownCollection ); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); // Successfully set attribute. assert_ok!(NonFungibles::set_attribute( signed(owner), @@ -476,7 +409,20 @@ fn clear_attribute_works() { let owner = ALICE; let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - nfts::create_collection_mint(owner, owner, item); + // Check error works for `Nfts::clear_attribute()`. + assert_noop!( + Nfts::set_attribute( + signed(owner), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute.clone(), + value.clone() + ), + NftsError::UnknownCollection + ); + + nfts::create_collection_and_mint(owner, owner, item); assert_ok!(Nfts::set_attribute( signed(owner), collection, @@ -503,6 +449,47 @@ fn clear_attribute_works() { }); } +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let metadata = BoundedVec::truncate_from("some metadata".into()); + let owner = ALICE; + + // Check error works for `Nfts::set_metadata()`. + assert_noop!( + NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone()), + NftsError::NoPermission + ); + nfts::create_collection_and_mint(owner, owner, item); + // Successfully set the metadata. + assert_ok!(NonFungibles::set_metadata(signed(owner), collection, item, metadata.clone())); + assert_eq!(Nfts::item_metadata(collection, item), Some(metadata)); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let metadata = BoundedVec::truncate_from("some metadata".into()); + let owner = ALICE; + + // Check error works for `Nfts::clear_metadata()`. + assert_noop!( + NonFungibles::clear_metadata(signed(owner), collection, item), + NftsError::NoPermission + ); + nfts::create_collection_and_mint(owner, owner, item); + assert_ok!(Nfts::set_metadata(signed(owner), collection, item, metadata)); + // Successfully clear the metadata. + assert_ok!(NonFungibles::clear_metadata(signed(owner), collection, item)); + assert!(Nfts::item_metadata(collection, item).is_none()); + }); +} + #[test] fn approve_item_attribute_works() { new_test_ext().execute_with(|| { @@ -513,7 +500,13 @@ fn approve_item_attribute_works() { let owner = ALICE; let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - nfts::create_collection_mint(owner, owner, item); + // Check error works for `Nfts::approve_item_attributes()`. + assert_noop!( + NonFungibles::approve_item_attributes(signed(owner), collection, item, delegate), + NftsError::UnknownItem + ); + + nfts::create_collection_and_mint(owner, owner, item); // Successfully approve delegate to set attributes. assert_ok!(NonFungibles::approve_item_attributes( signed(owner), @@ -542,7 +535,19 @@ fn cancel_item_attribute_approval_works() { let owner = ALICE; let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); - nfts::create_collection_mint(owner, owner, item); + // Check error works for `Nfts::approve_item_attributes()`. + assert_noop!( + NonFungibles::cancel_item_attributes_approval( + signed(owner), + collection, + item, + delegate, + CancelAttributesApprovalWitness { account_attributes: 1 } + ), + NftsError::UnknownItem + ); + + nfts::create_collection_and_mint(owner, owner, item); assert_ok!(Nfts::approve_item_attributes(signed(owner), collection, item, delegate)); // Successfully cancel item attribute approval. assert_ok!(Nfts::cancel_item_attributes_approval( @@ -567,42 +572,88 @@ fn cancel_item_attribute_approval_works() { } #[test] -fn create_works() { +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let owner = ALICE; + let max_supply = 10; + + nfts::create_collection(owner); + // Successfully set the max supply for the collection. + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply)); + (0..max_supply).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + }); + // Throws `MaxSupplyReached` error if number of minted items is over the max supply. + assert_noop!( + Nfts::mint(signed(owner), collection, 42, owner, None), + NftsError::MaxSupplyReached + ); + // Override the max supply. + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, max_supply * 2)); + assert_ok!(Nfts::mint(signed(owner), collection, 42, owner, None)); + }); +} + +#[test] +fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - let admin = ALICE; - let config = CollectionConfig { - max_supply: None, - mint_settings: MintSettings::default(), - settings: CollectionSettings::all_enabled(), - }; let collection = COLLECTION; - let creator = ALICE; + let delegates = 10..20; + let item = ITEM; + let owner = ALICE; - // Origin checks. - for origin in vec![root(), none()] { - assert_noop!(NonFungibles::create(origin, admin, config.clone()), BadOrigin); - } - // Successfully create a collection. - assert_ok!(NonFungibles::create(signed(creator), admin, config)); - assert_eq!(Nfts::collection_owner(collection), Some(creator)); - System::assert_last_event(Event::Created { id: collection, creator, admin }.into()); + // Check error works for `Nfts::clear_all_transfer_approvals()`. + assert_noop!( + NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item), + NftsError::UnknownCollection + ); + + nfts::create_collection_and_mint(owner, owner, item); + delegates.clone().for_each(|delegate| { + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, delegate, None)); + }); + // Successfully clear all transfer approvals. + assert_ok!(NonFungibles::clear_all_transfer_approvals(signed(owner), collection, item)); + delegates.for_each(|delegate| { + assert!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &delegate) + .is_err()); + }); }); } #[test] -fn destroy_works() { +fn clear_collection_approvals_works() { new_test_ext().execute_with(|| { let collection = COLLECTION; - let witness = DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 }; + let delegates = 10..20; + let owner = ALICE; + let approvals = (delegates.end - delegates.start) as u32; - // Check error works for `Nfts::destroy()`. - assert_noop!( - NonFungibles::destroy(signed(ALICE), collection, witness), - NftsError::UnknownCollection + nfts::create_collection_and_mint(owner, owner, ITEM); + delegates.clone().for_each(|delegate| { + assert_ok!(Nfts::approve_collection_transfer( + signed(owner), + collection, + delegate, + None + )); + }); + // Partially clear collection approvals. + assert_eq!( + NonFungibles::clear_collection_approvals(signed(owner), collection, 1), + Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(1)).into()) ); - nfts::create_collection(ALICE); - assert_ok!(NonFungibles::destroy(signed(ALICE), collection, witness)); - assert_eq!(Nfts::collection_owner(collection), None); + assert_eq!( + CollectionApprovals::iter_prefix((collection, owner,)).count(), + (approvals - 1) as usize + ); + // Successfully clear all collection approvals. + assert_eq!( + NonFungibles::clear_collection_approvals(signed(owner), collection, approvals), + Ok(Some(NftsWeightInfoOf::::clear_collection_approvals(approvals - 1)).into()) + ); + assert!(CollectionApprovals::iter_prefix((collection, owner,)).count().is_zero()); }); } @@ -682,7 +733,7 @@ fn owner_of_works() { let owner = ALICE; assert_eq!(NonFungibles::read(OwnerOf { collection, item }), ReadResult::OwnerOf(None)); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); assert_eq!( NonFungibles::read(OwnerOf { collection, item }), ReadResult::OwnerOf(Some(owner)) @@ -694,61 +745,121 @@ fn owner_of_works() { }); } -#[test] -fn get_attribute_works() { - new_test_ext().execute_with(|| { - let attribute = BoundedVec::truncate_from("some attribute".into()); - let collection = COLLECTION; - let item = ITEM; - let metadata = "some value".as_bytes().to_vec(); - let owner = ALICE; +mod get_attribute { + use super::*; - nfts::create_collection_mint(owner, owner, item); - // No attribute set. - assert_eq!( - NonFungibles::read(GetAttribute { - collection, - item, - namespace: AttributeNamespace::CollectionOwner, - key: attribute.clone() - }), - ReadResult::GetAttribute(None) - ); - // Successfully get an existing attribute. - assert_ok!(Nfts::set_attribute( - signed(owner), - collection, - Some(item), - AttributeNamespace::CollectionOwner, - attribute.clone(), - BoundedVec::truncate_from(metadata.clone()), - )); - assert_eq!( - NonFungibles::read(GetAttribute { - collection, - item, - namespace: AttributeNamespace::CollectionOwner, - key: attribute.clone() - }), - ReadResult::GetAttribute(Some(metadata)) - ); - assert_eq!( - NonFungibles::read(GetAttribute { - collection, - item, - namespace: AttributeNamespace::CollectionOwner, - key: attribute.clone() - }) - .encode(), - nfts::get_attribute( + #[test] + fn get_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".into()); + let collection = COLLECTION; + let item = ITEM; + let metadata = "some value".as_bytes().to_vec(); + let owner = ALICE; + + nfts::create_collection_and_mint(owner, owner, item); + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(None) + ); + // Successfully get an existing attribute. + assert_ok!(Nfts::set_attribute( + signed(owner), collection, Some(item), AttributeNamespace::CollectionOwner, - attribute - ) - .encode() - ); - }); + attribute.clone(), + BoundedVec::truncate_from(metadata.clone()), + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(Some(metadata)) + ); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + nfts::get_attribute( + collection, + Some(item), + AttributeNamespace::CollectionOwner, + attribute + ) + .encode() + ); + }); + } + + #[test] + fn get_collection_attribute_works() { + new_test_ext().execute_with(|| { + let attribute = BoundedVec::truncate_from("some attribute".into()); + let collection = COLLECTION; + let metadata = "some value".as_bytes().to_vec(); + let owner = ALICE; + + nfts::create_collection(owner); + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: None, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(None) + ); + // Successfully get an existing attribute. + assert_ok!(Nfts::set_attribute( + signed(owner), + collection, + None, + AttributeNamespace::CollectionOwner, + attribute.clone(), + BoundedVec::truncate_from(metadata.clone()), + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: None, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }), + ReadResult::GetAttribute(Some(metadata)) + ); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item: None, + namespace: AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + nfts::get_attribute( + collection, + None, + AttributeNamespace::CollectionOwner, + attribute + ) + .encode() + ); + }); + } } #[test] @@ -759,7 +870,7 @@ fn collection_works() { let owner = ALICE; assert_eq!(NonFungibles::read(Collection(collection)), ReadResult::Collection(None)); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); assert_eq!( NonFungibles::read(Collection(collection)), ReadResult::Collection(CollectionOf::::get(collection)), @@ -771,6 +882,19 @@ fn collection_works() { }); } +#[test] +fn next_collection_id_works() { + new_test_ext().execute_with(|| { + assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(0))); + nfts::create_collection_and_mint(ALICE, ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(1))); + assert_eq!( + NonFungibles::read(NextCollectionId).encode(), + Some(NextCollectionIdOf::::get().unwrap_or_default()).encode(), + ); + }); +} + #[test] fn item_metadata_works() { new_test_ext().execute_with(|| { @@ -784,7 +908,7 @@ fn item_metadata_works() { NonFungibles::read(ItemMetadata { collection, item }), ReadResult::ItemMetadata(None) ); - nfts::create_collection_mint(owner, owner, item); + nfts::create_collection_and_mint(owner, owner, item); // Successfully set the metadata of an item. assert_ok!(NonFungibles::set_metadata( signed(owner), @@ -803,19 +927,6 @@ fn item_metadata_works() { }); } -#[test] -fn next_collection_id_works() { - new_test_ext().execute_with(|| { - assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(0))); - nfts::create_collection_mint(ALICE, ALICE, ITEM); - assert_eq!(NonFungibles::read(NextCollectionId), ReadResult::NextCollectionId(Some(1))); - assert_eq!( - NonFungibles::read(NextCollectionId).encode(), - Some(NextCollectionIdOf::::get().unwrap_or_default()).encode(), - ); - }); -} - // Helper functions for interacting with pallet-nfts. mod nfts { use super::*; @@ -827,17 +938,17 @@ mod nfts { .unwrap_or_default() } - pub(super) fn create_collection_mint_and_approve( - owner: AccountId, - mint_to: AccountId, - item: ItemIdOf, - operator: AccountId, - ) { - let (collection, item) = create_collection_mint(owner, mint_to, item); - assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, operator, None)); + pub(super) fn create_collection(owner: AccountId) -> u32 { + let next_id = NextCollectionIdOf::::get().unwrap_or_default(); + assert_ok!(Nfts::create( + signed(owner), + owner, + collection_config_with_all_settings_enabled() + )); + next_id } - pub(super) fn create_collection_mint( + pub(super) fn create_collection_and_mint( owner: AccountId, mint_to: AccountId, item: ItemIdOf, @@ -847,14 +958,15 @@ mod nfts { (collection, item) } - pub(super) fn create_collection(owner: AccountId) -> u32 { - let next_id = NextCollectionIdOf::::get().unwrap_or_default(); - assert_ok!(Nfts::create( - signed(owner), - owner, - collection_config_with_all_settings_enabled() - )); - next_id + pub(super) fn create_collection_mint_and_approve( + owner: AccountId, + mint_to: AccountId, + item: ItemIdOf, + operator: AccountId, + ) { + let (collection, item) = create_collection_and_mint(owner, mint_to, item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, item, operator, None)); + assert_ok!(Nfts::check_approval_permission(&collection, &Some(item), &owner, &operator)); } pub(super) fn collection_config_with_all_settings_enabled( @@ -906,22 +1018,20 @@ mod ensure_codec_indexes { "approve", ), ( - clear_all_transfer_approvals { + mint { + to: Default::default(), collection: Default::default(), item: Default::default(), + witness: MintWitness { + owned_item: Default::default(), + mint_price: Default::default(), + }, }, - 5, - "clear_all_transfer_approvals", - ), - ( - clear_collection_approvals { - collection: Default::default(), - limit: Default::default(), - }, - 6, - "clear_collection_approvals", + 7, + "mint", ), - (create { admin: Default::default(), config: Default::default() }, 7, "create"), + (burn { collection: Default::default(), item: Default::default() }, 8, "burn"), + (create { admin: Default::default(), config: Default::default() }, 12, "create"), ( destroy { collection: Default::default(), @@ -931,7 +1041,7 @@ mod ensure_codec_indexes { attributes: Default::default(), }, }, - 8, + 13, "destroy", ), ( @@ -942,7 +1052,7 @@ mod ensure_codec_indexes { key: Default::default(), value: Default::default(), }, - 12, + 14, "set_attribute", ), ( @@ -952,7 +1062,7 @@ mod ensure_codec_indexes { namespace: AttributeNamespace::CollectionOwner, key: Default::default(), }, - 13, + 15, "clear_attribute", ), ( @@ -961,12 +1071,12 @@ mod ensure_codec_indexes { item: Default::default(), data: Default::default(), }, - 14, + 16, "set_metadata", ), ( clear_metadata { collection: Default::default(), item: Default::default() }, - 15, + 17, "clear_metadata", ), ( @@ -975,7 +1085,7 @@ mod ensure_codec_indexes { item: Default::default(), delegate: Default::default(), }, - 16, + 18, "approve_item_attributes", ), ( @@ -987,28 +1097,30 @@ mod ensure_codec_indexes { account_attributes: Default::default(), }, }, - 17, + 19, "cancel_item_attributes_approval", ), ( set_max_supply { collection: Default::default(), max_supply: Default::default() }, - 18, + 20, "set_max_supply", ), ( - mint { - to: Default::default(), + clear_all_transfer_approvals { collection: Default::default(), item: Default::default(), - witness: MintWitness { - owned_item: Default::default(), - mint_price: Default::default(), - }, }, - 19, - "mint", + 21, + "clear_all_transfer_approvals", + ), + ( + clear_collection_approvals { + collection: Default::default(), + limit: Default::default(), + }, + 22, + "clear_collection_approvals", ), - (burn { collection: Default::default(), item: Default::default() }, 20, "burn"), ] .iter() .for_each(|(variant, expected_index, name)| { @@ -1102,7 +1214,7 @@ mod read_weights { owner_of: NonFungibles::weight(&OwnerOf { collection: COLLECTION, item: ITEM }), get_attribute: NonFungibles::weight(&GetAttribute { collection: COLLECTION, - item: ITEM, + item: Some(ITEM), namespace: AttributeNamespace::CollectionOwner, key: BoundedVec::default(), }), diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index a04580a87..4f6c0817e 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -27,7 +27,7 @@ use frame_support::{ BoundedVec, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; -use sp_runtime::traits::{Bounded, One}; +use sp_runtime::traits::{Bounded, One, Zero}; use super::*; use crate::Pallet as Nfts; diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 2526d8366..ac5c8fe1a 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::{ use frame_system::Config as SystemConfig; pub use pallet::*; use sp_runtime::{ - traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, + traits::{IdentifyAccount, Saturating, StaticLookup, Verify}, RuntimeDebug, }; pub use types::*; diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index c7926a7f7..9f74106c9 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -170,15 +170,15 @@ impl> Contains f c, RuntimeCall::NonFungibles( transfer { .. } | - approve { .. } | clear_all_transfer_approvals { .. } | - clear_collection_approvals { .. } | - create { .. } | destroy { .. } | - set_metadata { .. } | clear_metadata { .. } | - set_attribute { .. } | clear_attribute { .. } | + approve { .. } | mint { .. } | + burn { .. } | create { .. } | + destroy { .. } | set_metadata { .. } | + clear_metadata { .. } | set_attribute { .. } | + clear_attribute { .. } | approve_item_attributes { .. } | cancel_item_attributes_approval { .. } | - mint { .. } | burn { .. } | - set_max_supply { .. }, + set_max_supply { .. } | clear_all_transfer_approvals { .. } | + clear_collection_approvals { .. }, ) ) }; @@ -210,8 +210,8 @@ impl Contains for Filter { TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | OwnerOf { .. } | GetAttribute { .. } | - Collection { .. } | NextCollectionId | - ItemMetadata { .. }, + Collection { .. } | ItemMetadata { .. } | + NextCollectionId, ) ) }; @@ -321,8 +321,13 @@ mod tests { operator: ACCOUNT, approved: false, }), - NonFungibles(clear_all_transfer_approvals { collection: 0, item: 0 }), - NonFungibles(clear_collection_approvals { collection: 0, limit: 0 }), + NonFungibles(mint { + to: ACCOUNT, + collection: 0, + item: 0, + witness: MintWitness { mint_price: None, owned_item: None }, + }), + NonFungibles(burn { collection: 0, item: 0 }), NonFungibles(create { admin: ACCOUNT, config: CollectionConfig { @@ -358,13 +363,8 @@ mod tests { witness: CancelAttributesApprovalWitness { account_attributes: 0 }, }), NonFungibles(set_max_supply { collection: 0, max_supply: 0 }), - NonFungibles(mint { - to: ACCOUNT, - collection: 0, - item: 0, - witness: MintWitness { mint_price: None, owned_item: None }, - }), - NonFungibles(burn { collection: 0, item: 0 }), + NonFungibles(clear_all_transfer_approvals { collection: 0, item: 0 }), + NonFungibles(clear_collection_approvals { collection: 0, limit: 0 }), ] .iter() { @@ -388,13 +388,13 @@ mod tests { NonFungibles(OwnerOf { collection: 1, item: 1 }), NonFungibles(GetAttribute { collection: 1, - item: 1, + item: Some(1), namespace: pallet_nfts::AttributeNamespace::CollectionOwner, key: bounded_vec![], }), NonFungibles(Collection(1)), - NonFungibles(NextCollectionId), NonFungibles(ItemMetadata { collection: 1, item: 1 }), + NonFungibles(NextCollectionId), ] .iter() { From 4bfbafe8304f58a55ba64c163a927089d466fd4c Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:03:49 +0700 Subject: [PATCH 20/25] chore: update function order --- pallets/api/src/nonfungibles/impls.rs | 4 +- pallets/api/src/nonfungibles/mod.rs | 90 +++++++++++++------------- pallets/api/src/nonfungibles/tests.rs | 92 +++++++++++++-------------- runtime/devnet/src/config/api/mod.rs | 12 ++-- 4 files changed, 101 insertions(+), 97 deletions(-) diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs index 6429e0343..7cc35e4f6 100644 --- a/pallets/api/src/nonfungibles/impls.rs +++ b/pallets/api/src/nonfungibles/impls.rs @@ -6,7 +6,7 @@ use super::{pallet::*, AccountIdLookupOf, CollectionIdOf, ItemIdOf, NftsOf}; impl Pallet { // Approves the transfer of a specific item or all collection items owned by the `owner` to an // `operator`. - /// + // // # Parameters // - `owner` - The owner of the specified collection item(s). // - `collection` - The collection identifier. @@ -27,7 +27,7 @@ impl Pallet { // Cancel an approval to transfer a specific item or all items within a collection owned by // the `owner`. - /// + // // # Parameters // - `owner` - The owner of the specified collection item(s). // - `collection` - The collection identifier. diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index e2ed7c4be..8172679c0 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -36,6 +36,7 @@ type ItemIdOf = as Inspect<::AccountId> type ItemPriceOf = BalanceOf; type NftsErrorOf = pallet_nfts::Error>; type NftsWeightInfoOf = >>::WeightInfo; +type WeightOf = ::WeightInfo; pub(crate) type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; pub(crate) type BalanceOf = @@ -44,7 +45,6 @@ pub(crate) type BalanceOf = >>::Balance; pub(crate) type NftsOf = pallet_nfts::Pallet>; pub(crate) type NftsInstanceOf = ::NftsInstance; -pub(crate) type WeightOf = ::WeightInfo; // Type aliases for pallet-nfts storage items. pub(crate) type AccountBalanceOf = pallet_nfts::AccountBalance>; pub(crate) type AttributeOf = pallet_nfts::Attribute>; @@ -164,13 +164,13 @@ pub mod pallet { Ok(().into()) } - /// Approves operator to withdraw item(s) from the contract's account. + /// Approves operator to transfer item(s) from the `owner`'s account. /// /// # Parameters /// - `collection` - The collection identifier. - /// - `item` - Optional item. `None` means all items owned in the specified collection. - /// - `operator` - The account that is allowed to withdraw the item. - /// - `approved` - Whether the operator is given or removed the right to withdraw the + /// - `item` - Optional item. `None` means all collection items owned by the `owner`. + /// - `operator` - The account that is allowed to transfer the item. + /// - `approved` - Whether the operator is given or removed the right to transfer the /// item(s). #[pallet::call_index(4)] #[pallet::weight(WeightOf::::approve(*approved as u32, item.is_some() as u32))] @@ -192,12 +192,12 @@ pub mod pallet { result } - /// Mints an item to the specified address. + /// Mints an item to the specified recipient account. /// /// # Parameters /// - `collection` - The collection of the item to mint. /// - `item` - An identifier of the new item. - /// - `to` - Account into which the item will be minted. + /// - `to` - The recipient account. /// - `witness` - 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`. @@ -221,7 +221,7 @@ pub mod pallet { Ok(()) } - /// Destroy a single collection item. Clearing the corresponding approvals. + /// Destroy a single collection item. /// /// # Parameters /// - `collection` - The collection identifier. @@ -265,7 +265,7 @@ pub mod pallet { /// /// # Parameters /// - `collection` - The collection to destroy. - /// - `witness` - Information on the items minted in the collection. This must be + /// - `witness` - Information on the items minted in the `collection`. This must be /// correct. #[pallet::call_index(13)] #[pallet::weight(NftsWeightInfoOf::::destroy( @@ -469,17 +469,25 @@ pub mod pallet { #[repr(u8)] #[allow(clippy::unnecessary_cast)] pub enum Read { - /// The total supply of a collection. + /// Amount of items the owner has within a `collection`. #[codec(index = 0)] - TotalSupply(CollectionIdOf), - /// Amount of items the owner has within a collection. - #[codec(index = 1)] BalanceOf { /// The collection identifier. collection: CollectionIdOf, /// The account whose balance is being queried. owner: AccountIdOf, }, + /// Owner of an item within a specified collection, if any. + #[codec(index = 1)] + OwnerOf { + /// The collection identifier. + collection: CollectionIdOf, + /// The item. + item: ItemIdOf, + }, + /// 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`. #[codec(index = 2)] Allowance { /// The collection identifier. @@ -491,20 +499,15 @@ pub mod pallet { /// The account that is allowed to transfer the collection item(s). operator: AccountIdOf, }, - /// Owner of an item within a specified collection, if any. + /// The total supply of a collection. #[codec(index = 5)] - OwnerOf { - /// The collection identifier. - collection: CollectionIdOf, - /// The item. - item: ItemIdOf, - }, + TotalSupply(CollectionIdOf), /// Attribute value of a specified collection item for a given key, if any. #[codec(index = 6)] GetAttribute { /// The collection identifier. collection: CollectionIdOf, - /// The item. If not provided, the attributes for the collection are queried. + /// The item. If not provided, the attributes for the `collection` are queried. item: Option>, /// The namespace of the attribute. namespace: AttributeNamespaceOf, @@ -531,16 +534,17 @@ pub mod pallet { #[derive(Debug)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] pub enum ReadResult { - /// The total supply of a collection. - TotalSupply(u128), - /// Amount of items the `owner` has within a `collection`. + /// Amount of items the owner has within a collection. BalanceOf(u32), - /// Allowance for an `operator` approved by an `owner` to transfer a specified `item` or - /// all collection items owned by the `owner`. - Allowance(bool), - /// Owner of an `item` within a specified `collection`, if any. + /// Owner of an item within a specified collection, if any. OwnerOf(Option>), - /// Attribute value of a specified collection item for a given `key`, if any. + /// Returns whether the operator is approved by the owner to withdraw item. If item is not + /// provided, it returns whether the operator is approved to withdraw all owner's items for + /// the given collection. + Allowance(bool), + /// The total supply of a collection. + TotalSupply(u128), + /// Attribute value of a specified collection item for a given key, if any. GetAttribute(Option>), /// Details of a specified collection, if any. Collection(Option>), @@ -555,14 +559,14 @@ pub mod pallet { pub fn encode(&self) -> Vec { use ReadResult::*; match self { - TotalSupply(result) => result.encode(), BalanceOf(result) => result.encode(), - Allowance(result) => result.encode(), OwnerOf(result) => result.encode(), + Allowance(result) => result.encode(), + TotalSupply(result) => result.encode(), GetAttribute(result) => result.encode(), Collection(result) => result.encode(), - ItemMetadata(result) => result.encode(), NextCollectionId(result) => result.encode(), + ItemMetadata(result) => result.encode(), } } } @@ -581,14 +585,14 @@ pub mod pallet { fn weight(request: &Self::Read) -> Weight { use Read::*; match request { - TotalSupply(_) => WeightOf::::total_supply(), BalanceOf { .. } => WeightOf::::balance_of(), - Allowance { .. } => WeightOf::::allowance(), OwnerOf { .. } => WeightOf::::owner_of(), + Allowance { .. } => WeightOf::::allowance(), + TotalSupply(_) => WeightOf::::total_supply(), GetAttribute { .. } => WeightOf::::get_attribute(), Collection(_) => WeightOf::::collection(), - ItemMetadata { .. } => WeightOf::::item_metadata(), NextCollectionId => WeightOf::::next_collection_id(), + ItemMetadata { .. } => WeightOf::::item_metadata(), } } @@ -599,32 +603,32 @@ pub mod pallet { fn read(request: Self::Read) -> Self::Result { use Read::*; match request { - TotalSupply(collection) => ReadResult::TotalSupply( - NftsOf::::collection_items(collection).unwrap_or_default() as u128, - ), BalanceOf { collection, owner } => ReadResult::BalanceOf( AccountBalanceOf::::get(collection, owner) .map(|(balance, _)| balance) .unwrap_or_default(), ), + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), Allowance { collection, owner, operator, item } => ReadResult::Allowance( NftsOf::::check_approval_permission(&collection, &item, &owner, &operator) .is_ok(), ), - OwnerOf { collection, item } => - ReadResult::OwnerOf(NftsOf::::owner(collection, item)), + TotalSupply(collection) => ReadResult::TotalSupply( + NftsOf::::collection_items(collection).unwrap_or_default() as u128, + ), GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( AttributeOf::::get((collection, item, namespace, key)) .map(|attribute| attribute.0.into()), ), Collection(collection) => ReadResult::Collection(CollectionOf::::get(collection)), - ItemMetadata { collection, item } => ReadResult::ItemMetadata( - NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), - ), NextCollectionId => ReadResult::NextCollectionId( NextCollectionIdOf::::get().or(T::CollectionId::initial_value()), ), + ItemMetadata { collection, item } => ReadResult::ItemMetadata( + NftsOf::::item_metadata(collection, item).map(|metadata| metadata.into()), + ), } } } diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 38d42d8b4..1d459d71e 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -10,11 +10,11 @@ use scale_info::TypeInfo; use crate::{ mock::*, nonfungibles::{ - weights::WeightInfo as WeightInfoTrait, AccountBalanceOf, AccountIdOf, AttributeNamespace, - AttributeOf, BalanceOf as DepositBalanceOf, BlockNumberFor, - CancelAttributesApprovalWitness, CollectionConfig, CollectionIdOf, CollectionOf, - CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, MintWitness, - NextCollectionIdOf, NftsErrorOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, ReadResult, + AccountBalanceOf, AccountIdOf, AttributeNamespace, AttributeOf, + BalanceOf as DepositBalanceOf, BlockNumberFor, CancelAttributesApprovalWitness, + CollectionConfig, CollectionIdOf, CollectionOf, CollectionSettings, Config, DestroyWitness, + ItemIdOf, MintSettings, MintWitness, NextCollectionIdOf, NftsErrorOf, NftsInstanceOf, + NftsWeightInfoOf, Read::*, ReadResult, WeightInfo as WeightInfoTrait, }, Read, }; @@ -657,27 +657,6 @@ fn clear_collection_approvals_works() { }); } -#[test] -fn total_supply_works() { - new_test_ext().execute_with(|| { - let owner = ALICE; - let collection = nfts::create_collection(owner); - - assert_eq!(NonFungibles::read(TotalSupply(collection)), ReadResult::TotalSupply(0)); - (0..10).into_iter().for_each(|i| { - assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); - assert_eq!( - NonFungibles::read(TotalSupply(collection)), - ReadResult::TotalSupply((i + 1).into()) - ); - assert_eq!( - NonFungibles::read(TotalSupply(collection)).encode(), - (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() - ); - }); - }); -} - #[test] fn balance_of_works() { new_test_ext().execute_with(|| { @@ -702,6 +681,26 @@ fn balance_of_works() { }); } +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let collection = COLLECTION; + let item = ITEM; + let owner = ALICE; + + assert_eq!(NonFungibles::read(OwnerOf { collection, item }), ReadResult::OwnerOf(None)); + nfts::create_collection_and_mint(owner, owner, item); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }), + ReadResult::OwnerOf(Some(owner)) + ); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }).encode(), + Nfts::owner(collection, item).encode() + ); + }); +} + #[test] fn allowance_works() { new_test_ext().execute_with(|| { @@ -726,22 +725,23 @@ fn allowance_works() { } #[test] -fn owner_of_works() { +fn total_supply_works() { new_test_ext().execute_with(|| { - let collection = COLLECTION; - let item = ITEM; let owner = ALICE; + let collection = nfts::create_collection(owner); - assert_eq!(NonFungibles::read(OwnerOf { collection, item }), ReadResult::OwnerOf(None)); - nfts::create_collection_and_mint(owner, owner, item); - assert_eq!( - NonFungibles::read(OwnerOf { collection, item }), - ReadResult::OwnerOf(Some(owner)) - ); - assert_eq!( - NonFungibles::read(OwnerOf { collection, item }).encode(), - Nfts::owner(collection, item).encode() - ); + assert_eq!(NonFungibles::read(TotalSupply(collection)), ReadResult::TotalSupply(0)); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + assert_eq!( + NonFungibles::read(TotalSupply(collection)), + ReadResult::TotalSupply((i + 1).into()) + ); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() + ); + }); }); } @@ -1135,12 +1135,16 @@ mod ensure_codec_indexes { #[test] fn ensure_read_variant_indexes() { [ - (TotalSupply::(Default::default()), 0u8, "TotalSupply"), ( BalanceOf:: { collection: Default::default(), owner: Default::default() }, - 1, + 0u8, "BalanceOf", ), + ( + OwnerOf:: { collection: Default::default(), item: Default::default() }, + 1, + "OwnerOf", + ), ( Allowance:: { collection: Default::default(), @@ -1151,11 +1155,7 @@ mod ensure_codec_indexes { 2, "Allowance", ), - ( - OwnerOf:: { collection: Default::default(), item: Default::default() }, - 5, - "OwnerOf", - ), + (TotalSupply::(Default::default()), 5, "TotalSupply"), ( GetAttribute:: { collection: Default::default(), diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 9f74106c9..d7237ed56 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -207,9 +207,9 @@ impl Contains for Filter { matches!( r, RuntimeRead::NonFungibles( - TotalSupply(..) | - BalanceOf { .. } | Allowance { .. } | - OwnerOf { .. } | GetAttribute { .. } | + BalanceOf { .. } | + OwnerOf { .. } | Allowance { .. } | + TotalSupply(..) | GetAttribute { .. } | Collection { .. } | ItemMetadata { .. } | NextCollectionId, ) @@ -377,15 +377,15 @@ mod tests { use super::{nonfungibles::Read::*, RuntimeRead::*}; for read in vec![ - NonFungibles(TotalSupply(1)), NonFungibles(BalanceOf { collection: 1, owner: ACCOUNT }), + NonFungibles(OwnerOf { collection: 1, item: 1 }), NonFungibles(Allowance { collection: 1, item: None, owner: ACCOUNT, operator: ACCOUNT, }), - NonFungibles(OwnerOf { collection: 1, item: 1 }), + NonFungibles(TotalSupply(1)), NonFungibles(GetAttribute { collection: 1, item: Some(1), @@ -393,8 +393,8 @@ mod tests { key: bounded_vec![], }), NonFungibles(Collection(1)), - NonFungibles(ItemMetadata { collection: 1, item: 1 }), NonFungibles(NextCollectionId), + NonFungibles(ItemMetadata { collection: 1, item: 1 }), ] .iter() { From ac68a1f66ac50b25958e21a45483358891575853 Mon Sep 17 00:00:00 2001 From: Daan van der Plas <93204684+Daanvdplas@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:03:10 +0200 Subject: [PATCH 21/25] chore: add release conventions document and refactor CI to run node release separately (#433) --- .github/workflows/release.yml | 11 ++++++----- docs/release.md | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 docs/release.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 248ac51b4..7b672f111 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,11 @@ on: description: Build node default: true runtime: - description: 'Runtime to build (devnet, testnet, mainnet)' - default: '' + description: 'Runtime to build (none, devnet, testnet, mainnet)' + default: none type: choice options: - - '' + - none - devnet - testnet - mainnet @@ -33,7 +33,7 @@ jobs: startsWith(github.event.release.tag_name, 'devnet') || startsWith(github.event.release.tag_name, 'mainnet') || github.event_name == 'workflow_dispatch' && - github.event.inputs.runtime != '' + github.event.inputs.runtime != 'none' permissions: contents: write steps: @@ -151,7 +151,8 @@ jobs: build-node: runs-on: ${{ matrix.platform.os }} - if: github.event_name != 'workflow_dispatch' && 'true' || inputs.node + if: | + startsWith(github.event.release.tag_name, 'node') || (github.event_name == 'workflow_dispatch' && inputs.node) permissions: contents: write strategy: diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..a78799f54 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,10 @@ +# Release Tag and Title Structure + +This table outlines the naming conventions for Node and Runtimes releases, including both the tag format and suggested title format. + +| **Release Type** | **Tag Format** | **Example Tag** | **Title Format** | **Example Title** | +|-----------------------------|---------------------|---------------------|----------------------------|----------------------------| +| Node | `node-x.y.z` | `node-0.1.0` | `Node v` | Node v0.1.0 | +| Devnet - Runtime | `devnet-x.y.z` | `devnet-0.1.0` | `Devnet Runtime v` | **Devnet Runtime v0.1.0** | +| Testnet - Runtime | `testnet-x.y.z` | `testnet-0.2.0` | `Testnet Runtime v` | **Testnet Runtime v0.2.0** | +| Mainnet - Runtime | `mainnet-x.y.z` | `mainnet-1.0.0` | `Mainnet Runtime v` | **Mainnet Runtime v1.0.0** | From a8b2e7e7b0570ab9d934171571b60fd914de56b3 Mon Sep 17 00:00:00 2001 From: Tin Chung <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:20:15 +0700 Subject: [PATCH 22/25] refactor: reuse `WeightInfo` in nonfungibles pallet test (#450) --- pallets/api/src/fungibles/tests.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 364ae31fe..43881ac0a 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -670,7 +670,6 @@ mod read_weights { use frame_support::weights::Weight; use super::*; - use crate::fungibles::{weights::WeightInfo, Config}; struct ReadWeightInfo { total_supply: Weight, @@ -712,13 +711,13 @@ mod read_weights { token_exists, } = ReadWeightInfo::new(); - assert_eq!(total_supply, ::WeightInfo::total_supply()); - assert_eq!(balance_of, ::WeightInfo::balance_of()); - assert_eq!(allowance, ::WeightInfo::allowance()); - assert_eq!(token_name, ::WeightInfo::token_name()); - assert_eq!(token_symbol, ::WeightInfo::token_symbol()); - assert_eq!(token_decimals, ::WeightInfo::token_decimals()); - assert_eq!(token_exists, ::WeightInfo::token_exists()); + assert_eq!(total_supply, WeightInfo::total_supply()); + assert_eq!(balance_of, WeightInfo::balance_of()); + assert_eq!(allowance, WeightInfo::allowance()); + assert_eq!(token_name, WeightInfo::token_name()); + assert_eq!(token_symbol, WeightInfo::token_symbol()); + assert_eq!(token_decimals, WeightInfo::token_decimals()); + assert_eq!(token_exists, WeightInfo::token_exists()); } // These types read from the `AssetMetadata` storage. From bbf59150f19cae8f39446f161ebc7d96c3d177fc Mon Sep 17 00:00:00 2001 From: Tin Chung <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:47:35 +0700 Subject: [PATCH 23/25] chore: update collection details field to public (#449) --- pallets/nfts/src/types.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index 135702b00..701102bb3 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -97,18 +97,18 @@ pub(super) type AccountDepositOf = #[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. From 9604472baa0400487a3d459b78312e5cfcc0a334 Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:26:55 +0700 Subject: [PATCH 24/25] chore: rebase and remove local collection details type --- pallets/api/src/nonfungibles/tests.rs | 31 +++++++-------------------- pallets/nfts/src/benchmarking.rs | 2 +- pallets/nfts/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index 1d459d71e..cda1bbd19 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -1,20 +1,19 @@ -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::Encode; use frame_support::{ assert_noop, assert_ok, dispatch::WithPostDispatchInfo, sp_runtime::{traits::Zero, BoundedVec, DispatchError::BadOrigin}, }; use pallet_nfts::{CollectionSetting, WeightInfo as NftsWeightInfoTrait}; -use scale_info::TypeInfo; use crate::{ mock::*, nonfungibles::{ - AccountBalanceOf, AccountIdOf, AttributeNamespace, AttributeOf, - BalanceOf as DepositBalanceOf, BlockNumberFor, CancelAttributesApprovalWitness, - CollectionConfig, CollectionIdOf, CollectionOf, CollectionSettings, Config, DestroyWitness, - ItemIdOf, MintSettings, MintWitness, NextCollectionIdOf, NftsErrorOf, NftsInstanceOf, - NftsWeightInfoOf, Read::*, ReadResult, WeightInfo as WeightInfoTrait, + AccountBalanceOf, AttributeNamespace, AttributeOf, BlockNumberFor, + CancelAttributesApprovalWitness, CollectionConfig, CollectionDetails, CollectionIdOf, + CollectionOf, CollectionSettings, Config, DestroyWitness, ItemIdOf, MintSettings, + MintWitness, NextCollectionIdOf, NftsErrorOf, NftsInstanceOf, NftsWeightInfoOf, Read::*, + ReadResult, WeightInfo as WeightInfoTrait, }, Read, }; @@ -28,17 +27,6 @@ type Event = crate::nonfungibles::Event; type NftsError = NftsErrorOf; type WeightInfo = ::WeightInfo; -/// Information about a collection. -#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -struct CollectionDetails { - pub(super) owner: AccountIdOf, - pub(super) owner_deposit: DepositBalanceOf, - pub(super) items: u32, - pub(super) item_metadatas: u32, - pub(super) item_configs: u32, - pub(super) attributes: u32, -} - mod transfer { use super::*; @@ -1288,7 +1276,6 @@ mod read_weights { mod encoding_read_result { use super::*; - use crate::nonfungibles::CollectionDetailsOf; #[test] fn total_supply() { @@ -1332,16 +1319,14 @@ mod encoding_read_result { #[test] fn collection() { - let bytes = CollectionDetails { + let mut details = Some(CollectionDetails { owner: ALICE, owner_deposit: 0, items: 0, item_metadatas: 0, item_configs: 0, attributes: 0, - } - .encode(); - let mut details = Some(CollectionDetailsOf::::decode(&mut &bytes[..]).unwrap()); + }); assert_eq!(ReadResult::Collection::(details.clone()).encode(), details.encode()); details = None; assert_eq!(ReadResult::Collection::(details.clone()).encode(), details.encode()); diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index 4f6c0817e..a04580a87 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -27,7 +27,7 @@ use frame_support::{ BoundedVec, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; -use sp_runtime::traits::{Bounded, One, Zero}; +use sp_runtime::traits::{Bounded, One}; use super::*; use crate::Pallet as Nfts; diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index ac5c8fe1a..2526d8366 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::{ use frame_system::Config as SystemConfig; pub use pallet::*; use sp_runtime::{ - traits::{IdentifyAccount, Saturating, StaticLookup, Verify}, + traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; pub use types::*; From 924e5209e5bea3bd4953c9b0cc24d961cd2e9dcd Mon Sep 17 00:00:00 2001 From: chungquantin <56880684+chungquantin@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:51:13 +0700 Subject: [PATCH 25/25] chore: remove impls.rs and update burn() --- pallets/api/src/nonfungibles/impls.rs | 50 --------------------- pallets/api/src/nonfungibles/mod.rs | 65 +++++++++++++++++++-------- pallets/api/src/nonfungibles/tests.rs | 13 +++--- 3 files changed, 54 insertions(+), 74 deletions(-) delete mode 100644 pallets/api/src/nonfungibles/impls.rs diff --git a/pallets/api/src/nonfungibles/impls.rs b/pallets/api/src/nonfungibles/impls.rs deleted file mode 100644 index 7cc35e4f6..000000000 --- a/pallets/api/src/nonfungibles/impls.rs +++ /dev/null @@ -1,50 +0,0 @@ -use frame_support::dispatch::DispatchResult; -use frame_system::pallet_prelude::*; - -use super::{pallet::*, AccountIdLookupOf, CollectionIdOf, ItemIdOf, NftsOf}; - -impl Pallet { - // Approves the transfer of a specific item or all collection items owned by the `owner` to an - // `operator`. - // - // # Parameters - // - `owner` - The owner of the specified collection item(s). - // - `collection` - The collection identifier. - // - `maybe_item` - The optional item of the collection to be approved for delegated transfer. - // If `None`, the approval applies to all `owner`'s collection items. - // - `operator` - The account to delegate permission to. - pub(crate) fn do_approve( - owner: OriginFor, - collection: CollectionIdOf, - maybe_item: Option>, - operator: AccountIdLookupOf, - ) -> DispatchResult { - match maybe_item { - Some(item) => NftsOf::::approve_transfer(owner, collection, item, operator, None), - None => NftsOf::::approve_collection_transfer(owner, collection, operator, None), - } - } - - // Cancel an approval to transfer a specific item or all items within a collection owned by - // the `owner`. - // - // # Parameters - // - `owner` - The owner of the specified collection item(s). - // - `collection` - The collection identifier. - // - `maybe_item` - The optional item of the collection that the operator has an approval to - // transfer. If not provided, an approval to transfer all `owner`'s collection items will be - // cancelled. - // - `operator` - The account that had permission to transfer a specified collection item or all - // collection items owned by the `owner`. - pub(crate) fn do_cancel_approval( - owner: OriginFor, - collection: CollectionIdOf, - maybe_item: Option>, - operator: AccountIdLookupOf, - ) -> DispatchResult { - match maybe_item { - Some(item) => NftsOf::::cancel_approval(owner, collection, item, operator), - None => NftsOf::::cancel_collection_approval(owner, collection, operator), - } - } -} diff --git a/pallets/api/src/nonfungibles/mod.rs b/pallets/api/src/nonfungibles/mod.rs index 8172679c0..9cb8777af 100644 --- a/pallets/api/src/nonfungibles/mod.rs +++ b/pallets/api/src/nonfungibles/mod.rs @@ -21,35 +21,31 @@ pub(crate) use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -mod impls; #[cfg(test)] mod tests; pub mod weights; +type AccountBalanceOf = pallet_nfts::AccountBalance>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type AccountIdOf = ::AccountId; type AttributeNamespaceOf = AttributeNamespace>; +type AttributeOf = pallet_nfts::Attribute>; +type BalanceOf = <>>::Currency as Currency< + ::AccountId, +>>::Balance; type CollectionConfigOf = CollectionConfig, BlockNumberFor, CollectionIdOf>; type CollectionDetailsOf = CollectionDetails, BalanceOf>; type CollectionIdOf = as Inspect<::AccountId>>::CollectionId; +type CollectionOf = pallet_nfts::Collection>; type ItemIdOf = as Inspect<::AccountId>>::ItemId; type ItemPriceOf = BalanceOf; +type NextCollectionIdOf = pallet_nfts::NextCollectionId>; type NftsErrorOf = pallet_nfts::Error>; +type NftsInstanceOf = ::NftsInstance; +type NftsOf = pallet_nfts::Pallet>; type NftsWeightInfoOf = >>::WeightInfo; type WeightOf = ::WeightInfo; -pub(crate) type AccountIdLookupOf = - <::Lookup as StaticLookup>::Source; -pub(crate) type BalanceOf = - <>>::Currency as Currency< - ::AccountId, - >>::Balance; -pub(crate) type NftsOf = pallet_nfts::Pallet>; -pub(crate) type NftsInstanceOf = ::NftsInstance; -// Type aliases for pallet-nfts storage items. -pub(crate) type AccountBalanceOf = pallet_nfts::AccountBalance>; -pub(crate) type AttributeOf = pallet_nfts::Attribute>; -pub(crate) type NextCollectionIdOf = pallet_nfts::NextCollectionId>; -pub(crate) type CollectionOf = pallet_nfts::Collection>; #[frame_support::pallet] pub mod pallet { @@ -227,16 +223,18 @@ pub mod pallet { /// - `collection` - The collection identifier. /// - `item` - The item to burn. #[pallet::call_index(8)] - #[pallet::weight(NftsWeightInfoOf::::burn())] + #[pallet::weight(NftsWeightInfoOf::::burn() + T::DbWeight::get().reads(1))] pub fn burn( origin: OriginFor, collection: CollectionIdOf, item: ItemIdOf, - ) -> DispatchResult { - let owner = ensure_signed(origin.clone())?; + ) -> DispatchResultWithPostInfo { + let owner = NftsOf::::owner(collection, item) + .ok_or(NftsErrorOf::::UnknownItem) + .map_err(|e| e.with_weight(T::DbWeight::get().reads(1)))?; NftsOf::::burn(origin, collection, item)?; Self::deposit_event(Event::Transfer { collection, item, from: Some(owner), to: None }); - Ok(()) + Ok(().into()) } /// Issue a new collection of non-fungible items. @@ -463,6 +461,37 @@ pub mod pallet { } } + impl Pallet { + // Approves the transfer of a specific item or all collection items owned by the `owner` to + // an `operator`. + fn do_approve( + owner: OriginFor, + collection: CollectionIdOf, + maybe_item: Option>, + operator: AccountIdLookupOf, + ) -> DispatchResult { + match maybe_item { + Some(item) => + NftsOf::::approve_transfer(owner, collection, item, operator, None), + None => NftsOf::::approve_collection_transfer(owner, collection, operator, None), + } + } + + // Cancel an approval to transfer a specific item or all items within a collection owned by + // the `owner`. + fn do_cancel_approval( + owner: OriginFor, + collection: CollectionIdOf, + maybe_item: Option>, + operator: AccountIdLookupOf, + ) -> DispatchResult { + match maybe_item { + Some(item) => NftsOf::::cancel_approval(owner, collection, item, operator), + None => NftsOf::::cancel_collection_approval(owner, collection, operator), + } + } + } + /// State reads for the non-fungibles API with required input. #[derive(Encode, Decode, Debug, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(PartialEq, Clone))] diff --git a/pallets/api/src/nonfungibles/tests.rs b/pallets/api/src/nonfungibles/tests.rs index cda1bbd19..4e10b8013 100644 --- a/pallets/api/src/nonfungibles/tests.rs +++ b/pallets/api/src/nonfungibles/tests.rs @@ -272,13 +272,14 @@ fn burn_works() { let owner = ALICE; let item = ITEM; - // Origin checks. - for origin in vec![root(), none()] { - assert_noop!(NonFungibles::burn(origin, collection, item), BadOrigin); - } - // Check error works for `Nfts::burn()`. - assert_noop!(NonFungibles::burn(signed(owner), collection, item), NftsError::UnknownItem); + // Throw `NftsError::UnknownItem` if no owner found for the item. + assert_noop!( + NonFungibles::burn(signed(owner), collection, item), + NftsError::UnknownItem.with_weight(DbWeight::get().reads(1)) + ); nfts::create_collection_and_mint(owner, owner, item); + // Check error works for `Nfts::burn()`. + assert_noop!(NonFungibles::burn(signed(BOB), collection, item), NftsError::NoPermission); // Successfully burn a collection item. let balance_before_burn = nfts::balance_of(collection, &owner); assert_ok!(NonFungibles::burn(signed(owner), collection, item));