diff --git a/Cargo.lock b/Cargo.lock index e31ca132e..5036eb15e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -907,7 +907,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.36.4", + "object 0.36.0", "rustc-demangle", ] @@ -7343,9 +7343,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -7514,6 +7514,7 @@ dependencies = [ "log", "pallet-assets", "pallet-balances", + "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 55a007895..31ccc076d 100644 --- a/pallets/api/Cargo.toml +++ b/pallets/api/Cargo.toml @@ -22,6 +22,7 @@ frame-benchmarking.workspace = true frame-support.workspace = true frame-system.workspace = true pallet-assets.workspace = true +pallet-nfts.workspace = true sp-runtime.workspace = true sp-std.workspace = true @@ -37,6 +38,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pop-chain-extension/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -47,6 +49,7 @@ std = [ "frame-system/std", "pallet-assets/std", "pallet-balances/std", + "pallet-nfts/std", "pop-chain-extension/std", "scale-info/std", "sp-core/std", @@ -57,5 +60,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 d94d19788..e3d706e2d 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 42c8bf0e2..a721ee362 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 } ); @@ -91,7 +96,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; @@ -110,6 +115,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)) + } +} +