diff --git a/Cargo.lock b/Cargo.lock index e9a8bb0e..bb753385 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -2111,7 +2111,7 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -7380,6 +7380,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "pallet-ismp", + "pallet-nfts 0.1.0", "parity-scale-codec", "pop-chain-extension", "scale-info", @@ -8272,39 +8273,39 @@ dependencies = [ [[package]] name = "pallet-nfts" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" +version = "0.1.0" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", - "sp-std", ] [[package]] name = "pallet-nfts" -version = "31.0.0" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1cd476809de3840e19091a083d5a79178af1f108ad489706e1f9e04c8836a4" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", "log", - "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-keystore", "sp-runtime", + "sp-std", ] [[package]] @@ -11051,7 +11052,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", @@ -11196,7 +11197,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", - "pallet-nfts 31.0.0", + "pallet-nfts 0.1.0", "pallet-nfts-runtime-api", "pallet-preimage", "pallet-proxy", @@ -16861,11 +16862,12 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.218.0" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b896fa8ceb71091ace9bcb81e853f54043183a1c9667cf93422c40252ffa0a" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" dependencies = [ "leb128", + "wasmparser 0.219.1", ] [[package]] @@ -17013,6 +17015,16 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.219.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +dependencies = [ + "bitflags 2.5.0", + "indexmap 2.2.6", +] + [[package]] name = "wasmparser-nostd" version = "0.100.2" @@ -17041,7 +17053,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -17096,7 +17108,7 @@ dependencies = [ "object 0.30.4", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -17131,7 +17143,7 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -17214,14 +17226,14 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.102.0", ] [[package]] name = "wast" -version = "218.0.0" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a53cd1f0fa505df97557e36a58bddb8296e2fcdcd089529545ebfdb18a1b9d7" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" dependencies = [ "bumpalo", "leb128", @@ -17232,9 +17244,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.218.0" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f87f8e14e776762e07927c27c2054d2cf678aab9aae2d431a79b3e31e4dd391" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ "wast", ] diff --git a/pallets/api/Cargo.toml b/pallets/api/Cargo.toml index b626cafc..80d6a364 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-core.workspace = true sp-runtime.workspace = true sp-std.workspace = true @@ -43,6 +44,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", ] @@ -55,6 +57,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-ismp/std", + "pallet-nfts/std", "pop-chain-extension/std", "scale-info/std", "sp-core/std", @@ -65,5 +68,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 65b69598..a05d0cf9 100644 --- a/pallets/api/src/lib.rs +++ b/pallets/api/src/lib.rs @@ -8,6 +8,7 @@ pub mod fungibles; pub mod messaging; #[cfg(test)] mod mock; +pub mod nonfungibles; /// Trait for performing reads of runtime state. pub trait Read { diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index 42c8bf0e..8a4ad27d 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,66 @@ 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 + } +} + +impl pallet_nfts::Config for Test { + type ApprovalsLimit = ConstU32<10>; + type AttributeDepositBase = ConstU128<1>; + type CollectionDeposit = ConstU128<2>; + type CollectionId = u32; + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type DepositPerByte = ConstU128<1>; + type Features = Features; + type ForceOrigin = frame_system::EnsureRoot; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type ItemAttributesApprovalsLimit = ConstU32<2>; + type ItemDeposit = ConstU128<1>; + type ItemId = u32; + type KeyLimit = ConstU32<50>; + type Locker = (); + type MaxAttributesPerCall = ConstU32<2>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxTips = ConstU32<10>; + type MetadataDepositBase = ConstU128<1>; + 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 RuntimeEvent = RuntimeEvent; +} + 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/mod.rs b/pallets/api/src/nonfungibles/mod.rs new file mode 100644 index 00000000..8d3f8db2 --- /dev/null +++ b/pallets/api/src/nonfungibles/mod.rs @@ -0,0 +1,428 @@ +//! 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. + +pub use pallet::*; +use pallet_nfts::WeightInfo; +use sp_runtime::traits::StaticLookup; +pub use types::*; + +#[cfg(test)] +mod tests; +pub mod types; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::Incrementable}; + use frame_system::pallet_prelude::*; + use pallet_nfts::{ + CancelAttributesApprovalWitness, CollectionConfig, CollectionSettings, DestroyWitness, + MintSettings, MintWitness, + }; + use sp_runtime::BoundedVec; + use sp_std::vec::Vec; + use types::{ + AccountIdOf, AttributeNamespaceOf, BalanceOf, CollectionConfigFor, CollectionDetailsFor, + CollectionIdOf, ItemIdOf, ItemPriceOf, NextCollectionIdOf, NftsOf, NftsWeightInfoOf, + }; + + use super::*; + + /// State reads for the non-fungibles API with required input. + #[derive(Encode, Decode, Debug, MaxEncodedLen)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + #[repr(u8)] + #[allow(clippy::unnecessary_cast)] + pub enum Read { + /// Total item supply of a collection. + #[codec(index = 0)] + TotalSupply(CollectionIdOf), + /// Account balance for a specified collection. + #[codec(index = 1)] + BalanceOf { collection: CollectionIdOf, owner: AccountIdOf }, + /// Allowance for an operator approved by an owner, for a specified collection or item. + #[codec(index = 2)] + Allowance { + collection: CollectionIdOf, + owner: AccountIdOf, + operator: AccountIdOf, + item: Option>, + }, + /// Owner of a specified collection item. + #[codec(index = 5)] + OwnerOf { collection: CollectionIdOf, item: ItemIdOf }, + /// Attribute value of a collection item. (Error: bounded collection is not partial) + #[codec(index = 6)] + GetAttribute { + collection: CollectionIdOf, + item: ItemIdOf, + namespace: AttributeNamespaceOf, + key: BoundedVec, + }, + /// Details of a collection. + #[codec(index = 9)] + Collection(CollectionIdOf), + /// Next collection ID. + #[codec(index = 10)] + NextCollectionId, + #[codec(index = 11)] + ItemMetadata { collection: CollectionIdOf, item: ItemIdOf }, + } + + /// Results of state reads for the non-fungibles API. + #[derive(Debug)] + #[cfg_attr(feature = "std", derive(PartialEq, Clone))] + pub enum ReadResult { + /// 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 collection item. + GetAttribute(Option>), + /// Details of a collection. + Collection(Option>), + /// Next collection ID. + NextCollectionId(Option>), + /// Collection item metadata. + 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>; + } + + #[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 collection ID. + collection: CollectionIdOf, + /// The item which is (dis)approved. `None` for all owner's items. + item: Option>, + /// The owner providing the allowance. + owner: AccountIdOf, + /// The beneficiary of the allowance. + operator: AccountIdOf, + /// 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 { + #[pallet::call_index(3)] + #[pallet::weight(NftsWeightInfoOf::::transfer())] + pub fn transfer( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + to: AccountIdOf, + ) -> DispatchResult { + let from = ensure_signed(origin.clone())?; + NftsOf::::transfer(origin, collection, item, T::Lookup::unlookup(to.clone()))?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(from), + to: Some(to), + price: None, + }); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(NftsWeightInfoOf::::approve_transfer() + NftsWeightInfoOf::::cancel_approval())] + pub fn approve( + origin: OriginFor, + collection: CollectionIdOf, + item: Option>, + operator: AccountIdOf, + approved: bool, + ) -> DispatchResult { + let owner = ensure_signed(origin.clone())?; + if approved { + NftsOf::::approve_transfer( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + None, + )?; + } else { + NftsOf::::cancel_approval( + origin, + collection, + item, + T::Lookup::unlookup(operator.clone()), + )?; + } + Self::deposit_event(Event::Approval { collection, item, operator, owner, approved }); + Ok(()) + } + + #[pallet::call_index(7)] + #[pallet::weight(NftsWeightInfoOf::::create())] + pub fn create( + origin: OriginFor, + id: CollectionIdOf, + admin: AccountIdOf, + config: CollectionConfigFor, + ) -> DispatchResult { + let creator = ensure_signed(origin.clone())?; + NftsOf::::create(origin, T::Lookup::unlookup(admin.clone()), config)?; + Self::deposit_event(Event::Created { id, admin, creator }); + Ok(()) + } + + #[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) + } + + #[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) + } + + #[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) + } + + #[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) + } + + #[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()), + ) + } + + #[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, + ) + } + + #[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) + } + + #[pallet::call_index(19)] + #[pallet::weight(NftsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + to: AccountIdOf, + collection: CollectionIdOf, + item: ItemIdOf, + witness: MintWitness, ItemPriceOf>, + ) -> DispatchResult { + let account = 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(account), + price: mint_price, + }); + Ok(()) + } + + #[pallet::call_index(20)] + #[pallet::weight(NftsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + collection: CollectionIdOf, + item: ItemIdOf, + ) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + NftsOf::::burn(origin, collection, item)?; + Self::deposit_event(Event::Transfer { + collection, + item, + from: Some(account), + to: None, + price: None, + }); + Ok(()) + } + } + + impl crate::Read for Pallet { + /// The type of read requested. + type Read = Read; + /// The type or result returned. + type Result = ReadResult; + + /// Determines the weight of the requested read, used to charge the appropriate weight + /// before the read is performed. + /// + /// # Parameters + /// - `request` - The read request. + fn weight(_request: &Self::Read) -> Weight { + Default::default() + } + + /// Performs the requested read and returns the result. + /// + /// # Parameters + /// - `request` - The read request. + fn read(value: Self::Read) -> Self::Result { + use Read::*; + match value { + TotalSupply(collection) => ReadResult::TotalSupply( + NftsOf::::collection_items(collection).unwrap_or_default() as u128, + ), + BalanceOf { collection, owner } => + ReadResult::BalanceOf(pallet_nfts::AccountBalance::::get(collection, owner)), + Allowance { collection, owner, operator, item } => ReadResult::Allowance( + NftsOf::::check_allowance(&collection, &item, &owner, &operator).is_ok(), + ), + OwnerOf { collection, item } => + ReadResult::OwnerOf(NftsOf::::owner(collection, item)), + GetAttribute { collection, item, namespace, key } => ReadResult::GetAttribute( + pallet_nfts::Attribute::::get((collection, Some(item), namespace, key)) + .map(|attribute| attribute.0.into()), + ), + Collection(collection) => + ReadResult::Collection(pallet_nfts::Collection::::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 00000000..803e8cf8 --- /dev/null +++ b/pallets/api/src/nonfungibles/tests.rs @@ -0,0 +1,576 @@ +use codec::Encode; +use frame_support::{assert_noop, assert_ok, traits::Incrementable}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{ + AccountBalance, CollectionConfig, CollectionDetails, CollectionSettings, DestroyWitness, + MintSettings, MintWitness, +}; +use sp_runtime::{BoundedVec, DispatchError::BadOrigin}; + +use super::types::{CollectionIdOf, ItemIdOf}; +use crate::{ + mock::*, + nonfungibles::{Event, Read::*, ReadResult}, + Read, +}; + +const ITEM: u32 = 1; + +type NftsError = pallet_nfts::Error; + +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 collection() { + let mut collection_details = Some(CollectionDetails { + owner: ALICE, + owner_deposit: 0, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }); + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + collection_details = None; + assert_eq!( + ReadResult::Collection::(collection_details.clone()).encode(), + collection_details.encode() + ); + } + + #[test] + fn 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() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let dest = BOB; + + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + for origin in vec![root(), none()] { + assert_noop!(NonFungibles::transfer(origin, collection, item, dest), BadOrigin); + } + // Successfully burn an existing new collection item. + let balance_before_transfer = AccountBalance::::get(collection, &dest); + assert_ok!(NonFungibles::transfer(signed(owner), collection, ITEM, dest)); + let balance_after_transfer = AccountBalance::::get(collection, &dest); + assert_eq!(AccountBalance::::get(collection, &owner), 0); + assert_eq!(balance_after_transfer - balance_before_transfer, 1); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: Some(dest), price: None } + .into(), + ); + }); +} + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + + // Successfully mint a new collection item. + let balance_before_mint = AccountBalance::::get(collection, owner); + assert_ok!(NonFungibles::mint( + signed(owner), + owner, + collection, + ITEM, + MintWitness { mint_price: None, owned_item: None } + )); + let balance_after_mint = AccountBalance::::get(collection, owner); + assert_eq!(balance_after_mint, 1); + assert_eq!(balance_after_mint - balance_before_mint, 1); + System::assert_last_event( + Event::Transfer { collection, item: ITEM, from: None, to: Some(owner), price: None } + .into(), + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + + // Successfully burn an existing new collection item. + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + assert_ok!(NonFungibles::burn(signed(owner), collection, ITEM)); + System::assert_last_event( + Event::Transfer { collection, item, from: Some(owner), to: None, price: None }.into(), + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint(owner, ITEM); + // Successfully approve `oeprator` to transfer the collection item. + assert_ok!(NonFungibles::approve(signed(owner), collection, Some(item), operator, true)); + System::assert_last_event( + Event::Approval { collection, item: Some(item), owner, operator, approved: true } + .into(), + ); + // Successfully transfer the item by the delegated account `operator`. + assert_ok!(Nfts::transfer(signed(operator), collection, item, operator)); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + // Successfully cancel the transfer approval of `operator` by `owner`. + assert_ok!(NonFungibles::approve(signed(owner), collection, Some(item), operator, false)); + // Failed to transfer the item by `operator` without permission. + assert_noop!( + Nfts::transfer(signed(operator), collection, item, operator), + NftsError::NoPermission + ); + }); +} + +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + assert_ok!(NonFungibles::set_max_supply(signed(owner), collection, 10)); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + }); + assert_noop!( + Nfts::mint(signed(owner), collection, 42, owner, None), + NftsError::MaxSupplyReached + ); + }); +} + +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!( + NonFungibles::read(OwnerOf { collection, item }).encode(), + Nfts::owner(collection, item).encode() + ); + }); +} + +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + let mut result: Option::ValueLimit>> = None; + // No attribute set. + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute.clone() + }) + .encode(), + result.encode() + ); + // Successfully get an existing attribute. + result = Some(value.clone()); + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + value, + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() + ); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + let value = BoundedVec::truncate_from("some metadata".as_bytes().to_vec()); + assert_ok!(NonFungibles::set_metadata(signed(ALICE), collection, item, value.clone())); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + Some(value).encode() + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_ok!(NonFungibles::set_metadata( + signed(ALICE), + collection, + item, + BoundedVec::truncate_from("some metadata".as_bytes().to_vec()) + )); + assert_ok!(NonFungibles::clear_metadata(signed(ALICE), collection, item)); + assert_eq!( + NonFungibles::read(ItemMetadata { collection, item }).encode(), + ReadResult::::ItemMetadata(None).encode() + ); + }); +} + +#[test] +fn clear_attribute_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let result: Option::ValueLimit>> = None; + assert_ok!(Nfts::set_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + BoundedVec::truncate_from("some value".as_bytes().to_vec()) + )); + // Successfully clear an attribute. + assert_ok!(Nfts::clear_attribute( + signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + attribute.clone(), + )); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: attribute + }) + .encode(), + result.encode() + ); + }); +} + +#[test] +fn approve_item_attribute_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, BOB)); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(BOB), + attribute.clone(), + value.clone() + )); + let result: Option::ValueLimit>> = Some(value); + assert_eq!( + NonFungibles::read(GetAttribute { + collection, + item, + namespace: pallet_nfts::AttributeNamespace::Account(BOB), + key: attribute + }) + .encode(), + result.encode() + ); + }); +} + +#[test] +fn cancel_item_attribute_approval_works() { + new_test_ext().execute_with(|| { + let (collection, item) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + let attribute = BoundedVec::truncate_from("some attribute".as_bytes().to_vec()); + let value = BoundedVec::truncate_from("some value".as_bytes().to_vec()); + // Successfully approve delegate to set attributes. + assert_ok!(Nfts::approve_item_attributes(signed(ALICE), collection, item, BOB)); + assert_ok!(Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(BOB), + attribute.clone(), + value.clone() + )); + assert_ok!(Nfts::cancel_item_attributes_approval( + signed(ALICE), + collection, + item, + BOB, + pallet_nfts::CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert_noop!( + Nfts::set_attribute( + signed(BOB), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(BOB), + attribute.clone(), + value.clone() + ), + NftsError::NoPermission + ); + }); +} + +#[test] +fn next_collection_id_works() { + new_test_ext().execute_with(|| { + let _ = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!(NonFungibles::read(NextCollectionId).encode(), Some(1).encode()); + assert_eq!( + NonFungibles::read(NextCollectionId).encode(), + pallet_nfts::NextCollectionId::::get() + .or(CollectionIdOf::::initial_value()) + .encode(), + ); + }); +} + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + ((i + 1) as u128).encode() + ); + assert_eq!( + NonFungibles::read(TotalSupply(collection)).encode(), + (Nfts::collection_items(collection).unwrap_or_default() as u128).encode() + ); + }); + }); +} + +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let next_collection_id = pallet_nfts::NextCollectionId::::get().unwrap_or_default(); + assert_ok!(NonFungibles::create( + signed(owner), + next_collection_id, + owner, + CollectionConfig { + max_supply: None, + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled() + }, + )); + assert_eq!(Nfts::collection_owner(next_collection_id), Some(owner)); + }); +} + +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let collection = nfts::create_collection(ALICE); + assert_ok!(NonFungibles::destroy( + signed(ALICE), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(Nfts::collection_owner(collection), None); + }); +} + +#[test] +fn collection_works() { + new_test_ext().execute_with(|| { + let (collection, _) = nfts::create_collection_mint(ALICE, ITEM); + assert_eq!( + NonFungibles::read(Collection(collection)).encode(), + pallet_nfts::Collection::::get(&collection).encode(), + ); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let collection = nfts::create_collection(owner); + (0..10).into_iter().for_each(|i| { + assert_ok!(Nfts::mint(signed(owner), collection, i, owner, None)); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner }).encode(), + (i + 1).encode() + ); + assert_eq!( + NonFungibles::read(BalanceOf { collection, owner }).encode(), + AccountBalance::::get(collection, owner).encode() + ); + }); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let owner = ALICE; + let operator = BOB; + let (collection, item) = nfts::create_collection_mint_and_approve(owner, ITEM, operator); + assert_eq!( + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) + .encode(), + true.encode() + ); + assert_eq!( + NonFungibles::read(Allowance { collection, item: Some(item), owner, operator }) + .encode(), + Nfts::check_allowance(&collection, &Some(item), &owner, &operator) + .is_ok() + .encode() + ); + }); +} + +fn signed(account_id: AccountId) -> RuntimeOrigin { + RuntimeOrigin::signed(account_id) +} + +fn root() -> RuntimeOrigin { + RuntimeOrigin::root() +} + +fn none() -> RuntimeOrigin { + RuntimeOrigin::none() +} + +mod nfts { + use super::*; + + pub(super) fn create_collection_mint_and_approve( + owner: AccountId, + item: ItemIdOf, + operator: AccountId, + ) -> (u32, u32) { + let (collection, item) = create_collection_mint(owner, item); + assert_ok!(Nfts::approve_transfer(signed(owner), collection, Some(item), operator, None)); + (collection, item) + } + + pub(super) fn create_collection_mint(owner: AccountId, item: ItemIdOf) -> (u32, u32) { + let collection = create_collection(owner); + assert_ok!(Nfts::mint(signed(owner), collection, item, owner, None)); + (collection, item) + } + + pub(super) fn create_collection(owner: AccountId) -> u32 { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + signed(owner), + owner, + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(super) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } + + pub(super) fn collection_config_with_all_settings_enabled( + ) -> CollectionConfig, CollectionIdOf> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } + } +} diff --git a/pallets/api/src/nonfungibles/types.rs b/pallets/api/src/nonfungibles/types.rs new file mode 100644 index 00000000..05ff7d98 --- /dev/null +++ b/pallets/api/src/nonfungibles/types.rs @@ -0,0 +1,30 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{nonfungibles_v2::Inspect, Currency}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use pallet_nfts::{ + AttributeNamespace, CollectionConfig, CollectionDetails, CollectionSetting, CollectionSettings, + DestroyWitness, ItemDeposit, ItemDetails, ItemSetting, MintSettings, MintType, MintWitness, +}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +// Type aliases for pallet-nfts. +pub(super) type NftsOf = pallet_nfts::Pallet; +pub(super) type NftsWeightInfoOf = ::WeightInfo; +// Type aliases for pallet-nfts storage items. +pub(super) type AccountIdOf = ::AccountId; +pub(super) type BalanceOf = <>::Currency as Currency< + ::AccountId, +>>::Balance; +pub(super) type NextCollectionIdOf = pallet_nfts::NextCollectionId; +pub(super) type CollectionIdOf = + as Inspect<::AccountId>>::CollectionId; +pub(super) type ItemIdOf = + as Inspect<::AccountId>>::ItemId; +pub(super) type ItemPriceOf = BalanceOf; +// TODO: Multi-instances. +pub(super) type CollectionDetailsFor = + CollectionDetails, BalanceOf>; +pub(super) type AttributeNamespaceOf = AttributeNamespace>; +pub(super) type CollectionConfigFor = + CollectionConfig, BlockNumberFor, CollectionIdOf>; diff --git a/pallets/nfts/Cargo.toml b/pallets/nfts/Cargo.toml index 19d35803..7ecbf385 100644 --- a/pallets/nfts/Cargo.toml +++ b/pallets/nfts/Cargo.toml @@ -1,33 +1,30 @@ [package] authors.workspace = true -description = "FRAME NFTs pallet (polkadot v1.15.0)" +description = "NFTs pallet (polkadot v1.15.0) for Pop Network Runtimes" edition.workspace = true -homepage = "https://substrate.io" -license = "Apache-2.0" +license.workspace = true name = "pallet-nfts" readme = "README.md" -repository.workspace = true -version = "31.0.0" - +version = "0.1.0" [package.metadata.docs.rs] targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] -codec = { workspace = true } -enumflags2 = { workspace = true } +codec.workspace = true +enumflags2.workspace = true frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true frame-system.workspace = true -log = { workspace = true } +log.workspace = true scale-info = { features = [ "derive" ], workspace = true } sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true [dev-dependencies] -pallet-balances = { default-features = true, workspace = true } -sp-keystore = { default-features = true, workspace = true } +pallet-balances.workspace = true +sp-keystore.workspace = true [features] default = [ "std" ] @@ -44,6 +41,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-io/std", diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index 8fa87557..ab66da26 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -578,9 +578,9 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let deadline = BlockNumberFor::::max_value(); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + assert_last_event::(Event::TransferApproved { collection, item: Some(item), owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { @@ -590,10 +590,10 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, Some(item), delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection, item: Some(item), owner: caller, delegate }.into()); } clear_all_transfer_approvals { @@ -603,7 +603,7 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + Nfts::::approve_transfer(origin, collection, Some(item), delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index f51de192..89de1f05 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -34,6 +34,19 @@ impl, I: 'static> Pallet { Collection::::get(collection).map(|i| i.owner) } + /// Get the total number of items in the collection, if the collection exists. + pub fn collection_items(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.items) + } + + /// Get the metadata of the collection item. + pub fn item_metadata( + collection: T::CollectionId, + item: T::ItemId, + ) -> Option> { + ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data) + } + /// Validates the signature of the given data with the provided signer's account ID. /// /// # Errors @@ -46,7 +59,7 @@ impl, I: 'static> Pallet { signer: &T::AccountId, ) -> DispatchResult { if signature.verify(&**data, &signer) { - return Ok(()) + return Ok(()); } // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index ad5d93c2..e1e79ef4 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -65,7 +65,6 @@ impl, I: 'static> Pallet { if let Some(check_origin) = maybe_check_origin { ensure!(check_origin == details.owner, Error::::NoPermission); } - let now = frame_system::Pallet::::block_number(); let deadline = maybe_deadline.map(|d| d.saturating_add(now)); @@ -74,15 +73,13 @@ impl, I: 'static> Pallet { .try_insert(delegate.clone(), deadline) .map_err(|_| Error::::ReachedApprovalLimit)?; Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::TransferApproved { collection, - item, + item: Some(item), owner: details.owner, delegate, deadline, }); - Ok(()) } @@ -129,7 +126,7 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::ApprovalCancelled { collection, - item, + item: Some(item), owner: details.owner, delegate, }); @@ -173,4 +170,84 @@ impl, I: 'static> Pallet { Ok(()) } + + pub(crate) fn do_approve_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let owner = Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == owner, Error::::NoPermission); + } + + Allowances::::mutate((&collection, &owner, &delegate), |allowance| { + *allowance = true; + }); + + Self::deposit_event(Event::TransferApproved { + collection, + item: None, + owner, + delegate, + deadline: None, + }); + Ok(()) + } + + pub(crate) fn do_cancel_collection( + maybe_check_origin: Option, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + let owner = Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + ensure!(check_origin == owner, Error::::NoPermission); + } + Allowances::::remove((&collection, &owner, &delegate)); + + Self::deposit_event(Event::ApprovalCancelled { collection, owner, item: None, delegate }); + + Ok(()) + } + + pub fn check_allowance( + collection: &T::CollectionId, + item: &Option, + owner: &T::AccountId, + delegate: &T::AccountId, + ) -> Result<(), DispatchError> { + // Check if a `delegate` has a permission to spend the collection. + if Allowances::::get((&collection, &owner, &delegate)) { + if let Some(item) = item { + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + }; + return Ok(()); + } + // Check if a `delegate` has a permission to spend the collection item. + if let Some(item) = item { + let details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let deadline = details.approvals.get(&delegate).ok_or(Error::::NoPermission)?; + if let Some(d) = deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *d, Error::::ApprovalExpired); + } + return Ok(()); + }; + Err(Error::::NoPermission.into()) + } } diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 348ec6b9..b7efd03a 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -137,6 +137,11 @@ impl, I: 'static> Pallet { } } + // TODO: Do we need another storage item to keep track of number of holders of a + // collection + let _ = + AccountBalance::::clear_prefix(collection, collection_details.items, None); + let _ = Allowances::::clear_prefix((collection,), collection_details.items, None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index e9843b2e..cc29f8da 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -69,6 +69,9 @@ impl, I: 'static> Pallet { } collection_details.items.saturating_inc(); + AccountBalance::::mutate(collection, &mint_to, |balance| { + balance.saturating_inc(); + }); let collection_config = Self::get_collection_config(&collection)?; let deposit_amount = match collection_config @@ -263,6 +266,9 @@ impl, I: 'static> Pallet { ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); + AccountBalance::::mutate(collection, &owner, |balance| { + balance.saturating_dec(); + }); if remove_config { ItemConfigOf::::remove(&collection, &item); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index b7223a7c..3b25b014 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -87,6 +87,14 @@ impl, I: 'static> Pallet { // Perform the transfer with custom details using the provided closure. with_details(&collection_details, &mut details)?; + // Update account balances. + AccountBalance::::mutate(collection, &details.owner, |balance| { + balance.saturating_dec(); + }); + AccountBalance::::mutate(collection, &dest, |balance| { + balance.saturating_inc(); + }); + // Update account ownership information. Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); @@ -137,7 +145,7 @@ impl, I: 'static> Pallet { // Check if the `origin` is the current owner of the collection. ensure!(origin == details.owner, Error::::NoPermission); if details.owner == new_owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. @@ -212,7 +220,7 @@ impl, I: 'static> Pallet { Collection::::try_mutate(collection, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; if details.owner == owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 89bfb963..bc8b67b6 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -402,6 +402,34 @@ pub mod pallet { pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + /// Number of collection items that accounts own. + #[pallet::storage] + pub type AccountBalance, I: 'static = ()> = StorageDoubleMap< + _, + Twox64Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, + u32, + ValueQuery, + >; + + /// Permission for the delegate to transfer all owner's items within a collection. + #[pallet::storage] + pub type Allowances, I: 'static = ()> = StorageNMap< + _, + ( + // Collection ID. + NMapKey, + // Collection Owner Id. + NMapKey, + // Delegate Id. + NMapKey, + ), + bool, + ValueQuery, + >; + /// Config of an item. #[pallet::storage] pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< @@ -460,7 +488,7 @@ pub mod pallet { /// a `delegate`. TransferApproved { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, deadline: Option>, @@ -469,7 +497,7 @@ pub mod pallet { /// `collection` was cancelled by its `owner`. ApprovalCancelled { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, }, @@ -1030,12 +1058,7 @@ pub mod pallet { Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { - let deadline = - details.approvals.get(&origin).ok_or(Error::::NoPermission)?; - if let Some(d) = deadline { - let block_number = frame_system::Pallet::::block_number(); - ensure!(block_number <= *d, Error::::ApprovalExpired); - } + Self::check_allowance(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) @@ -1090,10 +1113,10 @@ pub mod pallet { if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { // NOTE: No alterations made to collection_details in this iteration so far, // so this is OK to do. - continue + continue; } } else { - continue + continue; } details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); @@ -1292,7 +1315,7 @@ pub mod pallet { pub fn approve_transfer( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, maybe_deadline: Option>, ) -> DispatchResult { @@ -1300,13 +1323,16 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; - Self::do_approve_transfer( - maybe_check_origin, - collection, - item, - delegate, - maybe_deadline, - ) + match maybe_item { + Some(item) => Self::do_approve_transfer( + maybe_check_origin, + collection, + item, + delegate, + maybe_deadline, + ), + None => Self::do_approve_collection(maybe_check_origin, collection, delegate), + } } /// Cancel one of the transfer approvals for a specific item. @@ -1328,14 +1354,18 @@ pub mod pallet { pub fn cancel_approval( origin: OriginFor, collection: T::CollectionId, - item: T::ItemId, + maybe_item: Option, delegate: AccountIdLookupOf, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; - Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) + match maybe_item { + Some(item) => + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate), + None => Self::do_cancel_collection(maybe_check_origin, collection, delegate), + } } /// Cancel all the approvals of a specific item. diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 44f2f32a..4d0f08c9 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -164,6 +164,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42)]); assert_ok!(Nfts::force_create( @@ -173,6 +174,7 @@ fn basic_minting_should_work() { )); assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); + assert_eq!(AccountBalance::::get(1, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } @@ -204,6 +206,7 @@ fn lifecycle_should_work() { account(10), default_item_config() )); + assert_eq!(AccountBalance::::get(0, account(10)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 6); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), @@ -212,8 +215,10 @@ fn lifecycle_should_work() { account(20), default_item_config() )); + assert_eq!(AccountBalance::::get(0, account(20)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 7); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); @@ -221,6 +226,8 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); + assert_eq!(AccountBalance::::get(0, account(1)), 0); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::reserved_balance(&account(1)), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); @@ -238,6 +245,7 @@ fn lifecycle_should_work() { Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), Error::::CollectionNotEmpty ); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(account(1)), @@ -248,7 +256,9 @@ fn lifecycle_should_work() { bvec![0], )); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); + assert_eq!(AccountBalance::::get(0, account(10)), 0); assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); let w = Nfts::get_destroy_witness(&0).unwrap(); @@ -256,6 +266,7 @@ fn lifecycle_should_work() { assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(AccountBalance::::get(0, account(1)), 0); assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); @@ -305,6 +316,14 @@ fn destroy_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_eq!(AccountBalance::::get(0, account(2)), 1); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(3), + None + )); assert_noop!( Nfts::destroy( RuntimeOrigin::signed(account(1)), @@ -323,6 +342,8 @@ fn destroy_should_work() { 0, Nfts::get_destroy_witness(&0).unwrap() )); + assert_eq!(AccountBalance::::iter_prefix(0).count(), 0); + assert_eq!(Allowances::::iter_prefix((0,)).count(), 0); assert!(!ItemConfigOf::::contains_key(0, 42)); assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); @@ -337,6 +358,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -402,6 +424,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -440,6 +463,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); + assert_eq!(AccountBalance::::get(1, account(2)), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -478,6 +502,8 @@ fn transfer_should_work() { )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -487,12 +513,14 @@ fn transfer_should_work() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(3)), 0, - 42, + Some(42), account(2), None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 0); + assert_eq!(AccountBalance::::get(0, account(4)), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1746,15 +1774,17 @@ fn burn_works() { account(5), default_item_config() )); + assert_eq!(AccountBalance::::get(0, account(5)), 2); assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), Error::::NoPermission ); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); + assert_eq!(AccountBalance::::get(0, account(5)), 1); assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); + assert_eq!(AccountBalance::::get(0, account(5)), 0); assert_eq!(Balances::reserved_balance(account(1)), 0); }); } @@ -1777,7 +1807,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); @@ -1791,7 +1821,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(2), None )); @@ -1819,7 +1849,7 @@ fn approval_lifecycle_works() { Nfts::approve_transfer( RuntimeOrigin::signed(account(1)), collection_id, - 1, + Some(1), account(2), None ), @@ -1829,7 +1859,7 @@ fn approval_lifecycle_works() { } #[test] -fn cancel_approval_works() { +fn check_allowance_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), @@ -1844,33 +1874,95 @@ fn cancel_approval_works() { default_item_config() )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(2), + None + )); + + // collection transfer approved. + assert_noop!( + Nfts::check_allowance(&1, &None, &account(1), &account(2)), + Error::::NoPermission + ); + assert_noop!( + Nfts::check_allowance(&1, &Some(43), &account(1), &account(2)), + Error::::UnknownItem + ); + assert_ok!(Nfts::check_allowance(&0, &None, &account(1), &account(2))); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(1), &account(2))); + + // collection item transfer approved. assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, + Some(42), + account(3), + None + )); + + assert_noop!( + Nfts::check_allowance(&0, &Some(43), &account(2), &account(3)), + Error::::UnknownItem + ); + assert_noop!( + Nfts::check_allowance(&0, &Some(42), &account(2), &account(4)), + Error::::NoPermission + ); + assert_ok!(Nfts::check_allowance(&0, &Some(42), &account(2), &account(3))); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, Some(42), account(3)), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(3)), Error::::NotDelegate ); @@ -1887,22 +1979,71 @@ fn cancel_approval_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, Some(42), account(3)), Error::::NoPermission ); System::set_block_number(current_block + 3); // 5 can cancel the approval since the deadline has passed. - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(5)), + 0, + Some(42), + account(3) + )); assert_eq!(approvals(0, 69), vec![]); }); } +#[test] +fn cancel_approval_collection_works_with_admin() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 1, None, account(3)), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, None, account(3))); + assert!(events().contains(&Event::::ApprovalCancelled { + collection: 0, + item: None, + owner: account(1), + delegate: account(3) + })); + assert_eq!(Allowances::::get((0, account(2), account(3))), false); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), + Error::::NoPermission + ); + }); +} + #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { @@ -1924,21 +2065,21 @@ fn approving_multiple_accounts_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(5), Some(2) )); @@ -1979,19 +2120,82 @@ fn approvals_limit_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(i), None )); } // the limit is 10 assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), + Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(14), + None + ), Error::::ReachedApprovalLimit ); }); } +#[test] +fn approval_collection_works_with_admin() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(1), + default_item_config() + )); + + // Error::ItemsNonTransferable. + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(account(1)), + 1, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(1)), 1, None, account(2), None), + Error::::ItemsNonTransferable + ); + + // Error::UnknownCollection. + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 2, None, account(3), None), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + 0, + None, + account(3), + None + )); + assert!(events().contains(&Event::::TransferApproved { + collection: 0, + item: None, + owner: account(1), + delegate: account(3), + deadline: None + })); + assert_eq!(Allowances::::get((0, account(1), account(3))), true); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); + }); +} + #[test] fn approval_deadline_works() { new_test_ext().execute_with(|| { @@ -2015,7 +2219,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), Some(2) )); @@ -2034,7 +2238,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(4)), 0, - 42, + Some(42), account(6), Some(4) )); @@ -2063,26 +2267,31 @@ fn cancel_approval_works_with_admin() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(account(2)), + 0, + Some(42), + account(3) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2107,26 +2316,26 @@ fn cancel_approval_works_with_force() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 1, Some(42), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(43), account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3))); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, Some(42), account(1)), Error::::NotDelegate ); }); @@ -2151,14 +2360,14 @@ fn clear_all_transfer_approvals_works() { assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(3), None )); assert_ok!(Nfts::approve_transfer( RuntimeOrigin::signed(account(2)), 0, - 42, + Some(42), account(4), None )); @@ -2188,6 +2397,36 @@ fn clear_all_transfer_approvals_works() { }); } +#[test] +fn total_supply_should_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let total_items = 10; + + // no collection. + assert_eq!(Nfts::collection_items(collection_id), None); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + // mint items and validate the total supply. + (0..total_items).into_iter().for_each(|i| { + assert_ok!(Nfts::force_mint( + RuntimeOrigin::root(), + collection_id, + i, + user_id.clone(), + ItemConfig::default() + )); + }); + assert_eq!(Nfts::collection_items(collection_id), Some(total_items)); + }); +} + #[test] fn max_supply_should_work() { new_test_ext().execute_with(|| { @@ -2524,6 +2763,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -2891,6 +3131,8 @@ fn claim_swap_should_work() { default_item_config(), )); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1.clone()), collection_id, @@ -2981,6 +3223,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); + assert_eq!(AccountBalance::::get(collection_id, user_1.clone()), 2); + assert_eq!(AccountBalance::::get(collection_id, user_2.clone()), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); @@ -3169,7 +3413,7 @@ fn pallet_level_feature_flags_should_work() { Nfts::approve_transfer( RuntimeOrigin::signed(user_id.clone()), collection_id, - item_id, + Some(item_id), account(2), None ), diff --git a/pallets/nfts/src/types.rs b/pallets/nfts/src/types.rs index f08f1d09..941da6ca 100644 --- a/pallets/nfts/src/types.rs +++ b/pallets/nfts/src/types.rs @@ -94,18 +94,18 @@ pub(super) type PreSignedAttributesOf = PreSignedAttributes< #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Collection's owner. - pub(super) owner: AccountId, + pub owner: AccountId, /// The total balance deposited by the owner for all the storage data associated with this /// collection. Used by `destroy`. - pub(super) owner_deposit: DepositBalance, + pub owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. - pub(super) items: u32, + pub items: u32, /// The total number of outstanding item metadata of this collection. - pub(super) item_metadatas: u32, + pub item_metadatas: u32, /// The total number of outstanding item configs of this collection. - pub(super) item_configs: u32, + pub item_configs: u32, /// The total number of attributes for this collection. - pub(super) attributes: u32, + pub attributes: u32, } /// Witness data for the destroy transactions. diff --git a/pallets/nfts/src/weights.rs b/pallets/nfts/src/weights.rs index c5fb60a2..c374d6db 100644 --- a/pallets/nfts/src/weights.rs +++ b/pallets/nfts/src/weights.rs @@ -1,30 +1,14 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. //! Autogenerated weights for `pallet_nfts` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 +//! DATE: 2024-10-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `R0GUE`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// ./target/release/pop-node // benchmark // pallet // --chain=dev @@ -34,12 +18,11 @@ // --no-storage-info // --no-median-slopes // --no-min-squares -// --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/nfts/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --output=./pallets/nfts/src/weights.rs +// --template=./scripts/pallet-weights-template.hbs +// --extrinsic= #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -107,10 +90,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -126,10 +109,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -141,6 +124,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -152,15 +137,19 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1005_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(1005_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -174,18 +163,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -195,18 +186,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -218,22 +211,24 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -245,6 +240,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -253,12 +250,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -269,12 +266,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -286,10 +283,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -299,10 +296,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -312,10 +309,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -329,10 +326,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -342,10 +339,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -355,10 +352,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -368,10 +365,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -381,10 +378,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -400,10 +397,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,10 +410,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -430,30 +427,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -461,12 +458,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -485,10 +482,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -502,10 +499,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,10 +516,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -536,10 +533,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -549,10 +546,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -560,10 +557,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -571,10 +568,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -582,10 +579,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -595,10 +592,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -608,10 +605,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -625,10 +622,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -644,28 +641,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -673,10 +672,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -686,10 +685,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -705,18 +704,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -726,6 +727,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -739,22 +742,22 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -766,12 +769,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -794,10 +797,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(27_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -813,10 +816,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -828,6 +831,8 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) @@ -839,15 +844,19 @@ impl WeightInfo for () { /// The range of component `m` is `[0, 1000]`. /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + fn destroy(m: u32, c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32216 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1004_u64)) + // Minimum execution time: 982_000_000 picoseconds. + Weight::from_parts(937_587_516, 2523990) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(34_348, 0).saturating_mul(m.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(23_800, 0).saturating_mul(c.into())) + // Standard Error: 12_288 + .saturating_add(Weight::from_parts(5_095_505, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1005_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(RocksDbWeight::get().writes(1005_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -861,18 +870,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(42_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -882,18 +893,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -905,22 +918,24 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `576` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(59_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -932,6 +947,8 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -940,12 +957,12 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `605` + // Estimated: `6068` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(83_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -956,12 +973,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 20_022 + .saturating_add(Weight::from_parts(16_005_327, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -973,10 +990,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -986,10 +1003,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -999,10 +1016,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1016,10 +1033,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3593) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1029,10 +1046,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1042,10 +1059,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1055,10 +1072,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1068,10 +1085,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1087,10 +1104,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(38_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1100,10 +1117,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1117,30 +1134,30 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1148,12 +1165,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 4466) + // Standard Error: 6_379 + .saturating_add(Weight::from_parts(5_018_740, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1172,10 +1189,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1189,10 +1206,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(29_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1206,10 +1223,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1223,10 +1240,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(29_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1236,10 +1253,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1247,10 +1264,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1258,10 +1275,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1269,10 +1286,10 @@ impl WeightInfo for () { /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1282,10 +1299,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1295,10 +1312,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1312,10 +1329,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1331,28 +1348,30 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `717` + // Estimated: `6068` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(45_000_000, 6068) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(390_532, 0) + // Standard Error: 84_277 + .saturating_add(Weight::from_parts(3_087_492, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -1360,10 +1379,10 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1373,10 +1392,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1392,18 +1411,20 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `907` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Minimum execution time: 75_000_000 picoseconds. + Weight::from_parts(77_000_000, 7662) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -1413,6 +1434,8 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1426,22 +1449,22 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(107_476_765, 6078) + // Standard Error: 61_259 + .saturating_add(Weight::from_parts(27_610_007, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -1453,16 +1476,16 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(57_358_180, 4466) + // Standard Error: 54_968 + .saturating_add(Weight::from_parts(27_429_606, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } -} +} \ No newline at end of file diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 872a129d..b66fc4e8 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -6,6 +6,8 @@ name = "pop-api" version = "0.0.0" [dependencies] +bitflags = { version = "1.3.2" } +enumflags2 = "0.7.9" ink = { git = "https://github.com/r0gue-io/ink", branch = "sub0", default-features = false } polkavm-derive = "0.11.0" pop-primitives = { path = "../primitives", default-features = false } @@ -24,4 +26,5 @@ path = "src/lib.rs" default = [ "std" ] fungibles = [ ] messaging = [ ] +nonfungibles = [ ] std = [ "ink/std", "pop-primitives/std" ] diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 0e9caf1b..31b99926 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -18,11 +18,13 @@ pallet-assets = { version = "37.0.0", default-features = false } pallet-balances = { version = "37.0.0", default-features = false } pallet-incentives = { path = "../../pallets/incentives", default-features = false } pallet-ismp = { git = "https://github.com/r0gue-io/ismp", branch = "polkadot-v1.14.0", default-features = false } +pallet-nfts = { path = "../../pallets/nfts", default-features = false } pallet-revive = { path = "../../pallets/revive", default-features = false } pallet-timestamp = { version = "35.0.0", default-features = false } pop-api = { path = "../../pop-api", default-features = false, features = [ "fungibles", "messaging", + "nonfungibles", ] } pop-primitives = { path = "../../primitives", default-features = false } pop-runtime-devnet = { path = "../../runtime/devnet", default-features = false } @@ -43,6 +45,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-incentives/std", + "pallet-nfts/std", "pallet-revive/std", "pallet-timestamp/std", "pop-api/std", diff --git a/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml new file mode 100644 index 00000000..ce8c07d7 --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = [ "R0GUE " ] +edition = "2021" +name = "nonfungibles" +version = "0.1.0" + +[dependencies] +ink = { git = "https://github.com/r0gue-io/ink", branch = "sub0", default-features = false } +polkavm-derive = "0.11.0" +pop-api = { path = "../../..", default-features = false, features = [ "nonfungibles" ] } + +[lib] +path = "lib.rs" + +[features] +default = [ "std" ] +e2e-tests = [ ] +ink-as-dependency = [ ] +std = [ + "ink/std", + "pop-api/std", +] diff --git a/pop-api/integration-tests/contracts/nonfungibles/lib.rs b/pop-api/integration-tests/contracts/nonfungibles/lib.rs new file mode 100644 index 00000000..201f8fd9 --- /dev/null +++ b/pop-api/integration-tests/contracts/nonfungibles/lib.rs @@ -0,0 +1,249 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +/// 1. PSP-34 +/// 2. PSP-34 Metadata +/// 3. Management +/// 4. PSP-34 Mintable & Burnable +use ink::prelude::vec::Vec; +use pop_api::{ + nonfungibles::{ + self as api, + events::{Approval, AttributeSet, Transfer}, + AttributeNamespace, CancelAttributesApprovalWitness, CollectionConfig, CollectionDetails, + CollectionId, DestroyWitness, ItemId, MintWitness, + }, + StatusCode, +}; + +pub type Result = core::result::Result; + +#[ink::contract] +mod nonfungibles { + use super::*; + + #[ink(storage)] + #[derive(Default)] + pub struct NonFungibles; + + impl NonFungibles { + #[ink(constructor, payable)] + pub fn new() -> Self { + ink::env::debug_println!("PopApiNonFungiblesExample::new"); + Default::default() + } + + /// 1. PSP-34 Interface: + /// - total_supply + /// - balance_of + /// - allowance + /// - transfer + /// - approve + /// - owner_of + + #[ink(message)] + pub fn total_supply(&self, collection: CollectionId) -> Result { + api::total_supply(collection) + } + + #[ink(message)] + pub fn balance_of(&self, collection: CollectionId, owner: AccountId) -> Result { + api::balance_of(collection, owner) + } + + #[ink(message)] + pub fn allowance( + &self, + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, + ) -> Result { + api::allowance(collection, owner, operator, item) + } + + #[ink(message)] + pub fn transfer( + &mut self, + collection: CollectionId, + item: ItemId, + to: AccountId, + ) -> Result<()> { + api::transfer(collection, item, to)?; + self.env().emit_event(Transfer { + from: Some(self.env().account_id()), + to: Some(to), + item, + }); + Ok(()) + } + + #[ink(message)] + pub fn approve( + &mut self, + collection: CollectionId, + item: Option, + operator: AccountId, + approved: bool, + ) -> Result<()> { + api::approve(collection, item, operator, approved)?; + self.env().emit_event(Approval { + owner: self.env().account_id(), + operator, + item, + approved, + }); + Ok(()) + } + + #[ink(message)] + pub fn owner_of( + &self, + collection: CollectionId, + item: ItemId, + ) -> Result> { + api::owner_of(collection, item) + } + + /// 2. PSP-34 Metadata Interface: + /// - get_attribute + + #[ink(message)] + pub fn get_attribute( + &self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result>> { + api::get_attribute(collection, item, namespace, key) + } + + /// 3. Asset Management: + /// - create + /// - destroy + /// - collection + /// - set_attribute + /// - clear_attribute + /// - set_metadata + /// - clear_metadata + /// - approve_item_attributes + /// - cancel_item_attributes_approval + /// - set_max_supply + /// - item_metadata + + #[ink(message)] + pub fn create( + &mut self, + id: CollectionId, + admin: AccountId, + config: CollectionConfig, + ) -> Result<()> { + api::create(id, admin, config) + } + + #[ink(message)] + pub fn destroy(&mut self, collection: CollectionId, witness: DestroyWitness) -> Result<()> { + api::destroy(collection, witness) + } + + #[ink(message)] + pub fn collection(&self, collection: CollectionId) -> Result> { + api::collection(collection) + } + + #[ink(message)] + pub fn set_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, + ) -> Result<()> { + api::set_attribute(collection, item, namespace, key.clone(), value.clone())?; + self.env().emit_event(AttributeSet { item, key, data: value }); + Ok(()) + } + + #[ink(message)] + pub fn clear_attribute( + &mut self, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + ) -> Result<()> { + api::clear_attribute(collection, item, namespace, key) + } + + #[ink(message)] + pub fn set_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + data: Vec, + ) -> Result<()> { + api::set_metadata(collection, item, data) + } + + #[ink(message)] + pub fn clear_metadata(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::clear_metadata(collection, item) + } + + #[ink(message)] + pub fn approve_item_attributes( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + ) -> Result<()> { + api::approve_item_attributes(collection, item, delegate) + } + + #[ink(message)] + pub fn cancel_item_attributes_approval( + &mut self, + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, + ) -> Result<()> { + api::cancel_item_attributes_approval(collection, item, delegate, witness) + } + + #[ink(message)] + pub fn set_max_supply(&mut self, collection: CollectionId, max_supply: u32) -> Result<()> { + api::set_max_supply(collection, max_supply) + } + + #[ink(message)] + pub fn item_metadata( + &mut self, + collection: CollectionId, + item: ItemId, + ) -> Result>> { + api::item_metadata(collection, item) + } + + /// 4. PSP-22 Mintable & Burnable Interface: + /// - mint + /// - burn + + #[ink(message)] + pub fn mint( + &mut self, + to: AccountId, + collection: CollectionId, + item: ItemId, + witness: MintWitness, + ) -> Result<()> { + api::mint(to, collection, item, witness) + } + + #[ink(message)] + pub fn burn(&mut self, collection: CollectionId, item: ItemId) -> Result<()> { + api::burn(collection, item) + } + } +} diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index 1ffdd849..efb4e132 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -9,7 +9,7 @@ use frame_support::{ }; use pallet_revive::{AddressMapper, Code, CollectEvents, ExecReturnValue}; use pop_runtime_devnet::{ - config::ismp::Router, Assets, Messaging, Revive, Runtime, RuntimeOrigin, System, UNIT, + config::ismp::Router, Assets, Messaging, Nfts, Revive, Runtime, RuntimeOrigin, System, UNIT, }; use scale::{Decode, Encode}; use sp_runtime::{ @@ -22,6 +22,7 @@ mod environment; mod fungibles; mod incentives; mod messaging; +mod nonfungibles; type Balance = u128; @@ -29,7 +30,7 @@ const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); const BOB: AccountId32 = AccountId32::new([2_u8; 32]); const DEBUG_OUTPUT: pallet_revive::DebugInfo = pallet_revive::DebugInfo::UnsafeDebug; const FERDIE: AccountId32 = AccountId32::new([3_u8; 32]); -const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +const GAS_LIMIT: Weight = Weight::from_parts(500_000_000_000, 3 * 1024 * 1024); const INIT_AMOUNT: Balance = 100_000_000 * UNIT; const INIT_VALUE: Balance = 100 * UNIT; diff --git a/pop-api/integration-tests/src/nonfungibles/mod.rs b/pop-api/integration-tests/src/nonfungibles/mod.rs new file mode 100644 index 00000000..bcf75566 --- /dev/null +++ b/pop-api/integration-tests/src/nonfungibles/mod.rs @@ -0,0 +1,450 @@ +use frame_support::BoundedVec; +use pop_api::{ + nonfungibles::{ + events::{Approval, AttributeSet, Transfer}, + types::*, + }, + primitives::BlockNumber, +}; +use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*}; +use utils::*; + +use super::*; + +mod utils; + +const COLLECTION_ID: CollectionId = 0; +const ITEM_ID: ItemId = 1; +const CONTRACT: &str = "contracts/nonfungibles/target/ink/nonfungibles.riscv"; + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + // No tokens in circulation. + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); + assert_eq!( + total_supply(&addr, COLLECTION_ID), + Ok(Nfts::collection_items(COLLECTION_ID).unwrap_or_default() as u128) + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + // No tokens in circulation. + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(0)); + + // Tokens in circulation. + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); + assert_eq!( + balance_of(&addr, COLLECTION_ID, ALICE), + Ok(nfts::balance_of(COLLECTION_ID, ALICE)), + ); + assert_eq!(total_supply(&addr, COLLECTION_ID), Ok(1)); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + // No tokens in circulation. + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, None), + Ok(!Nfts::check_allowance(&COLLECTION_ID, &None, &account_id, &ALICE).is_err()), + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, None), + Ok(false) + ); + + let (_, item) = nfts::create_collection_mint_and_approve( + &account_id, + &account_id, + ITEM_ID, + &account_id, + &ALICE, + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, Some(item)), + Ok(Nfts::check_allowance(&COLLECTION_ID, &Some(item), &account_id.clone(), &ALICE) + .is_ok()), + ); + assert_eq!( + allowance(&addr.clone(), COLLECTION_ID, account_id.clone(), ALICE, Some(item)), + Ok(true) + ); + }); +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + let before_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_ok!(transfer(&addr, collection, item, ALICE)); + let after_transfer_balance = nfts::balance_of(COLLECTION_ID, ALICE); + assert_eq!(after_transfer_balance - before_transfer_balance, 1); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(approve(&addr, collection, Some(item), ALICE, true)); + assert!( + Nfts::check_allowance(&collection, &Some(item), &account_id.clone(), &ALICE).is_ok(), + ); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(ALICE), collection, item, BOB.into())); + }); +} + +#[test] +fn owner_of_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &ALICE, ITEM_ID); + assert_eq!(owner_of(&addr, collection, item), Ok(ALICE)); + }); +} + +#[test] +fn get_attribute_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account_id.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + get_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + ), + Ok(Some("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn set_attribute_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + + assert_ok!(set_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec(), + "some value".as_bytes().to_vec(), + )); + + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn clear_attribute_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account_id.clone()), + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(clear_attribute( + &addr.clone(), + collection, + item, + AttributeNamespace::CollectionOwner, + "some attribute".as_bytes().to_vec() + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::CollectionOwner, + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + None + ); + }); +} + +#[test] +fn approve_item_attributes_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(approve_item_attributes(&addr.clone(), collection, item, ALICE)); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_eq!( + pallet_nfts::Attribute::::get(( + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + AttributeKey::truncate_from("some attribute".as_bytes().to_vec()), + )) + .map(|attribute| attribute.0), + Some(AttributeValue::truncate_from("some value".as_bytes().to_vec())) + ); + }); +} + +#[test] +fn cancel_item_attributes_approval_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(account_id.clone()), + collection, + item, + ALICE.into() + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + )); + assert_ok!(cancel_item_attributes_approval( + &addr.clone(), + collection, + item, + ALICE, + CancelAttributesApprovalWitness { account_attributes: 1 } + )); + assert!(Nfts::set_attribute( + RuntimeOrigin::signed(ALICE), + collection, + Some(item), + pallet_nfts::AttributeNamespace::Account(ALICE), + BoundedVec::truncate_from("some attribute".as_bytes().to_vec()), + BoundedVec::truncate_from("some value".as_bytes().to_vec()), + ) + .is_err()); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(set_metadata(&addr.clone(), collection, item, vec![])); + assert_eq!(Nfts::item_metadata(collection, item), Some(MetadataData::default())); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(Nfts::set_metadata( + RuntimeOrigin::signed(account_id.clone()), + collection, + item, + MetadataData::default() + )); + assert_ok!(clear_metadata(&addr.clone(), collection, item)); + assert_eq!(Nfts::item_metadata(collection, item), None); + }); +} + +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let collection = nfts::next_collection_id(); + assert_ok!(create( + &addr.clone(), + COLLECTION_ID, + account_id.clone(), + CollectionConfig { + max_supply: Some(100), + mint_settings: MintSettings::default(), + settings: CollectionSettings::all_enabled(), + } + )); + assert_eq!( + pallet_nfts::Collection::::get(collection), + Some(pallet_nfts::CollectionDetails { + owner: account_id.clone(), + owner_deposit: 100000000000, + items: 0, + item_metadatas: 0, + item_configs: 0, + attributes: 0, + }) + ); + }); +} + +#[test] +fn destroy_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let collection = nfts::create_collection(&account_id, &account_id); + assert_ok!(destroy( + &addr.clone(), + collection, + DestroyWitness { item_metadatas: 0, item_configs: 0, attributes: 0 } + )); + assert_eq!(pallet_nfts::Collection::::get(collection), None); + }); +} + +#[test] +fn set_max_supply_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + let value = 10; + + let collection = nfts::create_collection(&account_id, &account_id); + assert_ok!(set_max_supply(&addr.clone(), collection, value)); + + (0..value).into_iter().for_each(|i| { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account_id.clone()), + collection, + i, + ALICE.into(), + None + )); + }); + assert!(Nfts::mint( + RuntimeOrigin::signed(account_id.clone()), + collection, + value + 1, + ALICE.into(), + None + ) + .is_err()); + }); +} + +#[test] +fn mint_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + let value = 10; + + let collection = nfts::create_collection(&account_id, &account_id); + assert_ok!(mint( + &addr.clone(), + ALICE, + collection, + ITEM_ID, + MintWitness { mint_price: None, owned_item: None } + )); + assert_eq!(nfts::balance_of(COLLECTION_ID, ALICE), 1); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + let (addr, account_id) = + instantiate(CONTRACT, INIT_VALUE, function_selector("new"), vec![]); + + let (collection, item) = + nfts::create_collection_and_mint_to(&account_id, &account_id, &account_id, ITEM_ID); + assert_ok!(burn(&addr.clone(), collection, ITEM_ID,)); + assert_eq!(nfts::balance_of(COLLECTION_ID, account_id), 0); + }); +} diff --git a/pop-api/integration-tests/src/nonfungibles/utils.rs b/pop-api/integration-tests/src/nonfungibles/utils.rs new file mode 100644 index 00000000..53d44ea1 --- /dev/null +++ b/pop-api/integration-tests/src/nonfungibles/utils.rs @@ -0,0 +1,360 @@ +use pallet_revive::AddressMapper; + +use super::*; +use crate::sp_core::H160; + +pub(super) type AttributeKey = BoundedVec::KeyLimit>; +pub(super) type AttributeValue = BoundedVec::ValueLimit>; +pub(super) type MetadataData = BoundedVec::StringLimit>; + +fn do_bare_call(function: &str, addr: &H160, params: Vec) -> ExecReturnValue { + let function = function_selector(function); + let params = [function, params].concat(); + bare_call(addr.clone(), params, 0).expect("should work") +} + +// TODO - issue #263 - why result.data[1..] +pub(super) fn decoded(result: ExecReturnValue) -> Result { + ::decode(&mut &result.data[1..]).map_err(|_| result) +} + +pub(super) fn total_supply(addr: &H160, collection: CollectionId) -> Result { + let result = do_bare_call("total_supply", addr, collection.encode()); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn balance_of( + addr: &H160, + collection: CollectionId, + owner: AccountId32, +) -> Result { + let params = [collection.encode(), owner.encode()].concat(); + let result = do_bare_call("balance_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn allowance( + addr: &H160, + collection: CollectionId, + owner: AccountId32, + operator: AccountId32, + item: Option, +) -> Result { + let params = [collection.encode(), owner.encode(), operator.encode(), item.encode()].concat(); + let result = do_bare_call("allowance", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn transfer( + addr: &H160, + collection: CollectionId, + item: ItemId, + to: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), to.encode()].concat(); + let result = do_bare_call("transfer", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve( + addr: &H160, + collection: CollectionId, + item: Option, + operator: AccountId32, + approved: bool, +) -> Result<(), Error> { + let params = + [collection.encode(), item.encode(), operator.encode(), approved.encode()].concat(); + let result = do_bare_call("approve", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn owner_of( + addr: &H160, + collection: CollectionId, + item: ItemId, +) -> Result { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("owner_of", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn get_attribute( + addr: &H160, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result>, Error> { + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + ] + .concat(); + let result = do_bare_call("get_attribute", &addr, params); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) +} + +pub(super) fn create( + addr: &H160, + id: CollectionId, + admin: AccountId32, + config: CollectionConfig, +) -> Result<(), Error> { + let params = [id.encode(), admin.encode(), config.encode()].concat(); + let result = do_bare_call("create", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn destroy( + addr: &H160, + collection: CollectionId, + witness: DestroyWitness, +) -> Result<(), Error> { + let params = [collection.encode(), witness.encode()].concat(); + let result = do_bare_call("destroy", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn collection( + addr: &H160, + collection: CollectionId, +) -> Result, Error> { + let result = do_bare_call("collection", &addr, collection.encode()); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_attribute( + addr: &H160, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<(), Error> { + let params = [ + collection.encode(), + item.encode(), + namespace.encode(), + AttributeKey::truncate_from(key).encode(), + AttributeValue::truncate_from(value).encode(), + ] + .concat(); + let result = do_bare_call("set_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_attribute( + addr: &H160, + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), namespace.encode(), key.encode()].concat(); + let result = do_bare_call("clear_attribute", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_metadata( + addr: &H160, + collection: CollectionId, + item: ItemId, + data: Vec, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), data.encode()].concat(); + let result = do_bare_call("set_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_metadata( + addr: &H160, + collection: CollectionId, + item: ItemId, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("clear_metadata", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve_item_attributes( + addr: &H160, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode()].concat(); + let result = do_bare_call("approve_item_attributes", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn cancel_item_attributes_approval( + addr: &H160, + collection: CollectionId, + item: ItemId, + delegate: AccountId32, + witness: CancelAttributesApprovalWitness, +) -> Result<(), Error> { + let params = [collection.encode(), item.encode(), delegate.encode(), witness.encode()].concat(); + let result = do_bare_call("cancel_item_attributes_approval", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn set_max_supply( + addr: &H160, + collection: CollectionId, + max_supply: u32, +) -> Result<(), Error> { + let params = [collection.encode(), max_supply.encode()].concat(); + let result = do_bare_call("set_max_supply", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn item_metadata( + addr: &H160, + collection: CollectionId, + item: ItemId, +) -> Result>, Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("item_metadata", &addr, params); + decoded::>, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) + .map(|value| value.map(|v| v.to_vec())) +} + +pub(super) fn mint( + addr: &H160, + to: AccountId32, + collection: CollectionId, + item: ItemId, + witness: MintWitness, +) -> Result<(), Error> { + let params = [to.encode(), collection.encode(), item.encode(), witness.encode()].concat(); + let result = do_bare_call("mint", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn burn(addr: &H160, collection: CollectionId, item: ItemId) -> Result<(), Error> { + let params = [collection.encode(), item.encode()].concat(); + let result = do_bare_call("burn", &addr, params); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) mod nfts { + use super::*; + + pub(crate) fn create_collection_and_mint_to( + owner: &AccountId32, + admin: &AccountId32, + to: &AccountId32, + item: ItemId, + ) -> (CollectionId, ItemId) { + let collection = create_collection(owner, admin); + mint(collection, item, owner, to); + (collection, item) + } + + pub(crate) fn create_collection_mint_and_approve( + owner: &AccountId32, + admin: &AccountId32, + item: ItemId, + to: &AccountId32, + operator: &AccountId32, + ) -> (u32, u32) { + let (collection, item) = create_collection_and_mint_to(&owner.clone(), admin, to, item); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(to.clone()), + collection, + Some(item), + operator.clone().into(), + None + )); + (collection, item) + } + + pub(crate) fn create_collection(owner: &AccountId32, admin: &AccountId32) -> CollectionId { + let next_id = next_collection_id(); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(owner.clone()), + admin.clone().into(), + collection_config_with_all_settings_enabled() + )); + next_id + } + + pub(crate) fn next_collection_id() -> u32 { + pallet_nfts::NextCollectionId::::get().unwrap_or_default() + } + + pub(crate) fn mint( + collection: CollectionId, + item: ItemId, + owner: &AccountId32, + to: &AccountId32, + ) -> ItemId { + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner.clone()), + collection, + item, + to.clone().into(), + None + )); + item + } + + pub(crate) fn balance_of(collection: CollectionId, owner: AccountId32) -> u32 { + pallet_nfts::AccountBalance::::get(collection, owner) + } + + pub(super) fn collection_config_with_all_settings_enabled( + ) -> pallet_nfts::CollectionConfig { + pallet_nfts::CollectionConfig { + settings: pallet_nfts::CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: pallet_nfts::MintSettings::default(), + } + } +} + +/// Get the last event from pallet contracts. +pub(super) fn last_contract_event() -> Vec { + let events = System::read_events_for_pallet::>(); + let contract_events = events + .iter() + .filter_map(|event| match event { + pallet_revive::Event::::ContractEmitted { data, .. } => Some(data.as_slice()), + _ => None, + }) + .collect::>(); + contract_events.last().unwrap().to_vec() +} + +/// Decodes a byte slice into an `AccountId` as defined in `primitives`. +/// +/// This is used to resolve type mismatches between the `AccountId` in the integration tests and the +/// contract environment. +pub fn account_id_from_slice(s: &[u8; 32]) -> pop_api::primitives::AccountId { + pop_api::primitives::AccountId::decode(&mut &s[..]).expect("Should be decoded to AccountId") +} diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 057aa3c4..d11f3710 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -12,6 +12,8 @@ use constants::DECODING_FAILED; use ink::env::chain_extension::{ChainExtensionMethod, FromStatusCode}; pub use v0::*; +/// Module providing macros. +pub mod macros; /// Module providing primitives types. pub mod primitives; /// The first version of the API. @@ -73,6 +75,7 @@ mod constants { pub(crate) const FUNGIBLES: u8 = 150; pub(crate) const MESSAGING: u8 = 151; pub(crate) const INCENTIVES: u8 = 152; + pub(crate) const NONFUNGIBLES: u8 = 154; } // Helper method to build a dispatch call or a call to read state. diff --git a/pop-api/src/macros.rs b/pop-api/src/macros.rs new file mode 100644 index 00000000..7a9fd34d --- /dev/null +++ b/pop-api/src/macros.rs @@ -0,0 +1,53 @@ +/// Implements encoding and decoding traits for a wrapper type that represents +/// bitflags. The wrapper type should contain a field of type `$size`, where +/// `$size` is an integer type (e.g., u8, u16, u32) that can represent the bitflags. +/// The `$bitflag_enum` type is the enumeration type that defines the individual bitflags. +/// +/// This macro provides implementations for the following traits: +/// - `MaxEncodedLen`: Calculates the maximum encoded length for the wrapper type. +/// - `Encode`: Encodes the wrapper type using the provided encoding function. +/// - `EncodeLike`: Trait indicating the type can be encoded as is. +/// - `Decode`: Decodes the wrapper type from the input. +/// - `TypeInfo`: Provides type information for the wrapper type. +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl ink::scale::MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl ink::scale::Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl ink::scale::EncodeLike for $wrapper {} + impl ink::scale::Decode for $wrapper { + fn decode( + input: &mut I, + ) -> ::core::result::Result { + let field = <$size>::decode(input)?; + Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) + } + } + + #[cfg(feature = "std")] + impl ink::scale_info::TypeInfo for $wrapper { + type Identity = Self; + + fn type_info() -> ink::scale_info::Type { + ink::scale_info::Type::builder() + .path(ink::scale_info::Path::new("BitFlags", module_path!())) + .type_params(vec![ink::scale_info::TypeParameter::new( + "T", + Some(ink::scale_info::meta_type::<$bitflag_enum>()), + )]) + .composite( + ink::scale_info::build::Fields::unnamed() + .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), + ) + } + } + }; +} +pub(crate) use impl_codec_bitflags; diff --git a/pop-api/src/v0/fungibles/errors.rs b/pop-api/src/v0/fungibles/errors.rs index a5c548a6..74083c24 100644 --- a/pop-api/src/v0/fungibles/errors.rs +++ b/pop-api/src/v0/fungibles/errors.rs @@ -1,8 +1,10 @@ -//! A set of errors for use in smart contracts that interact with the fungibles api. This includes errors compliant to standards. +//! A set of errors for use in smart contracts that interact with the fungibles api. This includes +//! errors compliant to standards. -use super::*; use ink::prelude::string::{String, ToString}; +use super::*; + /// Represents various errors related to fungible tokens. /// /// The `FungiblesError` provides a detailed and specific set of error types that can occur when @@ -103,6 +105,11 @@ impl From for PSP22Error { #[cfg(test)] mod tests { + use ink::{ + prelude::string::String, + scale::{Decode, Encode}, + }; + use super::{FungiblesError, PSP22Error}; use crate::{ constants::{ASSETS, BALANCES}, @@ -114,8 +121,6 @@ mod tests { }, StatusCode, }; - use ink::prelude::string::String; - use ink::scale::{Decode, Encode}; fn error_into_status_code(error: Error) -> StatusCode { let mut encoded_error = error.encode(); diff --git a/pop-api/src/v0/fungibles/traits.rs b/pop-api/src/v0/fungibles/traits.rs index 92ad55e3..8a07f2a9 100644 --- a/pop-api/src/v0/fungibles/traits.rs +++ b/pop-api/src/v0/fungibles/traits.rs @@ -1,9 +1,11 @@ //! Traits that can be used by contracts. Including standard compliant traits. -use super::*; use core::result::Result; + use ink::prelude::string::String; +use super::*; + /// The PSP22 trait. #[ink::trait_definition] pub trait Psp22 { diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index ecad085c..50b41e78 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -13,6 +13,9 @@ pub mod incentives; /// APIs for messaging. #[cfg(feature = "messaging")] pub mod messaging; +/// APIs for nonfungible tokens. +#[cfg(feature = "nonfungibles")] +pub mod nonfungibles; pub(crate) const V0: u8 = 0; diff --git a/pop-api/src/v0/nonfungibles/errors.rs b/pop-api/src/v0/nonfungibles/errors.rs new file mode 100644 index 00000000..c955bf60 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/errors.rs @@ -0,0 +1,35 @@ +//! A set of errors for use in smart contracts that interact with the nonfungibles api. This +//! includes errors compliant to standards. + +use ink::prelude::string::{String, ToString}; + +use super::*; + +/// The PSP34 error. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Psp34Error { + /// Custom error type for cases if writer of traits added own restrictions + Custom(String), + /// Returned if owner approves self + SelfApprove, + /// Returned if the caller doesn't have allowance for transferring. + NotApproved, + /// Returned if the owner already own the token. + TokenExists, + /// Returned if the token doesn't exist + TokenNotExists, + /// Returned if safe transfer check fails + SafeTransferCheckFailed(String), +} + +impl From for Psp34Error { + /// Converts a `StatusCode` to a `PSP22Error`. + fn from(value: StatusCode) -> Self { + let encoded = value.0.to_le_bytes(); + match encoded { + // TODO: Handle conversion. + _ => Psp34Error::Custom(value.0.to_string()), + } + } +} diff --git a/pop-api/src/v0/nonfungibles/events.rs b/pop-api/src/v0/nonfungibles/events.rs new file mode 100644 index 00000000..3c409868 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/events.rs @@ -0,0 +1,50 @@ +//! A set of events for use in smart contracts interacting with the nonfungibles API. +//! +//! The `Transfer` and `Approval` events conform to the PSP-34 standard. +//! +//! These events are not emitted by the API itself but can be used in your contracts to +//! track token operations. Be mindful of the costs associated with emitting events. +//! +//! For more details, refer to [ink! events](https://use.ink/basics/events). + +use super::*; + +/// Event emitted when a token transfer occurs. +#[ink::event] +pub struct Transfer { + /// The source of the transfer. `None` when minting. + #[ink(topic)] + pub from: Option, + /// The recipient of the transfer. `None` when burning. + #[ink(topic)] + pub to: Option, + /// The item transferred (or minted/burned). + pub item: ItemId, +} + +/// Event emitted when a token approve occurs. +#[ink::event] +pub struct Approval { + /// The owner providing the allowance. + #[ink(topic)] + pub owner: AccountId, + /// The beneficiary of the allowance. + #[ink(topic)] + pub operator: AccountId, + /// The item which is (dis)approved. `None` for all owner's items. + pub item: Option, + /// Whether allowance is set or removed. + pub approved: bool, +} + +/// Event emitted when an attribute is set for a token. +#[ink::event] +pub struct AttributeSet { + /// The item which attribute is set. + #[ink(topic)] + pub item: ItemId, + /// The key for the attribute. + pub key: Vec, + /// The data for the attribute. + pub data: Vec, +} diff --git a/pop-api/src/v0/nonfungibles/mod.rs b/pop-api/src/v0/nonfungibles/mod.rs new file mode 100644 index 00000000..efe185ba --- /dev/null +++ b/pop-api/src/v0/nonfungibles/mod.rs @@ -0,0 +1,300 @@ +//! The `nonfungibles` module provides an API for interacting and managing nonfungible tokens. +//! +//! The API includes the following interfaces: +//! 1. PSP-34 +//! 2. PSP-34 Metadata +//! 3. PSP-22 Mintable & Burnable + +use constants::*; +pub use errors::*; +pub use events::*; +use ink::prelude::vec::Vec; +pub use traits::*; +pub use types::*; + +use crate::{ + constants::NONFUNGIBLES, + primitives::{AccountId, BlockNumber}, + ChainExtensionMethodApi, Result, StatusCode, +}; + +pub mod errors; +pub mod events; +pub mod traits; +pub mod types; + +#[inline] +pub fn total_supply(collection: CollectionId) -> Result { + build_read_state(TOTAL_SUPPLY) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(collection)) +} + +#[inline] +pub fn balance_of(collection: CollectionId, owner: AccountId) -> Result { + build_read_state(BALANCE_OF) + .input::<(CollectionId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, owner)) +} + +#[inline] +pub fn allowance( + collection: CollectionId, + owner: AccountId, + operator: AccountId, + item: Option, +) -> Result { + build_read_state(ALLOWANCE) + .input::<(CollectionId, AccountId, AccountId, Option)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, owner, operator, item)) +} + +#[inline] +pub fn transfer(collection: CollectionId, item: ItemId, to: AccountId) -> Result<()> { + build_dispatch(TRANSFER) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, to)) +} + +#[inline] +pub fn approve( + collection: CollectionId, + item: Option, + operator: AccountId, + approved: bool, +) -> Result<()> { + build_dispatch(APPROVE) + .input::<(CollectionId, Option, AccountId, bool)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, operator, approved)) +} + +#[inline] +pub fn owner_of(collection: CollectionId, item: ItemId) -> Result> { + build_read_state(OWNER_OF) + .input::<(CollectionId, ItemId)>() + .output::>, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn get_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result>> { + build_read_state(GET_ATTRIBUTE) + .input::<(CollectionId, ItemId, AttributeNamespace, Vec)>() + .output::>>, true>() + .handle_error_code::() + .call(&(collection, item, namespace, key)) +} + +#[inline] +pub fn create(id: CollectionId, admin: AccountId, config: CollectionConfig) -> Result<()> { + build_dispatch(CREATE) + .input::<(CollectionId, AccountId, CollectionConfig)>() + .output::, true>() + .handle_error_code::() + .call(&(id, admin, config))?; + Ok(()) +} + +#[inline] +pub fn destroy(collection: CollectionId, witness: DestroyWitness) -> Result<()> { + build_dispatch(DESTROY) + .input::<(CollectionId, DestroyWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, witness)) +} + +#[inline] +pub fn collection(collection: CollectionId) -> Result> { + build_read_state(COLLECTION) + .input::() + .output::>, true>() + .handle_error_code::() + .call(&(collection)) +} + +#[inline] +pub fn item_metadata(collection: CollectionId, item: ItemId) -> Result>> { + build_read_state(ITEM_METADATA) + .input::<(CollectionId, ItemId)>() + .output::>>, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn set_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, + value: Vec, +) -> Result<()> { + build_dispatch(SET_ATTRIBUTE) + .input::<(CollectionId, Option, AttributeNamespace, Vec, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, Some(item), namespace, key, value)) +} + +#[inline] +pub fn clear_attribute( + collection: CollectionId, + item: ItemId, + namespace: AttributeNamespace, + key: Vec, +) -> Result<()> { + build_dispatch(CLEAR_ATTRIBUTE) + .input::<(CollectionId, Option, AttributeNamespace, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, Some(item), namespace, key)) +} + +#[inline] +pub fn set_metadata(collection: CollectionId, item: ItemId, data: Vec) -> Result<()> { + build_dispatch(SET_METADATA) + .input::<(CollectionId, ItemId, Vec)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, data)) +} + +#[inline] +pub fn clear_metadata(collection: CollectionId, item: ItemId) -> Result<()> { + build_dispatch(CLEAR_METADATA) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn approve_item_attributes( + collection: CollectionId, + item: ItemId, + delegate: AccountId, +) -> Result<()> { + build_dispatch(APPROVE_ITEM_ATTRIBUTES) + .input::<(CollectionId, ItemId, AccountId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate)) +} + +#[inline] +pub fn cancel_item_attributes_approval( + collection: CollectionId, + item: ItemId, + delegate: AccountId, + witness: CancelAttributesApprovalWitness, +) -> Result<()> { + build_dispatch(CANCEL_ITEM_ATTRIBUTES_APPROVAL) + .input::<(CollectionId, ItemId, AccountId, CancelAttributesApprovalWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item, delegate, witness)) +} + +#[inline] +pub fn set_max_supply(collection: CollectionId, max_supply: u32) -> Result<()> { + build_dispatch(SET_MAX_SUPPLY) + .input::<(CollectionId, u32)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, max_supply)) +} + +#[inline] +pub fn mint( + to: AccountId, + collection: CollectionId, + item: ItemId, + witness: MintWitness, +) -> Result<()> { + build_dispatch(MINT) + .input::<(AccountId, CollectionId, ItemId, MintWitness)>() + .output::, true>() + .handle_error_code::() + .call(&(to, collection, item, witness)) +} + +#[inline] +pub fn burn(collection: CollectionId, item: ItemId) -> Result<()> { + build_dispatch(BURN) + .input::<(CollectionId, ItemId)>() + .output::, true>() + .handle_error_code::() + .call(&(collection, item)) +} + +#[inline] +pub fn next_collection_id() -> Result { + build_read_state(NEXT_COLLECTION_ID) + .output::, true>() + .handle_error_code::() + .call(&()) +} + +mod constants { + /// 1. PSP-34 + pub(super) const TOTAL_SUPPLY: u8 = 0; + pub(super) const BALANCE_OF: u8 = 1; + pub(super) const ALLOWANCE: u8 = 2; + pub(super) const TRANSFER: u8 = 3; + pub(super) const APPROVE: u8 = 4; + pub(super) const OWNER_OF: u8 = 5; + + /// 2. PSP-34 Metadata + pub(super) const GET_ATTRIBUTE: u8 = 6; + + /// 3. Management + pub(super) const CREATE: u8 = 7; + pub(super) const DESTROY: u8 = 8; + pub(super) const COLLECTION: u8 = 9; + pub(super) const NEXT_COLLECTION_ID: u8 = 10; + pub(super) const ITEM_METADATA: u8 = 11; + pub(super) const SET_ATTRIBUTE: u8 = 12; + pub(super) const CLEAR_ATTRIBUTE: u8 = 13; + pub(super) const SET_METADATA: u8 = 14; + pub(super) const CLEAR_METADATA: u8 = 15; + pub(super) const APPROVE_ITEM_ATTRIBUTES: u8 = 16; + pub(super) const CANCEL_ITEM_ATTRIBUTES_APPROVAL: u8 = 17; + pub(super) const SET_MAX_SUPPLY: u8 = 18; + + /// 4. PSP-34 Mintable & Burnable + pub(super) const MINT: u8 = 19; + pub(super) const BURN: u8 = 20; +} + +// Helper method to build a dispatch call. +// +// Parameters: +// - 'dispatchable': The index of the dispatchable function within the module. +fn build_dispatch(dispatchable: u8) -> ChainExtensionMethodApi { + crate::v0::build_dispatch(NONFUNGIBLES, dispatchable) +} + +// Helper method to build a call to read state. +// +// Parameters: +// - 'state_query': The index of the runtime state query. +fn build_read_state(state_query: u8) -> ChainExtensionMethodApi { + crate::v0::build_read_state(NONFUNGIBLES, state_query) +} diff --git a/pop-api/src/v0/nonfungibles/traits.rs b/pop-api/src/v0/nonfungibles/traits.rs new file mode 100644 index 00000000..c9e3ad04 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/traits.rs @@ -0,0 +1,85 @@ +//! Traits that can be used by contracts. Including standard compliant traits. + +use core::result::Result; + +use super::*; + +#[ink::trait_definition] +pub trait Psp34 { + /// Returns the collection `Id`. + #[ink(message, selector = 0xffa27a5f)] + fn collection_id(&self) -> ItemId; + + // Returns the current total supply of the NFT. + #[ink(message, selector = 0x628413fe)] + fn total_supply(&self) -> u128; + + /// Returns the amount of items the owner has within a collection. + /// + /// # Parameters + /// - `owner` - The account whose balance is being queried. + #[ink(message, selector = 0xcde7e55f)] + fn balance_of(&self, owner: AccountId) -> u32; + + /// Returns whether the operator is approved by the owner to withdraw `item`. If `item` is + /// `None`, it returns whether the operator is approved to withdraw all owner's items for the + /// given collection. + /// + /// # Parameters + /// * `owner` - The account that owns the item(s). + /// * `operator` - the account that is allowed to withdraw the item(s). + /// * `item` - The item. If `None`, it is regarding all owner's items in collection. + #[ink(message, selector = 0x4790f55a)] + fn allowance(&self, owner: AccountId, operator: AccountId, id: Option) -> bool; + + /// Transfers an owned or approved item to the specified recipient. + /// + /// # Parameters + /// * `to` - The recipient account. + /// * `item` - The item. + /// - `data` - Additional data in unspecified format. + #[ink(message, selector = 0x3128d61b)] + fn transfer(&mut self, to: AccountId, id: ItemId, data: Vec) -> Result<(), Psp34Error>; + + /// Approves operator to withdraw item(s) from the contract's account. + /// + /// # Parameters + /// * `operator` - The account that is allowed to withdraw the item. + /// * `item` - Optional item. `None` means all items owned in the specified collection. + /// * `approved` - Whether the operator is given or removed the right to withdraw the item(s). + #[ink(message, selector = 0x1932a8b0)] + fn approve( + &mut self, + operator: AccountId, + id: Option, + approved: bool, + ) -> Result<(), Psp34Error>; + + /// Returns the owner of an item within a specified collection, if any. + /// + /// # Parameters + /// * `item` - The item. + #[ink(message, selector = 0x1168624d)] + fn owner_of(&self, id: ItemId) -> Option; +} + +#[ink::trait_definition] +pub trait Psp34Metadata { + /// Returns the attribute of `item` for the given `key`. + /// + /// # Parameters + /// * `item` - The item. If `None` the attributes for the collection are queried. + /// * `namespace` - The attribute's namespace. + /// * `key` - The key of the attribute. + #[ink(message, selector = 0xf19d48d1)] + fn get_attribute(&self, id: ItemId, key: Vec) -> Option>; +} + +#[ink::trait_definition] +pub trait Psp34Enumerable { + #[ink(message, selector = 0x3bcfb511)] + fn owners_token_by_index(&self, owner: AccountId, index: u128) -> Result; + + #[ink(message, selector = 0xcd0340d0)] + fn token_by_index(&self, index: u128) -> Result; +} diff --git a/pop-api/src/v0/nonfungibles/types.rs b/pop-api/src/v0/nonfungibles/types.rs new file mode 100644 index 00000000..e7029418 --- /dev/null +++ b/pop-api/src/v0/nonfungibles/types.rs @@ -0,0 +1,223 @@ +use enumflags2::{bitflags, BitFlags}; + +use super::*; +use crate::{macros::impl_codec_bitflags, primitives::AccountId}; + +pub type ItemId = u32; +pub type CollectionId = u32; +pub(super) type Balance = u32; + +/// Information about a collection. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: Balance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, +} + +/// Attribute namespaces for non-fungible tokens. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum AttributeNamespace { + /// An attribute set by collection's owner. + #[codec(index = 1)] + CollectionOwner, + /// An attribute set by item's owner. + #[codec(index = 2)] + ItemOwner, + /// An attribute set by a pre-approved account. + #[codec(index = 3)] + Account(AccountId), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum MintType { + /// Only an `Issuer` could mint items. + Issuer, + /// Anyone could mint items. + Public, + /// Only holders of items in specified collection could mint new items. + HolderOf(CollectionId), +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, +} + +/// Witness data for the destroy transactions. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct DestroyWitness { + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + #[codec(compact)] + pub item_configs: u32, + /// The total number of attributes for this collection. + #[codec(compact)] + pub attributes: u32, +} + +/// Witness data for items mint transactions. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owned_item: Option, + /// The price specified in mint settings. + pub mint_price: Option, +} + +/// Collection's configuration. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct CollectionConfig { + /// Collection's settings. + pub settings: CollectionSettings, + /// Collection's max supply. + pub max_supply: Option, + /// Default settings each item will get during the mint. + pub mint_settings: MintSettings, +} + +impl CollectionConfig { + pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { + !self.settings.is_disabled(setting) + } + + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { + self.settings.is_disabled(setting) + } + + pub fn enable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.remove(setting); + } + + pub fn disable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 user-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum CollectionSetting { + /// Items in this collection are transferable. + TransferableItems, + /// The metadata of this collection can be modified. + UnlockedMetadata, + /// Attributes of this collection can be modified. + UnlockedAttributes, + /// The supply of this collection can be modified. + UnlockedMaxSupply, + /// When this isn't set then the deposit is required to hold the items of this collection. + DepositRequired, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CollectionSettings(pub BitFlags); + +impl CollectionSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + + pub fn get_disabled(&self) -> BitFlags { + self.0 + } + + pub fn is_disabled(&self, setting: CollectionSetting) -> bool { + self.0.contains(setting) + } + + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); + +/// Support for up to 64 user-enabled features on an item. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum ItemSetting { + /// This item is transferable. + Transferable, + /// The metadata of this item can be modified. + UnlockedMetadata, + /// Attributes of this item can be modified. + UnlockedAttributes, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ItemSettings(pub BitFlags); + +impl ItemSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + + pub fn get_disabled(&self) -> BitFlags { + self.0 + } + + pub fn is_disabled(&self, setting: ItemSetting) -> bool { + self.0.contains(setting) + } + + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(ItemSettings, u64, ItemSetting); + +/// Holds the information about minting. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub struct MintSettings { + /// Whether anyone can mint or if minters are restricted to some subset. + pub mint_type: MintType, + /// An optional price per mint. + pub price: Option, + /// When the mint starts. + pub start_block: Option, + /// When the mint ends. + pub end_block: Option, + /// Default settings each item will get during the mint. + pub default_item_settings: ItemSettings, +} + +impl Default for MintSettings { + fn default() -> Self { + Self { + mint_type: MintType::Issuer, + price: None, + start_block: None, + end_block: None, + default_item_settings: ItemSettings::all_enabled(), + } + } +} diff --git a/runtime/devnet/src/config/api/mod.rs b/runtime/devnet/src/config/api/mod.rs index 911c6e19..2677626c 100644 --- a/runtime/devnet/src/config/api/mod.rs +++ b/runtime/devnet/src/config/api/mod.rs @@ -12,8 +12,8 @@ use versioning::*; use crate::{ config::{assets::TrustBackedAssetsInstance, xcm::LocalOriginToLocation}, - fungibles, messaging, Balances, Ismp, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, - RuntimeHoldReason, TransactionByteFee, + fungibles, messaging, nonfungibles, Balances, Ismp, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeHoldReason, TransactionByteFee, }; mod versioning; @@ -37,6 +37,9 @@ pub enum RuntimeRead { /// Messaging state queries. #[codec(index = 151)] Messaging(messaging::Read), + /// Non-fungible token queries. + #[codec(index = 154)] + NonFungibles(nonfungibles::Read), } impl Readable for RuntimeRead { @@ -49,6 +52,7 @@ impl Readable for RuntimeRead { match self { RuntimeRead::Fungibles(key) => fungibles::Pallet::weight(key), RuntimeRead::Messaging(key) => messaging::Pallet::weight(key), + RuntimeRead::NonFungibles(key) => nonfungibles::Pallet::weight(key), } } @@ -57,6 +61,8 @@ impl Readable for RuntimeRead { match self { RuntimeRead::Fungibles(key) => RuntimeResult::Fungibles(fungibles::Pallet::read(key)), RuntimeRead::Messaging(key) => RuntimeResult::Messaging(messaging::Pallet::read(key)), + RuntimeRead::NonFungibles(key) => + RuntimeResult::NonFungibles(nonfungibles::Pallet::read(key)), } } } @@ -67,7 +73,9 @@ impl Readable for RuntimeRead { pub enum RuntimeResult { /// Fungible token read results. Fungibles(fungibles::ReadResult), - // Messaging state read results. + /// Non-fungible token read results. + NonFungibles(nonfungibles::ReadResult), + /// Messaging state read results. Messaging(messaging::ReadResult), } @@ -77,6 +85,7 @@ impl RuntimeResult { match self { RuntimeResult::Fungibles(result) => result.encode(), RuntimeResult::Messaging(result) => result.encode(), + RuntimeResult::NonFungibles(result) => result.encode(), } } } @@ -108,6 +117,10 @@ impl fungibles::Config for Runtime { type WeightInfo = fungibles::weights::SubstrateWeight; } +impl nonfungibles::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + #[derive(Default)] pub struct Config; impl pallet_api::extension::Config for Config { @@ -161,10 +174,8 @@ pub struct Filter(PhantomData); impl> Contains for Filter { fn contains(c: &RuntimeCall) -> bool { - use fungibles::Call::*; - use messaging::Call::*; - use pallet_incentives::Call::*; - T::BaseCallFilter::contains(c) && + let contain_fungibles: bool = { + use fungibles::Call::*; matches!( c, RuntimeCall::Fungibles( @@ -175,34 +186,92 @@ impl> Contains f create { .. } | set_metadata { .. } | start_destroy { .. } | clear_metadata { .. } | - mint { .. } | burn { .. } - ) | RuntimeCall::Messaging( + mint { .. } | burn { .. }, + ) + ) + }; + + let contain_nonfungibles: bool = { + use nonfungibles::Call::*; + matches!( + c, + RuntimeCall::NonFungibles( + transfer { .. } | + approve { .. } | create { .. } | + destroy { .. } | set_metadata { .. } | + clear_metadata { .. } | + set_attribute { .. } | + clear_attribute { .. } | + approve_item_attributes { .. } | + cancel_item_attributes_approval { .. } | + mint { .. } | burn { .. } | + set_max_supply { .. }, + ) + ) + }; + + let contain_messaging: bool = { + use messaging::Call::*; + matches!( + c, + RuntimeCall::Messaging( send { .. } | ismp_get { .. } | ismp_post { .. } | xcm_new_query { .. } | - remove { .. } - ) | RuntimeCall::Incentives( - register_contract { .. } | claim_rewards { .. } | deposit_funds { .. } + remove { .. }, ) ) + }; + + let contain_incentives: bool = { + use pallet_incentives::Call::*; + matches!( + c, + RuntimeCall::Incentives( + register_contract { .. } | claim_rewards { .. } | deposit_funds { .. }, + ) + ) + }; + + T::BaseCallFilter::contains(c) && + contain_fungibles | contain_nonfungibles | contain_messaging | contain_incentives } } impl Contains for Filter { fn contains(r: &RuntimeRead) -> bool { - use fungibles::Read::*; - use messaging::Read::*; - matches!( - r, - RuntimeRead::Fungibles( - TotalSupply(..) | - BalanceOf { .. } | - Allowance { .. } | - TokenName(..) | TokenSymbol(..) | - TokenDecimals(..) | - TokenExists(..) - ) | RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..)) - ) + 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 { .. }, + ) + ) + }; + + let contain_messaging: bool = { + use messaging::Read::*; + matches!(r, RuntimeRead::Messaging(Poll(..) | Get(..) | QueryId(..))) + }; + + contain_fungibles | contain_nonfungibles | contain_messaging } } @@ -210,8 +279,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::*; @@ -222,6 +292,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] @@ -269,6 +343,72 @@ mod tests { } } + #[test] + fn filter_allows_nonfungibles_calls() { + use pallet_api::nonfungibles::{ + types::{CollectionConfig, CollectionSettings, MintSettings}, + Call::*, + }; + use pallet_nfts::{CancelAttributesApprovalWitness, DestroyWitness}; + + for call in vec![ + NonFungibles(transfer { collection: 0, item: 0, to: ACCOUNT }), + NonFungibles(approve { + collection: 0, + item: Some(0), + operator: ACCOUNT, + approved: false, + }), + NonFungibles(create { + id: 0, + 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_fungibles_reads() { use super::{fungibles::Read::*, RuntimeRead::*}; @@ -286,4 +426,33 @@ mod tests { assert!(Filter::::contains(&read)) } } + + #[test] + fn filter_allows_nonfungibles_reads() { + use super::{nonfungibles::Read::*, RuntimeRead::*}; + + for read in vec![ + NonFungibles(TotalSupply(1)), + NonFungibles(BalanceOf { collection: 1, owner: ACCOUNT }), + NonFungibles(Allowance { + collection: 1, + item: None, + owner: ACCOUNT, + operator: ACCOUNT, + }), + NonFungibles(OwnerOf { collection: 1, item: 1 }), + NonFungibles(GetAttribute { + collection: 0, + item: 0, + namespace: pallet_nfts::AttributeNamespace::CollectionOwner, + key: bounded_vec![], + }), + NonFungibles(Collection(1)), + NonFungibles(NextCollectionId), + ] + .iter() + { + assert!(Filter::::contains(read)) + } + } } diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index 326b7e59..28a51d52 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -6,7 +6,7 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureSigned}; use pallet_nfts::PalletFeatures; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId, Signature}; -use sp_runtime::traits::Verify; +use sp_runtime::traits::{Get, Verify}; use crate::{ deposit, AccountId, Assets, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, @@ -37,6 +37,15 @@ parameter_types! { pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(PartialEq, Clone))] +pub struct KeyLimit; +impl Get for KeyLimit { + fn get() -> u32 { + N + } +} + impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; @@ -56,7 +65,7 @@ impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ItemId = ItemId; // TODO: source from primitives - type KeyLimit = ConstU32<64>; + type KeyLimit = KeyLimit<64>; type Locker = (); type MaxAttributesPerCall = ConstU32<10>; type MaxDeadlineDuration = NftsMaxDeadlineDuration; diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index e0579078..498ffda2 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -41,7 +41,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -use pallet_api::{fungibles, messaging}; +use pallet_api::{fungibles, messaging, nonfungibles}; use pallet_balances::Call as BalancesCall; use pallet_ismp::mmr::{Leaf, Proof, ProofKeys}; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; @@ -697,6 +697,9 @@ mod runtime { #[runtime::pallet_index(152)] pub type Incentives = pallet_incentives::Pallet; + #[runtime::pallet_index(154)] + pub type NonFungibles = nonfungibles::Pallet; + // Revive #[runtime::pallet_index(255)] pub type Revive = pallet_revive::Pallet; @@ -711,6 +714,7 @@ mod benches { [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] [pallet_message_queue, MessageQueue] + [pallet_nfts, Nfts] [pallet_sudo, Sudo] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem]