From 97a7e3096abab6b8dcdfe76afe52384f952ee6d0 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 21 Sep 2022 17:57:14 +0300 Subject: [PATCH 01/94] Basics --- Cargo.lock | 5 +- frame/nfts/Cargo.toml | 1 + frame/nfts/src/functions.rs | 58 +++++ frame/nfts/src/impl_nonfungibles.rs | 1 + frame/nfts/src/lib.rs | 48 ++++ frame/nfts/src/mock.rs | 7 +- frame/nfts/src/tests.rs | 378 ++++++++++++++++++++++++++-- frame/nfts/src/types.rs | 124 ++++++++- 8 files changed, 591 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d56a05fc0f7ed..cef247d87bafb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1881,9 +1881,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", ] @@ -5911,6 +5911,7 @@ dependencies = [ name = "pallet-nfts" version = "4.0.0-dev" dependencies = [ + "enumflags2", "frame-benchmarking", "frame-support", "frame-system", diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 7f1ce4ff416b0..20d6c4b7f083f 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +enumflags2 = { version = "0.7.5" } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 27ab752dbabf6..d6aa5bd780a77 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -18,6 +18,7 @@ //! Various pieces of common functionality. use super::*; +use enumflags2::BitFlags; use frame_support::{ ensure, traits::{ExistenceRequirement, Get}, @@ -39,6 +40,14 @@ impl, I: 'static> Pallet { ensure!(!collection_details.is_frozen, Error::::Frozen); ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + let config = + CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; + let user_features: BitFlags = config.user_features.get(); + ensure!( + !user_features.contains(UserFeature::NonTransferableItems), + Error::::ItemsNotTransferable + ); + let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; ensure!(!details.is_frozen, Error::::Frozen); @@ -69,6 +78,7 @@ impl, I: 'static> Pallet { pub fn do_create_collection( collection: T::CollectionId, owner: T::AccountId, + user_config: UserFeatures, admin: T::AccountId, deposit: DepositBalanceOf, free_holding: bool, @@ -94,6 +104,12 @@ impl, I: 'static> Pallet { }, ); + let collection_config = CollectionConfig { + system_features: SystemFeatures::new((T::DefaultSystemConfig::get()).get()), + user_features: user_config, + }; + CollectionConfigs::::insert(&collection, collection_config); + CollectionAccount::::insert(&owner, &collection, ()); Self::deposit_event(event); Ok(()) @@ -229,6 +245,14 @@ impl, I: 'static> Pallet { let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); + let config = + CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; + let user_features: BitFlags = config.user_features.get(); + ensure!( + !user_features.contains(UserFeature::NonTransferableItems), + Error::::ItemsNotTransferable + ); + if let Some(ref price) = price { ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); Self::deposit_event(Event::ItemPriceSet { @@ -254,6 +278,14 @@ impl, I: 'static> Pallet { let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner != buyer, Error::::NoPermission); + let config = + CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; + let user_features: BitFlags = config.user_features.get(); + ensure!( + !user_features.contains(UserFeature::NonTransferableItems), + Error::::NotForSale + ); + let price_info = ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; @@ -284,4 +316,30 @@ impl, I: 'static> Pallet { Ok(()) } + + pub fn do_change_collection_config( + id: T::CollectionId, + caller: T::AccountId, + current_config: CollectionConfig, + new_config: UserFeatures, + ) -> DispatchResult { + let collection = Collection::::get(id).ok_or(Error::::UnknownCollection)?; + ensure!(collection.owner == caller, Error::::NoPermission); + + let user_features: BitFlags = current_config.user_features.get(); + + if user_features.contains(UserFeature::IsLocked) { + return Err(Error::::CollectionIsLocked.into()) + } + + CollectionConfigs::::try_mutate(id, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; + + config.user_features = new_config; + + Self::deposit_event(Event::::CollectionConfigChanged { id }); + + Ok(()) + }) + } } diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index cead6f562ab58..bff4d768b7d6c 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -95,6 +95,7 @@ impl, I: 'static> Create<::AccountId> for Pallet Self::do_create_collection( *collection, who.clone(), + UserFeatures::new(UserFeature::Administration.into()), admin.clone(), T::CollectionDeposit::get(), false, diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index cdb098d2eceed..4f9d03399e901 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -42,6 +42,7 @@ mod types; pub mod weights; use codec::{Decode, Encode}; +use enumflags2::BitFlags; use frame_support::{ traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, @@ -154,6 +155,8 @@ pub mod pallet { #[pallet::constant] type ApprovalsLimit: Get; + type DefaultSystemConfig: Get; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -278,6 +281,11 @@ pub mod pallet { pub(super) type CollectionMaxSupply, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + /// Maps a unique collection id to it's config. + #[pallet::storage] + pub(super) type CollectionConfigs, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -372,6 +380,8 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// The config of a collection has change. + CollectionConfigChanged { id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -407,6 +417,8 @@ pub mod pallet { BadWitness, /// The item ID is already taken. InUse, + /// Items within that collection are non-transferable. + ItemsNotTransferable, /// The item or collection is frozen. Frozen, /// The provided account is not a delegate. @@ -419,6 +431,8 @@ pub mod pallet { Unaccepted, /// The item is locked. Locked, + /// The collection is locked. + CollectionIsLocked, /// All items have been minted. MaxSupplyReached, /// The max supply has already been set. @@ -469,6 +483,7 @@ pub mod pallet { pub fn create( origin: OriginFor, collection: T::CollectionId, + config: UserFeatures, admin: AccountIdLookupOf, ) -> DispatchResult { let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; @@ -477,6 +492,7 @@ pub mod pallet { Self::do_create_collection( collection, owner.clone(), + config, admin.clone(), T::CollectionDeposit::get(), false, @@ -506,6 +522,7 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, + config: UserFeatures, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -514,6 +531,7 @@ pub mod pallet { Self::do_create_collection( collection, owner.clone(), + config, owner.clone(), Zero::zero(), free_holding, @@ -521,6 +539,19 @@ pub mod pallet { ) } + #[pallet::weight(0)] + pub fn change_collection_config( + origin: OriginFor, + id: T::CollectionId, + new_config: UserFeatures, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let current_config = + CollectionConfigs::::get(id).ok_or(Error::::UnknownCollection)?; + Self::do_change_collection_config(id, sender, current_config, new_config)?; + Ok(()) + } + /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the @@ -954,6 +985,14 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; + let config = CollectionConfigs::::get(collection) + .ok_or(Error::::UnknownCollection)?; + let user_features: BitFlags = config.user_features.get(); + ensure!( + !user_features.contains(UserFeature::NonTransferableItems), + Error::::ItemsNotTransferable + ); + let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = @@ -1388,6 +1427,15 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; + let config = CollectionConfigs::::get(collection) + .ok_or(Error::::UnknownCollection)?; + + let user_features: BitFlags = config.user_features.get(); + ensure!( + !user_features.contains(UserFeature::IsLocked), + Error::::CollectionIsLocked + ); + let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index bfa6c185ed78c..4fa4a1a381902 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_nfts; use frame_support::{ - construct_runtime, + construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; use sp_core::H256; @@ -84,6 +84,10 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } +parameter_types! { + pub NoDeposit: SystemFeatures = SystemFeatures::new(SystemFeature::NoDeposit.into()); +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type CollectionId = u32; @@ -101,6 +105,7 @@ impl Config for Test { type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; type ApprovalsLimit = ConstU32<10>; + type DefaultSystemConfig = NoDeposit; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 19d24f4924d46..d94201e96b7bb 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -22,6 +22,9 @@ use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Curr use pallet_balances::Error as BalancesError; use sp_std::prelude::*; +pub const DEFAULT_SYSTEM_FEATURES: SystemFeature = SystemFeature::NoDeposit; +pub const DEFAULT_USER_FEATURES: UserFeature = UserFeature::Administration; + fn items() -> Vec<(u64, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); r.sort(); @@ -88,6 +91,21 @@ fn events() -> Vec> { result } +fn get_id_from_event() -> Result<::CollectionId, &'static str> { + let last_event = System::events().pop(); + if let Some(e) = last_event.clone() { + match e.event { + mock::RuntimeEvent::Nfts(inner_event) => match inner_event { + Event::ForceCreated { collection, .. } => return Ok(collection), + _ => {}, + }, + _ => {}, + } + } + + Err("bad event") +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -98,23 +116,69 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(Origin::root(), 1, 2, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 1, + 2, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Nfts::mint(Origin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } +#[test] +fn collection_locking_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + user_id, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); + + let id = get_id_from_event().unwrap(); + let new_config = UserFeatures::new(UserFeature::IsLocked.into()); + + assert_ok!(Nfts::change_collection_config(Origin::signed(user_id), id, new_config)); + + let collection_config = CollectionConfigs::::get(id); + + let expected_config = CollectionConfig { + system_features: SystemFeatures::new(DEFAULT_SYSTEM_FEATURES.into()), + user_features: new_config, + }; + + assert_eq!(Some(expected_config), collection_config); + }); +} + #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create( + Origin::signed(1), + 0, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + 1 + )); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); @@ -157,7 +221,12 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create( + Origin::signed(1), + 0, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + 1 + )); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -168,7 +237,13 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -179,7 +254,13 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 3)); @@ -188,13 +269,36 @@ fn transfer_should_work() { assert_ok!(Nfts::approve_transfer(Origin::signed(3), 0, 42, 2, None)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 4)); + + // validate we can't transfer non-transferable items + let collection_id = 1; + assert_ok!(Nfts::force_create( + Origin::root(), + 1, + 1, + UserFeatures::new(UserFeature::NonTransferableItems.into()), + true + )); + + assert_ok!(Nfts::mint(Origin::signed(1), 1, 1, 42)); + + assert_noop!( + Nfts::transfer(Origin::signed(1), collection_id, 42, 3,), + Error::::ItemsNotTransferable + ); }); } #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(Origin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); @@ -211,7 +315,13 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -236,7 +346,12 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(Origin::signed(1), 0, 1)); + assert_ok!(Nfts::create( + Origin::signed(1), + 0, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + 1 + )); assert_eq!(collections(), vec![(1, 0)]); assert_noop!(Nfts::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted); assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); @@ -275,7 +390,13 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 2)); @@ -294,7 +415,13 @@ fn set_collection_metadata_should_work() { Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); // Cannot add metadata to unowned item assert_noop!( Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), @@ -351,7 +478,13 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( @@ -396,6 +529,17 @@ fn set_item_metadata_should_work() { ); assert_ok!(Nfts::clear_metadata(Origin::signed(1), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); + + // collection's metadata can't be changed after the collection gets locked + assert_ok!(Nfts::change_collection_config( + Origin::signed(1), + 0, + UserFeatures::new(UserFeature::IsLocked.into()) + )); + assert_noop!( + Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), + Error::::CollectionIsLocked + ); }); } @@ -404,7 +548,13 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -449,7 +599,13 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -481,7 +637,13 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); @@ -515,7 +677,13 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + false + )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -539,7 +707,13 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); @@ -548,13 +722,36 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::approve_transfer(Origin::signed(4), 0, 42, 2, None)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 2)); + + // ensure we can't buy an item when the collection has a NonTransferableItems flag + let collection_id = 1; + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + 1, + UserFeatures::new(UserFeature::NonTransferableItems.into()), + true + )); + + assert_ok!(Nfts::mint(Origin::signed(1), 1, collection_id, 1)); + + assert_noop!( + Nfts::approve_transfer(Origin::signed(1), collection_id, 1, 2, None), + Error::::ItemsNotTransferable + ); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -601,7 +798,13 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); let current_block = 1; @@ -620,7 +823,13 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); for i in 3..13 { @@ -640,7 +849,13 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. @@ -664,7 +879,13 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -692,7 +913,13 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -714,7 +941,13 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + 0, + 1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -747,7 +980,13 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + user_id, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( @@ -793,7 +1032,13 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + user_id, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_1, user_id)); assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_2, user_id)); @@ -830,6 +1075,23 @@ fn set_price_should_work() { item: item_2 })); assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // ensure we can't set price when the items are non-transferable + let collection_id = 1; + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + user_id, + UserFeatures::new(UserFeature::NonTransferableItems.into()), + true + )); + + assert_ok!(Nfts::mint(Origin::signed(user_id), collection_id, item_1, user_id)); + + assert_noop!( + Nfts::set_price(Origin::signed(user_id), collection_id, item_1, Some(2), None), + Error::::ItemsNotTransferable + ); }); } @@ -851,7 +1113,13 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(Origin::root(), collection_id, user_1, true)); + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + user_1, + UserFeatures::new(DEFAULT_USER_FEATURES.into()), + true + )); assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_1, user_1)); assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_2, user_1)); @@ -952,5 +1220,61 @@ fn buy_item_should_work() { }); assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); } + + // ensure we can't buy an item when the collection has a NonTransferableItems flag + let collection_id = 1; + assert_ok!(Nfts::force_create( + Origin::root(), + collection_id, + user_1, + UserFeatures::new(UserFeature::NonTransferableItems.into()), + true + )); + + assert_ok!(Nfts::mint(Origin::signed(user_1), collection_id, item_1, user_1)); + + assert_noop!( + Nfts::buy_item(Origin::signed(user_2), collection_id, item_1, price_1.into()), + Error::::NotForSale + ); + }); +} + +#[test] +fn different_user_flags() { + new_test_ext().execute_with(|| { + // when setting one feature it's required to call .into() on it + let user_features = UserFeatures::new(UserFeature::IsLocked.into()); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, user_features, false)); + + let collection_config = CollectionConfigs::::get(0); + let stored_user_features = collection_config.unwrap().user_features.get(); + assert!(stored_user_features.contains(UserFeature::IsLocked)); + assert!(!stored_user_features.contains(UserFeature::Administration)); + + // no need to call .into() for multiple features + let user_features = UserFeatures::new(UserFeature::Administration | UserFeature::IsLocked); + assert_ok!(Nfts::force_create(Origin::root(), 1, 1, user_features, false)); + let collection_config = CollectionConfigs::::get(1); + let stored_user_features = collection_config.unwrap().user_features.get(); + assert!(stored_user_features.contains(UserFeature::IsLocked)); + assert!(stored_user_features.contains(UserFeature::Administration)); + + assert_ok!(Nfts::force_create( + Origin::root(), + 2, + 1, + UserFeatures::new(BitFlags::EMPTY), + false + )); + + use enumflags2::BitFlag; + assert_ok!(Nfts::force_create( + Origin::root(), + 3, + 1, + UserFeatures::new(UserFeature::empty()), + false + )); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index db1c351c4a9c5..3c0b0c3791277 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -18,11 +18,12 @@ //! Various basic types for use in the Nfts pallet. use super::*; +use enumflags2::{bitflags, BitFlags}; use frame_support::{ pallet_prelude::{BoundedVec, MaxEncodedLen}, traits::Get, }; -use scale_info::TypeInfo; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -127,3 +128,124 @@ pub struct ItemMetadata> { /// Whether the item metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } + +// Support for up to 64 user-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum UserFeature { + Administration, + IsLocked, + NonTransferableItems, + MetadataIsLocked, + AttributesAreLocked, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct UserFeatures(pub BitFlags); + +impl UserFeatures { + pub fn new(input: BitFlags) -> Self { + UserFeatures(input) + } + + pub fn get(&self) -> BitFlags { + self.0.clone() + } +} + +impl MaxEncodedLen for UserFeatures { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +impl Encode for UserFeatures { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for UserFeatures { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) + } +} +impl TypeInfo for UserFeatures { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("UserFeature"))) + } +} + +// Support for up to 64 system-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum SystemFeature { + NoDeposit, + NoCollectionDeposit, + NoItemDeposit, + NoDataDeposit, + NoTrading, + NoAttributes, + NoApprovals, + NoSwaps, + NoClaims, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] +pub struct SystemFeatures(pub BitFlags); + +impl SystemFeatures { + pub fn new(input: BitFlags) -> Self { + SystemFeatures(input) + } + + pub fn get(&self) -> BitFlags { + self.0.clone() + } +} + +impl MaxEncodedLen for SystemFeatures { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +impl Eq for SystemFeatures {} +impl Encode for SystemFeatures { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for SystemFeatures { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) + } +} +impl TypeInfo for SystemFeatures { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("SystemFeature"))) + } +} + +// TODO: Implement Default + +#[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +pub struct CollectionConfig { + pub system_features: SystemFeatures, + pub user_features: UserFeatures, +} From a85608f59457b2dd0eca4351a3d269cdc95b5311 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 23 Sep 2022 19:06:59 +0300 Subject: [PATCH 02/94] WIP: change the data format --- frame/nfts/src/functions.rs | 27 ++--- frame/nfts/src/impl_nonfungibles.rs | 7 +- frame/nfts/src/lib.rs | 23 ++-- frame/nfts/src/mock.rs | 5 +- frame/nfts/src/tests.rs | 102 ++++++++-------- frame/nfts/src/types.rs | 110 ++++++------------ .../support/src/traits/tokens/nonfungibles.rs | 3 +- 7 files changed, 121 insertions(+), 156 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index d6aa5bd780a77..e649077d0c424 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -42,9 +42,9 @@ impl, I: 'static> Pallet { let config = CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.user_features.get(); + let user_features: BitFlags = config.get(); // TODO: can we omit the type? ensure!( - !user_features.contains(UserFeature::NonTransferableItems), + !user_features.contains(CollectionFeature::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -78,7 +78,7 @@ impl, I: 'static> Pallet { pub fn do_create_collection( collection: T::CollectionId, owner: T::AccountId, - user_config: UserFeatures, + collection_config: CollectionFeatures, admin: T::AccountId, deposit: DepositBalanceOf, free_holding: bool, @@ -104,10 +104,7 @@ impl, I: 'static> Pallet { }, ); - let collection_config = CollectionConfig { - system_features: SystemFeatures::new((T::DefaultSystemConfig::get()).get()), - user_features: user_config, - }; + // let config/*: BitFlags*/ = CollectionFeatures::new(collection_config); CollectionConfigs::::insert(&collection, collection_config); CollectionAccount::::insert(&owner, &collection, ()); @@ -247,9 +244,9 @@ impl, I: 'static> Pallet { let config = CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.user_features.get(); + let user_features: BitFlags = config.get(); ensure!( - !user_features.contains(UserFeature::NonTransferableItems), + !user_features.contains(CollectionFeature::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -280,9 +277,9 @@ impl, I: 'static> Pallet { let config = CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.user_features.get(); + let user_features: BitFlags = config.get(); ensure!( - !user_features.contains(UserFeature::NonTransferableItems), + !user_features.contains(CollectionFeature::NonTransferableItems), Error::::NotForSale ); @@ -317,16 +314,16 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn do_change_collection_config( + /*pub fn do_change_collection_config( id: T::CollectionId, caller: T::AccountId, current_config: CollectionConfig, - new_config: UserFeatures, + new_config: CollectionFeatures, ) -> DispatchResult { let collection = Collection::::get(id).ok_or(Error::::UnknownCollection)?; ensure!(collection.owner == caller, Error::::NoPermission); - let user_features: BitFlags = current_config.user_features.get(); + let user_features: BitFlags = current_config.user_features.get(); if user_features.contains(UserFeature::IsLocked) { return Err(Error::::CollectionIsLocked.into()) @@ -341,5 +338,5 @@ impl, I: 'static> Pallet { Ok(()) }) - } + }*/ } diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index bff4d768b7d6c..4dced442839a1 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -85,17 +85,20 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } -impl, I: 'static> Create<::AccountId> for Pallet { +impl, I: 'static> Create<::AccountId, BitFlags> + for Pallet +{ /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, + config: &BitFlags, ) -> DispatchResult { Self::do_create_collection( *collection, who.clone(), - UserFeatures::new(UserFeature::Administration.into()), + CollectionFeatures::new(*config), admin.clone(), T::CollectionDeposit::get(), false, diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 4f9d03399e901..2ac14d6a0ee20 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -155,7 +155,7 @@ pub mod pallet { #[pallet::constant] type ApprovalsLimit: Get; - type DefaultSystemConfig: Get; + type FeatureFlags: Get; #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. @@ -283,8 +283,9 @@ pub mod pallet { /// Maps a unique collection id to it's config. #[pallet::storage] + // TODO: rename to CollectionConfigOf ? pub(super) type CollectionConfigs, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig>; + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionFeatures>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -483,7 +484,7 @@ pub mod pallet { pub fn create( origin: OriginFor, collection: T::CollectionId, - config: UserFeatures, + config: CollectionFeatures, admin: AccountIdLookupOf, ) -> DispatchResult { let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; @@ -522,7 +523,7 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, - config: UserFeatures, + config: CollectionFeatures, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -539,18 +540,18 @@ pub mod pallet { ) } - #[pallet::weight(0)] + /*#[pallet::weight(0)] pub fn change_collection_config( origin: OriginFor, id: T::CollectionId, - new_config: UserFeatures, + new_config: CollectionFeatures, ) -> DispatchResult { let sender = ensure_signed(origin)?; let current_config = CollectionConfigs::::get(id).ok_or(Error::::UnknownCollection)?; Self::do_change_collection_config(id, sender, current_config, new_config)?; Ok(()) - } + }*/ /// Destroy a collection of fungible items. /// @@ -987,9 +988,9 @@ pub mod pallet { let config = CollectionConfigs::::get(collection) .ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.user_features.get(); + let user_features: BitFlags = config.get(); ensure!( - !user_features.contains(UserFeature::NonTransferableItems), + !user_features.contains(CollectionFeature::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -1430,9 +1431,9 @@ pub mod pallet { let config = CollectionConfigs::::get(collection) .ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.user_features.get(); + let user_features: BitFlags = config.get(); ensure!( - !user_features.contains(UserFeature::IsLocked), + !user_features.contains(CollectionFeature::IsLocked), Error::::CollectionIsLocked ); diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 4fa4a1a381902..8581855081b4b 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -20,6 +20,7 @@ use super::*; use crate as pallet_nfts; +use enumflags2::BitFlags; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, @@ -85,7 +86,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub NoDeposit: SystemFeatures = SystemFeatures::new(SystemFeature::NoDeposit.into()); + pub FeatureFlags: SystemFeatures = BitFlags::EMPTY; } impl Config for Test { @@ -105,7 +106,7 @@ impl Config for Test { type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; type ApprovalsLimit = ConstU32<10>; - type DefaultSystemConfig = NoDeposit; + type FeatureFlags = FeatureFlags; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index d94201e96b7bb..c94c28cdb8d11 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -22,8 +22,7 @@ use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Curr use pallet_balances::Error as BalancesError; use sp_std::prelude::*; -pub const DEFAULT_SYSTEM_FEATURES: SystemFeature = SystemFeature::NoDeposit; -pub const DEFAULT_USER_FEATURES: UserFeature = UserFeature::Administration; +// pub const DEFAULT_COLLECTION_FEATURES: CollectionFeature = BitFlags::EMPTY; fn items() -> Vec<(u64, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); @@ -112,7 +111,7 @@ fn basic_setup_works() { assert_eq!(items(), vec![]); }); } - +/* #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { @@ -120,7 +119,7 @@ fn basic_minting_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_eq!(collections(), vec![(1, 0)]); @@ -131,7 +130,7 @@ fn basic_minting_should_work() { Origin::root(), 1, 2, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_eq!(collections(), vec![(1, 0), (2, 1)]); @@ -140,7 +139,7 @@ fn basic_minting_should_work() { }); } -#[test] +/*#[test] fn collection_locking_should_work() { new_test_ext().execute_with(|| { let user_id = 1; @@ -149,25 +148,21 @@ fn collection_locking_should_work() { Origin::root(), 0, user_id, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); let id = get_id_from_event().unwrap(); - let new_config = UserFeatures::new(UserFeature::IsLocked.into()); + let new_config = CollectionFeatures::new(CollectionFeature::IsLocked.into()); assert_ok!(Nfts::change_collection_config(Origin::signed(user_id), id, new_config)); let collection_config = CollectionConfigs::::get(id); - let expected_config = CollectionConfig { - system_features: SystemFeatures::new(DEFAULT_SYSTEM_FEATURES.into()), - user_features: new_config, - }; - + let expected_config = CollectionConfig(new_config); assert_eq!(Some(expected_config), collection_config); }); -} +}*/ #[test] fn lifecycle_should_work() { @@ -176,7 +171,7 @@ fn lifecycle_should_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), 1 )); assert_eq!(Balances::reserved_balance(&1), 2); @@ -224,7 +219,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), 1 )); @@ -241,7 +236,7 @@ fn mint_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -258,7 +253,7 @@ fn transfer_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -276,7 +271,7 @@ fn transfer_should_work() { Origin::root(), 1, 1, - UserFeatures::new(UserFeature::NonTransferableItems.into()), + CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), true )); @@ -296,7 +291,7 @@ fn freezing_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -319,7 +314,7 @@ fn origin_guards_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -349,7 +344,7 @@ fn transfer_owner_should_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), 1 )); assert_eq!(collections(), vec![(1, 0)]); @@ -394,7 +389,7 @@ fn set_team_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); @@ -419,7 +414,7 @@ fn set_collection_metadata_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); // Cannot add metadata to unowned item @@ -482,7 +477,7 @@ fn set_item_metadata_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -534,7 +529,7 @@ fn set_item_metadata_should_work() { assert_ok!(Nfts::change_collection_config( Origin::signed(1), 0, - UserFeatures::new(UserFeature::IsLocked.into()) + CollectionFeatures::new(CollectionFeature::IsLocked.into()) )); assert_noop!( Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), @@ -552,7 +547,7 @@ fn set_attribute_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); @@ -603,7 +598,7 @@ fn set_attribute_should_respect_freeze() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); @@ -641,7 +636,7 @@ fn force_item_status_should_work() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -681,7 +676,7 @@ fn burn_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), false )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); @@ -711,7 +706,7 @@ fn approval_lifecycle_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -729,7 +724,7 @@ fn approval_lifecycle_works() { Origin::root(), collection_id, 1, - UserFeatures::new(UserFeature::NonTransferableItems.into()), + CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), true )); @@ -749,7 +744,7 @@ fn cancel_approval_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -802,7 +797,7 @@ fn approving_multiple_accounts_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -827,7 +822,7 @@ fn approvals_limit_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -853,7 +848,7 @@ fn approval_deadline_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -883,7 +878,7 @@ fn cancel_approval_works_with_admin() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -917,7 +912,7 @@ fn cancel_approval_works_with_force() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -945,7 +940,7 @@ fn clear_all_transfer_approvals_works() { Origin::root(), 0, 1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -984,7 +979,7 @@ fn max_supply_should_work() { Origin::root(), collection_id, user_id, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); assert!(!CollectionMaxSupply::::contains_key(collection_id)); @@ -1036,7 +1031,7 @@ fn set_price_should_work() { Origin::root(), collection_id, user_id, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); @@ -1082,7 +1077,7 @@ fn set_price_should_work() { Origin::root(), collection_id, user_id, - UserFeatures::new(UserFeature::NonTransferableItems.into()), + CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), true )); @@ -1117,7 +1112,7 @@ fn buy_item_should_work() { Origin::root(), collection_id, user_1, - UserFeatures::new(DEFAULT_USER_FEATURES.into()), + CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), true )); @@ -1227,7 +1222,7 @@ fn buy_item_should_work() { Origin::root(), collection_id, user_1, - UserFeatures::new(UserFeature::NonTransferableItems.into()), + CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), true )); @@ -1244,27 +1239,29 @@ fn buy_item_should_work() { fn different_user_flags() { new_test_ext().execute_with(|| { // when setting one feature it's required to call .into() on it - let user_features = UserFeatures::new(UserFeature::IsLocked.into()); + let user_features = CollectionFeatures::new(CollectionFeature::IsLocked.into()); assert_ok!(Nfts::force_create(Origin::root(), 0, 1, user_features, false)); let collection_config = CollectionConfigs::::get(0); let stored_user_features = collection_config.unwrap().user_features.get(); - assert!(stored_user_features.contains(UserFeature::IsLocked)); - assert!(!stored_user_features.contains(UserFeature::Administration)); + assert!(stored_user_features.contains(CollectionFeature::IsLocked)); + assert!(!stored_user_features.contains(CollectionFeature::MetadataIsLocked)); // no need to call .into() for multiple features - let user_features = UserFeatures::new(UserFeature::Administration | UserFeature::IsLocked); + let user_features = CollectionFeatures::new( + CollectionFeature::MetadataIsLocked | CollectionFeature::IsLocked, + ); assert_ok!(Nfts::force_create(Origin::root(), 1, 1, user_features, false)); let collection_config = CollectionConfigs::::get(1); let stored_user_features = collection_config.unwrap().user_features.get(); - assert!(stored_user_features.contains(UserFeature::IsLocked)); - assert!(stored_user_features.contains(UserFeature::Administration)); + assert!(stored_user_features.contains(CollectionFeature::IsLocked)); + assert!(stored_user_features.contains(CollectionFeature::MetadataIsLocked)); assert_ok!(Nfts::force_create( Origin::root(), 2, 1, - UserFeatures::new(BitFlags::EMPTY), + CollectionFeatures::new(BitFlags::EMPTY), false )); @@ -1273,8 +1270,9 @@ fn different_user_flags() { Origin::root(), 3, 1, - UserFeatures::new(UserFeature::empty()), + CollectionFeatures::new(CollectionFeature::empty()), false )); }); } +*/ diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 3c0b0c3791277..3fcdb9d8b807f 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -18,6 +18,7 @@ //! Various basic types for use in the Nfts pallet. use super::*; +use codec::EncodeLike; use enumflags2::{bitflags, BitFlags}; use frame_support::{ pallet_prelude::{BoundedVec, MaxEncodedLen}, @@ -133,119 +134,82 @@ pub struct ItemMetadata> { #[bitflags] #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] -pub enum UserFeature { - Administration, +pub enum CollectionFeature { IsLocked, - NonTransferableItems, - MetadataIsLocked, - AttributesAreLocked, + NonTransferableItems, // LockedItems + MetadataIsLocked, // LockedMetadata + AttributesAreLocked, // LockedAttributes } -/// Wrapper type for `BitFlags` that implements `Codec`. +// TODO: rename CollectionFeatures => CollectionConfig +// TODO: do we need ::new()? +// TODO: can we create an alias for BitFlags ? + +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct UserFeatures(pub BitFlags); +pub struct CollectionFeatures(BitFlags); -impl UserFeatures { - pub fn new(input: BitFlags) -> Self { - UserFeatures(input) +impl CollectionFeatures { + pub fn new(input: BitFlags) -> Self { + CollectionFeatures(input) } - pub fn get(&self) -> BitFlags { - self.0.clone() + // TODO: what if we rename to fields()? + pub fn get(&self) -> BitFlags { + self.0.clone() // TODO: do we need that .clone()? } } -impl MaxEncodedLen for UserFeatures { +impl MaxEncodedLen for CollectionFeatures { fn max_encoded_len() -> usize { u64::max_encoded_len() } } -impl Encode for UserFeatures { +impl Encode for CollectionFeatures { fn using_encoded R>(&self, f: F) -> R { self.0.bits().using_encoded(f) } } -impl Decode for UserFeatures { +impl EncodeLike for CollectionFeatures {} +impl Decode for CollectionFeatures { fn decode(input: &mut I) -> sp_std::result::Result { let field = u64::decode(input)?; - Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) + Ok(Self( + >::from_bits(field as u64).map_err(|_| "invalid value")?, + )) } } -impl TypeInfo for UserFeatures { + +impl TypeInfo for CollectionFeatures { type Identity = Self; fn type_info() -> Type { Type::builder() .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("UserFeature"))) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("CollectionFeature"))) } } +// TODO: remove +// #[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +// pub struct CollectionConfig(pub CollectionFeatures); + // Support for up to 64 system-enabled features on a collection. #[bitflags] #[repr(u64)] -#[derive(Copy, Clone, RuntimeDebug, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq)] pub enum SystemFeature { - NoDeposit, NoCollectionDeposit, NoItemDeposit, - NoDataDeposit, + NoMetadataDeposit, + NoAttributesDeposit, NoTrading, NoAttributes, NoApprovals, NoSwaps, - NoClaims, -} - -/// Wrapper type for `BitFlags` that implements `Codec`. -#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] -pub struct SystemFeatures(pub BitFlags); - -impl SystemFeatures { - pub fn new(input: BitFlags) -> Self { - SystemFeatures(input) - } - - pub fn get(&self) -> BitFlags { - self.0.clone() - } -} - -impl MaxEncodedLen for SystemFeatures { - fn max_encoded_len() -> usize { - u64::max_encoded_len() - } -} - -impl Eq for SystemFeatures {} -impl Encode for SystemFeatures { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl Decode for SystemFeatures { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u64::decode(input)?; - Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) - } -} -impl TypeInfo for SystemFeatures { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("SystemFeature"))) - } + NoPublicMints, } -// TODO: Implement Default - -#[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] -pub struct CollectionConfig { - pub system_features: SystemFeatures, - pub user_features: UserFeatures, -} +pub(super) type SystemFeatures = BitFlags; diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index d043a87ce7c10..7e5b02d2a5052 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -122,12 +122,13 @@ pub trait InspectEnumerable: Inspect { } /// Trait for providing the ability to create collections of nonfungible items. -pub trait Create: Inspect { +pub trait Create: Inspect { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( collection: &Self::CollectionId, who: &AccountId, admin: &AccountId, + config: &CollectionFeatures, ) -> DispatchResult; } From 6db840a75da1b7fdff7321b8e52de0a38fe5fa98 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 12:13:14 +0300 Subject: [PATCH 03/94] Refactor --- frame/nfts/src/functions.rs | 35 ++--- frame/nfts/src/impl_nonfungibles.rs | 6 +- frame/nfts/src/lib.rs | 27 ++-- frame/nfts/src/tests.rs | 135 +++++++----------- frame/nfts/src/types.rs | 47 +++--- .../support/src/traits/tokens/nonfungibles.rs | 4 +- 6 files changed, 110 insertions(+), 144 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index e649077d0c424..943cd0a12566d 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -18,7 +18,6 @@ //! Various pieces of common functionality. use super::*; -use enumflags2::BitFlags; use frame_support::{ ensure, traits::{ExistenceRequirement, Get}, @@ -41,10 +40,11 @@ impl, I: 'static> Pallet { ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); let config = - CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.get(); // TODO: can we omit the type? + CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; + + let settings = config.values(); ensure!( - !user_features.contains(CollectionFeature::NonTransferableItems), + !settings.contains(CollectionSetting::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -78,7 +78,7 @@ impl, I: 'static> Pallet { pub fn do_create_collection( collection: T::CollectionId, owner: T::AccountId, - collection_config: CollectionFeatures, + config: CollectionConfig, admin: T::AccountId, deposit: DepositBalanceOf, free_holding: bool, @@ -104,8 +104,7 @@ impl, I: 'static> Pallet { }, ); - // let config/*: BitFlags*/ = CollectionFeatures::new(collection_config); - CollectionConfigs::::insert(&collection, collection_config); + CollectionConfigOf::::insert(&collection, config); CollectionAccount::::insert(&owner, &collection, ()); Self::deposit_event(event); @@ -243,10 +242,11 @@ impl, I: 'static> Pallet { ensure!(details.owner == sender, Error::::NoPermission); let config = - CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.get(); + CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; + + let settings = config.values(); ensure!( - !user_features.contains(CollectionFeature::NonTransferableItems), + !settings.contains(CollectionSetting::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -276,10 +276,11 @@ impl, I: 'static> Pallet { ensure!(details.owner != buyer, Error::::NoPermission); let config = - CollectionConfigs::::get(collection).ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.get(); + CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; + + let settings = config.values(); ensure!( - !user_features.contains(CollectionFeature::NonTransferableItems), + !settings.contains(CollectionSetting::NonTransferableItems), Error::::NotForSale ); @@ -318,18 +319,18 @@ impl, I: 'static> Pallet { id: T::CollectionId, caller: T::AccountId, current_config: CollectionConfig, - new_config: CollectionFeatures, + new_config: CollectionConfig, ) -> DispatchResult { let collection = Collection::::get(id).ok_or(Error::::UnknownCollection)?; ensure!(collection.owner == caller, Error::::NoPermission); - let user_features: BitFlags = current_config.user_features.get(); + let settings = current_config.values(); - if user_features.contains(UserFeature::IsLocked) { + if settings.contains(CollectionSetting::IsLocked) { return Err(Error::::CollectionIsLocked.into()) } - CollectionConfigs::::try_mutate(id, |maybe_config| { + CollectionConfigOf::::try_mutate(id, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; config.user_features = new_config; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 4dced442839a1..3639032478dc1 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -85,7 +85,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } -impl, I: 'static> Create<::AccountId, BitFlags> +impl, I: 'static> Create<::AccountId, CollectionSettings> for Pallet { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. @@ -93,12 +93,12 @@ impl, I: 'static> Create<::AccountId, BitFlags, + config: &CollectionSettings, ) -> DispatchResult { Self::do_create_collection( *collection, who.clone(), - CollectionFeatures::new(*config), + CollectionConfig(*config), admin.clone(), T::CollectionDeposit::get(), false, diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 2ac14d6a0ee20..e1c41779aa2f3 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -42,7 +42,6 @@ mod types; pub mod weights; use codec::{Decode, Encode}; -use enumflags2::BitFlags; use frame_support::{ traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, @@ -283,9 +282,8 @@ pub mod pallet { /// Maps a unique collection id to it's config. #[pallet::storage] - // TODO: rename to CollectionConfigOf ? - pub(super) type CollectionConfigs, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionFeatures>; + pub(super) type CollectionConfigOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -484,7 +482,7 @@ pub mod pallet { pub fn create( origin: OriginFor, collection: T::CollectionId, - config: CollectionFeatures, + config: CollectionConfig, admin: AccountIdLookupOf, ) -> DispatchResult { let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; @@ -523,7 +521,7 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, - config: CollectionFeatures, + config: CollectionConfig, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -544,11 +542,11 @@ pub mod pallet { pub fn change_collection_config( origin: OriginFor, id: T::CollectionId, - new_config: CollectionFeatures, + new_config: CollectionConfig, ) -> DispatchResult { let sender = ensure_signed(origin)?; let current_config = - CollectionConfigs::::get(id).ok_or(Error::::UnknownCollection)?; + CollectionConfigOf::::get(id).ok_or(Error::::UnknownCollection)?; Self::do_change_collection_config(id, sender, current_config, new_config)?; Ok(()) }*/ @@ -986,11 +984,12 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let config = CollectionConfigs::::get(collection) + let config = CollectionConfigOf::::get(collection) .ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.get(); + + let settings = config.values(); ensure!( - !user_features.contains(CollectionFeature::NonTransferableItems), + !settings.contains(CollectionSetting::NonTransferableItems), Error::::ItemsNotTransferable ); @@ -1428,12 +1427,12 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let config = CollectionConfigs::::get(collection) + let config = CollectionConfigOf::::get(collection) .ok_or(Error::::UnknownCollection)?; - let user_features: BitFlags = config.get(); + let settings = config.values(); ensure!( - !user_features.contains(CollectionFeature::IsLocked), + !settings.contains(CollectionSetting::IsLocked), Error::::CollectionIsLocked ); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index c94c28cdb8d11..47a1eece5c482 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -22,8 +22,6 @@ use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Curr use pallet_balances::Error as BalancesError; use sp_std::prelude::*; -// pub const DEFAULT_COLLECTION_FEATURES: CollectionFeature = BitFlags::EMPTY; - fn items() -> Vec<(u64, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); r.sort(); @@ -111,28 +109,16 @@ fn basic_setup_works() { assert_eq!(items(), vec![]); }); } -/* + #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create( - Origin::root(), - 1, - 2, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 1, 2, CollectionConfig::empty(), true)); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Nfts::mint(Origin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); @@ -148,7 +134,7 @@ fn collection_locking_should_work() { Origin::root(), 0, user_id, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); @@ -157,13 +143,14 @@ fn collection_locking_should_work() { assert_ok!(Nfts::change_collection_config(Origin::signed(user_id), id, new_config)); - let collection_config = CollectionConfigs::::get(id); + let collection_config = CollectionConfigOf::::get(id); let expected_config = CollectionConfig(new_config); assert_eq!(Some(expected_config), collection_config); }); }*/ +/* #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { @@ -171,7 +158,7 @@ fn lifecycle_should_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), 1 )); assert_eq!(Balances::reserved_balance(&1), 2); @@ -219,7 +206,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), 1 )); @@ -236,7 +223,7 @@ fn mint_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -253,7 +240,7 @@ fn transfer_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -291,7 +278,7 @@ fn freezing_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -314,7 +301,7 @@ fn origin_guards_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -344,7 +331,7 @@ fn transfer_owner_should_work() { assert_ok!(Nfts::create( Origin::signed(1), 0, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), 1 )); assert_eq!(collections(), vec![(1, 0)]); @@ -389,7 +376,7 @@ fn set_team_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); @@ -414,7 +401,7 @@ fn set_collection_metadata_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); // Cannot add metadata to unowned item @@ -477,7 +464,7 @@ fn set_item_metadata_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -547,7 +534,7 @@ fn set_attribute_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); @@ -598,7 +585,7 @@ fn set_attribute_should_respect_freeze() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); @@ -636,7 +623,7 @@ fn force_item_status_should_work() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -676,7 +663,7 @@ fn burn_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), false )); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); @@ -706,7 +693,7 @@ fn approval_lifecycle_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -744,7 +731,7 @@ fn cancel_approval_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -797,7 +784,7 @@ fn approving_multiple_accounts_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -822,7 +809,7 @@ fn approvals_limit_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -848,7 +835,7 @@ fn approval_deadline_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -878,7 +865,7 @@ fn cancel_approval_works_with_admin() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -912,7 +899,7 @@ fn cancel_approval_works_with_force() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -940,7 +927,7 @@ fn clear_all_transfer_approvals_works() { Origin::root(), 0, 1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); @@ -979,7 +966,7 @@ fn max_supply_should_work() { Origin::root(), collection_id, user_id, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); assert!(!CollectionMaxSupply::::contains_key(collection_id)); @@ -1031,7 +1018,7 @@ fn set_price_should_work() { Origin::root(), collection_id, user_id, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); @@ -1112,7 +1099,7 @@ fn buy_item_should_work() { Origin::root(), collection_id, user_1, - CollectionFeatures::new(DEFAULT_COLLECTION_FEATURES), + CollectionConfig::empty(), true )); @@ -1234,45 +1221,29 @@ fn buy_item_should_work() { ); }); } - +*/ #[test] -fn different_user_flags() { +fn various_collection_settings() { new_test_ext().execute_with(|| { - // when setting one feature it's required to call .into() on it - let user_features = CollectionFeatures::new(CollectionFeature::IsLocked.into()); - assert_ok!(Nfts::force_create(Origin::root(), 0, 1, user_features, false)); - - let collection_config = CollectionConfigs::::get(0); - let stored_user_features = collection_config.unwrap().user_features.get(); - assert!(stored_user_features.contains(CollectionFeature::IsLocked)); - assert!(!stored_user_features.contains(CollectionFeature::MetadataIsLocked)); - - // no need to call .into() for multiple features - let user_features = CollectionFeatures::new( - CollectionFeature::MetadataIsLocked | CollectionFeature::IsLocked, - ); - assert_ok!(Nfts::force_create(Origin::root(), 1, 1, user_features, false)); - let collection_config = CollectionConfigs::::get(1); - let stored_user_features = collection_config.unwrap().user_features.get(); - assert!(stored_user_features.contains(CollectionFeature::IsLocked)); - assert!(stored_user_features.contains(CollectionFeature::MetadataIsLocked)); - - assert_ok!(Nfts::force_create( - Origin::root(), - 2, - 1, - CollectionFeatures::new(BitFlags::EMPTY), - false - )); - - use enumflags2::BitFlag; - assert_ok!(Nfts::force_create( - Origin::root(), - 3, - 1, - CollectionFeatures::new(CollectionFeature::empty()), - false - )); + // when we set only one value it's required to call .into() on it + let config = CollectionConfig(CollectionSetting::IsLocked.into()); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, config, false)); + + let config = CollectionConfigOf::::get(0).unwrap(); + let stored_settings = config.values(); + assert!(stored_settings.contains(CollectionSetting::IsLocked)); + assert!(!stored_settings.contains(CollectionSetting::MetadataIsLocked)); + + // no need to call .into() for multiple values + let settings = + CollectionConfig(CollectionSetting::MetadataIsLocked | CollectionSetting::IsLocked); + assert_ok!(Nfts::force_create(Origin::root(), 1, 1, settings, false)); + + let config = CollectionConfigOf::::get(1).unwrap(); + let stored_settings = config.values(); + assert!(stored_settings.contains(CollectionSetting::IsLocked)); + assert!(stored_settings.contains(CollectionSetting::MetadataIsLocked)); + + assert_ok!(Nfts::force_create(Origin::root(), 2, 1, CollectionConfig::empty(), false)); }); } -*/ diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 3fcdb9d8b807f..68b471cf565a4 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -134,68 +134,63 @@ pub struct ItemMetadata> { #[bitflags] #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] -pub enum CollectionFeature { +pub enum CollectionSetting { IsLocked, NonTransferableItems, // LockedItems MetadataIsLocked, // LockedMetadata AttributesAreLocked, // LockedAttributes } -// TODO: rename CollectionFeatures => CollectionConfig -// TODO: do we need ::new()? -// TODO: can we create an alias for BitFlags ? +pub(super) type CollectionSettings = BitFlags; -/// Wrapper type for `BitFlags` that implements `Codec`. +/// Wrapper type for `CollectionSettings` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct CollectionFeatures(BitFlags); +pub struct CollectionConfig(pub CollectionSettings); -impl CollectionFeatures { - pub fn new(input: BitFlags) -> Self { - CollectionFeatures(input) +impl CollectionConfig { + pub fn new(input: CollectionSettings) -> Self { + CollectionConfig(input) } - // TODO: what if we rename to fields()? - pub fn get(&self) -> BitFlags { - self.0.clone() // TODO: do we need that .clone()? + pub fn empty() -> Self { + CollectionConfig(BitFlags::EMPTY) + } + + pub fn values(&self) -> CollectionSettings { + self.0 } } -impl MaxEncodedLen for CollectionFeatures { +impl MaxEncodedLen for CollectionConfig { fn max_encoded_len() -> usize { u64::max_encoded_len() } } -impl Encode for CollectionFeatures { +impl Encode for CollectionConfig { fn using_encoded R>(&self, f: F) -> R { self.0.bits().using_encoded(f) } } -impl EncodeLike for CollectionFeatures {} -impl Decode for CollectionFeatures { +impl EncodeLike for CollectionConfig {} +impl Decode for CollectionConfig { fn decode(input: &mut I) -> sp_std::result::Result { let field = u64::decode(input)?; - Ok(Self( - >::from_bits(field as u64).map_err(|_| "invalid value")?, - )) + Ok(Self(CollectionSettings::from_bits(field as u64).map_err(|_| "invalid value")?)) } } -impl TypeInfo for CollectionFeatures { +impl TypeInfo for CollectionConfig { type Identity = Self; fn type_info() -> Type { Type::builder() .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("CollectionFeature"))) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("CollectionSetting"))) } } -// TODO: remove -// #[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] -// pub struct CollectionConfig(pub CollectionFeatures); - // Support for up to 64 system-enabled features on a collection. #[bitflags] #[repr(u64)] diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index 7e5b02d2a5052..edad84e7a2179 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -122,13 +122,13 @@ pub trait InspectEnumerable: Inspect { } /// Trait for providing the ability to create collections of nonfungible items. -pub trait Create: Inspect { +pub trait Create: Inspect { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( collection: &Self::CollectionId, who: &AccountId, admin: &AccountId, - config: &CollectionFeatures, + config: &CollectionConfig, ) -> DispatchResult; } From fa3a2a7c299c982804b7c89009f9a71ef2c20006 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 12:14:53 +0300 Subject: [PATCH 04/94] Remove redundant new() method --- frame/nfts/src/types.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 68b471cf565a4..54b4b83040314 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -148,10 +148,6 @@ pub(super) type CollectionSettings = BitFlags; pub struct CollectionConfig(pub CollectionSettings); impl CollectionConfig { - pub fn new(input: CollectionSettings) -> Self { - CollectionConfig(input) - } - pub fn empty() -> Self { CollectionConfig(BitFlags::EMPTY) } From 472a7535ddb3b2b24bcc7c14fa8c99342977075d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 13:06:33 +0300 Subject: [PATCH 05/94] Rename settings --- frame/nfts/src/lib.rs | 2 +- frame/nfts/src/tests.rs | 15 ++++++++------- frame/nfts/src/types.rs | 7 +++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index e1c41779aa2f3..90d82fa004827 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1432,7 +1432,7 @@ pub mod pallet { let settings = config.values(); ensure!( - !settings.contains(CollectionSetting::IsLocked), + !settings.contains(CollectionSetting::LockedMetadata), Error::::CollectionIsLocked ); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 47a1eece5c482..cf6b72bb381ed 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1226,23 +1226,24 @@ fn buy_item_should_work() { fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it - let config = CollectionConfig(CollectionSetting::IsLocked.into()); + let config = CollectionConfig(CollectionSetting::NonTransferableItems.into()); assert_ok!(Nfts::force_create(Origin::root(), 0, 1, config, false)); let config = CollectionConfigOf::::get(0).unwrap(); let stored_settings = config.values(); - assert!(stored_settings.contains(CollectionSetting::IsLocked)); - assert!(!stored_settings.contains(CollectionSetting::MetadataIsLocked)); + assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); + assert!(!stored_settings.contains(CollectionSetting::LockedMetadata)); // no need to call .into() for multiple values - let settings = - CollectionConfig(CollectionSetting::MetadataIsLocked | CollectionSetting::IsLocked); + let settings = CollectionConfig( + CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, + ); assert_ok!(Nfts::force_create(Origin::root(), 1, 1, settings, false)); let config = CollectionConfigOf::::get(1).unwrap(); let stored_settings = config.values(); - assert!(stored_settings.contains(CollectionSetting::IsLocked)); - assert!(stored_settings.contains(CollectionSetting::MetadataIsLocked)); + assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); + assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); assert_ok!(Nfts::force_create(Origin::root(), 2, 1, CollectionConfig::empty(), false)); }); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 54b4b83040314..3f322c916e990 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -135,10 +135,9 @@ pub struct ItemMetadata> { #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum CollectionSetting { - IsLocked, - NonTransferableItems, // LockedItems - MetadataIsLocked, // LockedMetadata - AttributesAreLocked, // LockedAttributes + NonTransferableItems, + LockedMetadata, + LockedAttributes, } pub(super) type CollectionSettings = BitFlags; From 2611dd41cd8f2d8e4f46b06a32d3810e27a68f01 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 13:22:26 +0300 Subject: [PATCH 06/94] Enable tests --- frame/nfts/src/tests.rs | 215 +++++++--------------------------------- 1 file changed, 35 insertions(+), 180 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index cf6b72bb381ed..5161e2c3d2114 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -88,21 +88,6 @@ fn events() -> Vec> { result } -fn get_id_from_event() -> Result<::CollectionId, &'static str> { - let last_event = System::events().pop(); - if let Some(e) = last_event.clone() { - match e.event { - mock::RuntimeEvent::Nfts(inner_event) => match inner_event { - Event::ForceCreated { collection, .. } => return Ok(collection), - _ => {}, - }, - _ => {}, - } - } - - Err("bad event") -} - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -129,38 +114,32 @@ fn basic_minting_should_work() { fn collection_locking_should_work() { new_test_ext().execute_with(|| { let user_id = 1; + let collection_id = 0; assert_ok!(Nfts::force_create( Origin::root(), - 0, + collection_id, user_id, CollectionConfig::empty(), true )); - let id = get_id_from_event().unwrap(); - let new_config = CollectionFeatures::new(CollectionFeature::IsLocked.into()); + let new_config = CollectionConfig(CollectionSetting::IsLocked.into()); - assert_ok!(Nfts::change_collection_config(Origin::signed(user_id), id, new_config)); + assert_ok!(Nfts::change_collection_config(Origin::signed(user_id), collection_id, new_config)); - let collection_config = CollectionConfigOf::::get(id); + let collection_config = CollectionConfigOf::::get(collection_id); let expected_config = CollectionConfig(new_config); assert_eq!(Some(expected_config), collection_config); }); }*/ -/* #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create( - Origin::signed(1), - 0, - CollectionConfig::empty(), - 1 - )); + assert_ok!(Nfts::create(Origin::signed(1), 0, CollectionConfig::empty(), 1)); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); @@ -203,12 +182,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create( - Origin::signed(1), - 0, - CollectionConfig::empty(), - 1 - )); + assert_ok!(Nfts::create(Origin::signed(1), 0, CollectionConfig::empty(), 1)); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); @@ -219,13 +193,7 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -236,13 +204,7 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(Origin::signed(2), 0, 42, 3)); @@ -258,7 +220,7 @@ fn transfer_should_work() { Origin::root(), 1, 1, - CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), + CollectionConfig(CollectionSetting::NonTransferableItems.into()), true )); @@ -274,13 +236,7 @@ fn transfer_should_work() { #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(Origin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); @@ -297,13 +253,7 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -328,12 +278,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create( - Origin::signed(1), - 0, - CollectionConfig::empty(), - 1 - )); + assert_ok!(Nfts::create(Origin::signed(1), 0, CollectionConfig::empty(), 1)); assert_eq!(collections(), vec![(1, 0)]); assert_noop!(Nfts::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted); assert_ok!(Nfts::set_accept_ownership(Origin::signed(2), Some(0))); @@ -372,13 +317,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_ok!(Nfts::mint(Origin::signed(2), 0, 42, 2)); @@ -397,13 +336,7 @@ fn set_collection_metadata_should_work() { Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), @@ -460,13 +393,7 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( @@ -513,15 +440,15 @@ fn set_item_metadata_should_work() { assert!(!ItemMetadataOf::::contains_key(0, 42)); // collection's metadata can't be changed after the collection gets locked - assert_ok!(Nfts::change_collection_config( + /*assert_ok!(Nfts::change_collection_config( Origin::signed(1), 0, - CollectionFeatures::new(CollectionFeature::IsLocked.into()) + CollectionConfig(CollectionSetting::LockedMetadata.into()) )); assert_noop!( Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::CollectionIsLocked - ); + );*/ }); } @@ -530,13 +457,7 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -581,13 +502,7 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -619,13 +534,7 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 69, 2)); assert_ok!(Nfts::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); @@ -659,13 +568,7 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), false)); assert_ok!(Nfts::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -689,13 +592,7 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(Origin::signed(3), 0, 42, 4)); @@ -711,7 +608,7 @@ fn approval_lifecycle_works() { Origin::root(), collection_id, 1, - CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), + CollectionConfig(CollectionSetting::NonTransferableItems.into()), true )); @@ -727,13 +624,7 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -780,13 +671,7 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); let current_block = 1; @@ -805,13 +690,7 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); for i in 3..13 { @@ -831,13 +710,7 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. @@ -861,13 +734,7 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -895,13 +762,7 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -923,13 +784,7 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create( - Origin::root(), - 0, - 1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(Origin::root(), 0, 1, CollectionConfig::empty(), true)); assert_ok!(Nfts::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(Origin::signed(2), 0, 42, 3, None)); @@ -1064,7 +919,7 @@ fn set_price_should_work() { Origin::root(), collection_id, user_id, - CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), + CollectionConfig(CollectionSetting::NonTransferableItems.into()), true )); @@ -1209,7 +1064,7 @@ fn buy_item_should_work() { Origin::root(), collection_id, user_1, - CollectionFeatures::new(CollectionFeature::NonTransferableItems.into()), + CollectionConfig(CollectionSetting::NonTransferableItems.into()), true )); @@ -1221,7 +1076,7 @@ fn buy_item_should_work() { ); }); } -*/ + #[test] fn various_collection_settings() { new_test_ext().execute_with(|| { From 46a6b7edb76cbaa47d03d4a43ad3205d439b3121 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:01:11 +0300 Subject: [PATCH 07/94] Chore --- frame/nfts/src/mock.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 7230bdb76c2f1..3e9e8ae631836 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -20,7 +20,7 @@ use super::*; use crate as pallet_nfts; -use enumflags2::BitFlags; +use enumflags2::BitFlag; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, @@ -86,7 +86,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub FeatureFlags: SystemFeatures = BitFlags::EMPTY; + pub FeatureFlags: SystemFeatures = SystemFeature::empty(); } impl Config for Test { From 5ff70486793631c21b60498d6ed74dfc73c8140f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:07:45 +0300 Subject: [PATCH 08/94] Change params order --- frame/nfts/src/functions.rs | 2 +- frame/nfts/src/impl_nonfungibles.rs | 2 +- frame/nfts/src/lib.rs | 6 +++--- frame/nfts/src/tests.rs | 9 ++++----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 9f92971ed1cd8..8491ca08a6f67 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -78,8 +78,8 @@ impl, I: 'static> Pallet { pub fn do_create_collection( collection: T::CollectionId, owner: T::AccountId, - config: CollectionConfig, admin: T::AccountId, + config: CollectionConfig, deposit: DepositBalanceOf, free_holding: bool, event: Event, diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 3639032478dc1..a4b5d3f8d9800 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -98,8 +98,8 @@ impl, I: 'static> Create<::AccountId, Collection Self::do_create_collection( *collection, who.clone(), - CollectionConfig(*config), admin.clone(), + CollectionConfig(*config), T::CollectionDeposit::get(), false, Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 293b4ce7f1473..0bdd93c37d670 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -524,8 +524,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - config: CollectionConfig, admin: AccountIdLookupOf, + config: CollectionConfig, ) -> DispatchResult { let collection = NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); @@ -536,8 +536,8 @@ pub mod pallet { Self::do_create_collection( collection, owner.clone(), - config, admin.clone(), + config, T::CollectionDeposit::get(), false, Event::Created { collection, creator: owner, owner: admin }, @@ -576,8 +576,8 @@ pub mod pallet { Self::do_create_collection( collection, owner.clone(), - config, owner.clone(), + config, Zero::zero(), free_holding, Event::ForceCreated { collection, owner }, diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 324de7297ed5c..1ac04807a40d2 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -117,8 +117,7 @@ fn collection_locking_should_work() { let collection_id = 0; assert_ok!(Nfts::force_create( - Origin::root(), - collection_id, + RuntimeOrigin::root(), user_id, CollectionConfig::empty(), true @@ -139,7 +138,7 @@ fn collection_locking_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), CollectionConfig::empty(), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0], false)); @@ -182,7 +181,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), CollectionConfig::empty(), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); @@ -286,7 +285,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), CollectionConfig::empty(), 1)); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), From f7ddfed93041e296beb941db5e9b781002617694 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:11:38 +0300 Subject: [PATCH 09/94] Delete the config on collection removal --- frame/nfts/src/functions.rs | 1 + frame/nfts/src/tests.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 8491ca08a6f67..c773ba668c8bf 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -146,6 +146,7 @@ impl, I: 'static> Pallet { CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); CollectionMaxSupply::::remove(&collection); + CollectionConfigOf::::remove(&collection); Self::deposit_event(Event::Destroyed { collection }); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 1ac04807a40d2..8db9784dea61e 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -167,6 +167,7 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Collection::::contains_key(0)); + assert!(!CollectionConfigOf::::contains_key(0)); assert!(!Item::::contains_key(0, 42)); assert!(!Item::::contains_key(0, 69)); assert!(!CollectionMetadataOf::::contains_key(0)); From d0d1d60867d4ced78c47a50c4e07f977b5adf366 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:22:03 +0300 Subject: [PATCH 10/94] Chore --- frame/nfts/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 8db9784dea61e..7d26b5a130cd6 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1189,10 +1189,10 @@ fn various_collection_settings() { assert!(!stored_settings.contains(CollectionSetting::LockedMetadata)); // no need to call .into() for multiple values - let settings = CollectionConfig( + let config = CollectionConfig( CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, settings, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); let config = CollectionConfigOf::::get(1).unwrap(); let stored_settings = config.values(); From 7d4b31f0cb4252c0ecd4423c014b3d63b6a56f50 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:22:28 +0300 Subject: [PATCH 11/94] Remove redundant system features --- frame/nfts/src/types.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 8e7089ffa6091..6e9f2ed98b848 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -210,10 +210,6 @@ impl TypeInfo for CollectionConfig { #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq)] pub enum SystemFeature { - NoCollectionDeposit, - NoItemDeposit, - NoMetadataDeposit, - NoAttributesDeposit, NoTrading, NoAttributes, NoApprovals, From dd846370a3cd65ca916c311c6e5df8f73f35e6ef Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sat, 24 Sep 2022 14:35:48 +0300 Subject: [PATCH 12/94] Rename force_item_status to force_collection_status --- frame/nfts/src/benchmarking.rs | 6 ++-- frame/nfts/src/lib.rs | 53 +++++++++++++++++----------------- frame/nfts/src/tests.rs | 13 +++++++-- frame/nfts/src/weights.rs | 6 ++-- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 00527abc99e02..f3fd64a5457a0 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -204,7 +204,7 @@ benchmarks_instance_pallet! { let i in 0 .. 5_000; let (collection, caller, caller_lookup) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); - Nfts::::force_item_status( + Nfts::::force_collection_status( SystemOrigin::Root.into(), collection, caller_lookup.clone(), @@ -283,10 +283,10 @@ benchmarks_instance_pallet! { }.into()); } - force_item_status { + force_collection_status { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_item_status { + let call = Call::::force_collection_status { collection, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 0bdd93c37d670..3ca0814efc4c0 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -376,7 +376,7 @@ pub mod pallet { /// All approvals of an item got cancelled. AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// A `collection` has had its attributes changed by the `Force` origin. - ItemStatusChanged { collection: T::CollectionId }, + CollectionStatusChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. CollectionMetadataSet { collection: T::CollectionId, @@ -1175,26 +1175,26 @@ pub mod pallet { Ok(()) } - /// Alter the attributes of a given item. + /// Alter the attributes of a given collection. /// /// Origin must be `ForceOrigin`. /// - /// - `collection`: The identifier of the item. - /// - `owner`: The new Owner of this item. - /// - `issuer`: The new Issuer of this item. - /// - `admin`: The new Admin of this item. - /// - `freezer`: The new Freezer of this item. - /// - `free_holding`: Whether a deposit is taken for holding an item of this collection. + /// - `collection`: The identifier of the collection. + /// - `owner`: The new Owner of this collection. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. + /// - `free_holding`: Whether a deposit is taken for holding an item in this collection. /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin /// instructions. /// - /// Emits `ItemStatusChanged` with the identity of the item. + /// Emits `CollectionStatusChanged` with the identity of the item. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_item_status())] - pub fn force_item_status( + #[pallet::weight(T::WeightInfo::force_collection_status())] + pub fn force_collection_status( origin: OriginFor, - collection: T::CollectionId, + collection_id: T::CollectionId, owner: AccountIdLookupOf, issuer: AccountIdLookupOf, admin: AccountIdLookupOf, @@ -1204,21 +1204,22 @@ pub mod pallet { ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Collection::::try_mutate(collection, |maybe_item| { - let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; - let old_owner = item.owner; + Collection::::try_mutate(collection_id, |maybe_collection| { + let mut collection = + maybe_collection.take().ok_or(Error::::UnknownCollection)?; + let old_owner = collection.owner; let new_owner = T::Lookup::lookup(owner)?; - item.owner = new_owner.clone(); - item.issuer = T::Lookup::lookup(issuer)?; - item.admin = T::Lookup::lookup(admin)?; - item.freezer = T::Lookup::lookup(freezer)?; - item.free_holding = free_holding; - item.is_frozen = is_frozen; - *maybe_item = Some(item); - CollectionAccount::::remove(&old_owner, &collection); - CollectionAccount::::insert(&new_owner, &collection, ()); - - Self::deposit_event(Event::ItemStatusChanged { collection }); + collection.owner = new_owner.clone(); + collection.issuer = T::Lookup::lookup(issuer)?; + collection.admin = T::Lookup::lookup(admin)?; + collection.freezer = T::Lookup::lookup(freezer)?; + collection.free_holding = free_holding; + collection.is_frozen = is_frozen; + *maybe_collection = Some(collection); + CollectionAccount::::remove(&old_owner, &collection_id); + CollectionAccount::::insert(&new_owner, &collection_id, ()); + + Self::deposit_event(Event::CollectionStatusChanged { collection: collection_id }); Ok(()) }) } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 7d26b5a130cd6..aa4c1eb91568e 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -578,7 +578,7 @@ fn set_attribute_should_respect_freeze() { } #[test] -fn force_item_status_should_work() { +fn force_collection_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); @@ -591,7 +591,16 @@ fn force_item_status_should_work() { assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Nfts::force_item_status(RuntimeOrigin::root(), 0, 1, 1, 1, 1, true, false)); + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + true, + false, + )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 1dab0838f32b2..39dd977674952 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -59,7 +59,7 @@ pub trait WeightInfo { fn thaw_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_item_status() -> Weight; + fn force_collection_status() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; fn set_metadata() -> Weight; @@ -200,7 +200,7 @@ impl WeightInfo for SubstrateWeight { } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) - fn force_item_status() -> Weight { + fn force_collection_status() -> Weight { Weight::from_ref_time(25_684_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) @@ -430,7 +430,7 @@ impl WeightInfo for () { } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) - fn force_item_status() -> Weight { + fn force_collection_status() -> Weight { Weight::from_ref_time(25_684_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) From 4920a0c7e139ad1b767ed4a726d6181337d30aaa Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 10:53:02 +0300 Subject: [PATCH 13/94] Update node runtime --- bin/node/runtime/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index eb6941e85215c..4c5a4d748ae3c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -53,6 +53,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_nfts::{SystemFeature, SystemFeatures}; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; @@ -1496,6 +1497,10 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +parameter_types! { + pub FeatureFlags: SystemFeatures = SystemFeature::empty(); +} + impl pallet_nfts::Config for Runtime { type RuntimeEvent = RuntimeEvent; type CollectionId = u32; @@ -1512,6 +1517,7 @@ impl pallet_nfts::Config for Runtime { type ValueLimit = ValueLimit; type ApprovalsLimit = ApprovalsLimit; type MaxTips = MaxTips; + type FeatureFlags = FeatureFlags; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); From 7456e73319906aacc86c4284fea0dfa271468306 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 10:57:16 +0300 Subject: [PATCH 14/94] Chore --- frame/nfts/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 6e9f2ed98b848..fad6b505d940f 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -167,7 +167,7 @@ pub struct CollectionConfig(pub CollectionSettings); impl CollectionConfig { pub fn empty() -> Self { - CollectionConfig(BitFlags::EMPTY) + Self(BitFlags::EMPTY) } pub fn values(&self) -> CollectionSettings { @@ -217,4 +217,4 @@ pub enum SystemFeature { NoPublicMints, } -pub(super) type SystemFeatures = BitFlags; +pub type SystemFeatures = BitFlags; From 9ff45b8836fcc3f73661719f5d3ae0f498ab7dba Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 10:59:03 +0300 Subject: [PATCH 15/94] Remove thaw_collection --- frame/nfts/src/benchmarking.rs | 11 +---------- frame/nfts/src/lib.rs | 29 ----------------------------- frame/nfts/src/tests.rs | 15 +++++++++++---- frame/nfts/src/weights.rs | 13 ------------- 4 files changed, 12 insertions(+), 56 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index f3fd64a5457a0..0a60af3de1230 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -212,7 +212,7 @@ benchmarks_instance_pallet! { caller_lookup.clone(), caller_lookup, true, - false, + CollectionConfig::empty(), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { @@ -247,15 +247,6 @@ benchmarks_instance_pallet! { assert_last_event::(Event::CollectionFrozen { collection }.into()); } - thaw_collection { - let (collection, caller, caller_lookup) = create_collection::(); - let origin = SystemOrigin::Signed(caller.clone()).into(); - Nfts::::freeze_collection(origin, collection)?; - }: _(SystemOrigin::Signed(caller.clone()), collection) - verify { - assert_last_event::(Event::CollectionThawed { collection }.into()); - } - transfer_ownership { let (collection, caller, _) = create_collection::(); let target: T::AccountId = account("target", 0, SEED); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 3ca0814efc4c0..1947cbc3fa0a1 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -345,8 +345,6 @@ pub mod pallet { Thawed { collection: T::CollectionId, item: T::ItemId }, /// Some `collection` was frozen. CollectionFrozen { collection: T::CollectionId }, - /// Some `collection` was thawed. - CollectionThawed { collection: T::CollectionId }, /// The owner changed. OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, /// The management team changed. @@ -891,33 +889,6 @@ pub mod pallet { }) } - /// Re-allow unprivileged transfers for a whole collection. - /// - /// Origin must be Signed and the sender should be the Admin of the `collection`. - /// - /// - `collection`: The collection to be thawed. - /// - /// Emits `CollectionThawed`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw_collection())] - pub fn thaw_collection( - origin: OriginFor, - collection: T::CollectionId, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.admin, Error::::NoPermission); - - details.is_frozen = false; - - Self::deposit_event(Event::::CollectionThawed { collection }); - Ok(()) - }) - } - /// Change the Owner of a collection. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index aa4c1eb91568e..66893b300405a 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -247,7 +247,16 @@ fn freezing_should_work() { assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(1), 0)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(1), 0)); + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + false, + CollectionConfig::empty(), + )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); } @@ -599,7 +608,7 @@ fn force_collection_status_should_work() { 1, 1, true, - false, + CollectionConfig::empty(), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); @@ -1151,8 +1160,6 @@ fn buy_item_should_work() { Error::::Frozen ); - assert_ok!(Nfts::thaw_collection(RuntimeOrigin::signed(user_1), collection_id)); - // freeze item assert_ok!(Nfts::freeze(RuntimeOrigin::signed(user_1), collection_id, item_3)); diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 39dd977674952..b92f1fce0deb0 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -56,7 +56,6 @@ pub trait WeightInfo { fn freeze() -> Weight; fn thaw() -> Weight; fn freeze_collection() -> Weight; - fn thaw_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; fn force_collection_status() -> Weight; @@ -178,12 +177,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) - fn thaw_collection() -> Weight { - Weight::from_ref_time(22_584_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) @@ -408,12 +401,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) - fn thaw_collection() -> Weight { - Weight::from_ref_time(22_584_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) From c41a48c1f17747ecfedea112702b42009a44840f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 10:59:23 +0300 Subject: [PATCH 16/94] Chore --- frame/nfts/src/functions.rs | 2 +- frame/nfts/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index c773ba668c8bf..6044b1f91bef6 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -84,7 +84,7 @@ impl, I: 'static> Pallet { free_holding: bool, event: Event, ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::InUse); + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); T::Currency::reserve(&owner, deposit)?; diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 1947cbc3fa0a1..a870541b2a111 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -456,8 +456,8 @@ pub mod pallet { WrongOwner, /// Invalid witness data given. BadWitness, - /// The item ID is already taken. - InUse, + /// Collection ID is already taken. + CollectionIdInUse, /// Items within that collection are non-transferable. ItemsNotTransferable, /// The item or collection is frozen. @@ -733,7 +733,7 @@ pub mod pallet { }) } - /// Reevaluate the deposits on some items. + /// Re-evaluate the deposits on some items. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// From fee3ade53136430d6cc06c64ed61f747c5d8d681 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 11:33:21 +0300 Subject: [PATCH 17/94] Connect collection.is_frozen to config --- frame/nfts/src/benchmarking.rs | 2 +- frame/nfts/src/features/common.rs | 32 ++++++++++++++++++++++ frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/functions.rs | 37 +++++++------------------- frame/nfts/src/impl_nonfungibles.rs | 7 +++-- frame/nfts/src/lib.rs | 38 +++++++++++++------------- frame/nfts/src/tests.rs | 41 ++++++++++++++--------------- frame/nfts/src/types.rs | 2 -- 8 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 frame/nfts/src/features/common.rs diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 0a60af3de1230..26807a24cbc66 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -284,7 +284,7 @@ benchmarks_instance_pallet! { admin: caller_lookup.clone(), freezer: caller_lookup, free_holding: true, - is_frozen: false, + config: CollectionConfig::empty(), }; }: { call.dispatch_bypass_filter(origin)? } verify { diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/common.rs new file mode 100644 index 0000000000000..48db43210e097 --- /dev/null +++ b/frame/nfts/src/features/common.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn is_collection_setting_disabled( + collection_id: &T::CollectionId, + setting: CollectionSetting, + ) -> Result { + let config = CollectionConfigOf::::get(&collection_id) + .ok_or(Error::::UnknownCollection)?; + + let settings = config.values(); + Ok(!settings.contains(setting)) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 5661797978439..f4a455d772c94 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -16,3 +16,4 @@ // limitations under the License. pub mod buy_sell; +pub mod common; diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 6044b1f91bef6..837e7ddaa07e2 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -36,17 +36,13 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!collection_details.is_frozen, Error::::Frozen); ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); - let config = - CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; - - let settings = config.values(); - ensure!( - !settings.contains(CollectionSetting::NonTransferableItems), - Error::::ItemsNotTransferable - ); + let action_allowed = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::NonTransferableItems, + )?; + ensure!(action_allowed, Error::::ItemsNotTransferable); let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; @@ -100,7 +96,6 @@ impl, I: 'static> Pallet { items: 0, item_metadatas: 0, attributes: 0, - is_frozen: false, }, ); @@ -246,14 +241,11 @@ impl, I: 'static> Pallet { let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); - let config = - CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; - - let settings = config.values(); - ensure!( - !settings.contains(CollectionSetting::NonTransferableItems), - Error::::ItemsNotTransferable - ); + let action_allowed = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::NonTransferableItems, + )?; + ensure!(action_allowed, Error::::ItemsNotTransferable); if let Some(ref price) = price { ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); @@ -280,15 +272,6 @@ impl, I: 'static> Pallet { let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner != buyer, Error::::NoPermission); - let config = - CollectionConfigOf::::get(collection).ok_or(Error::::UnknownCollection)?; - - let settings = config.values(); - ensure!( - !settings.contains(CollectionSetting::NonTransferableItems), - Error::::NotForSale - ); - let price_info = ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index a4b5d3f8d9800..6b0d5a90fccac 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -78,8 +78,11 @@ impl, I: 'static> Inspect<::AccountId> for Palle /// /// Default implementation is that all items are transferable. fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { - match (Collection::::get(collection), Item::::get(collection, item)) { - (Some(cd), Some(id)) if !cd.is_frozen && !id.is_frozen => true, + match (CollectionConfigOf::::get(collection), Item::::get(collection, item)) { + (Some(cc), Some(id)) + if !cc.values().contains(CollectionSetting::NonTransferableItems) && + !id.is_frozen => + true, _ => false, } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index a870541b2a111..af413fa439e0e 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -878,11 +878,15 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.freezer, Error::::NoPermission); + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); - details.is_frozen = true; + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; + let mut settings = config.values(); + settings.insert(CollectionSetting::NonTransferableItems); + config.0 = settings; Self::deposit_event(Event::::CollectionFrozen { collection }); Ok(()) @@ -1001,20 +1005,17 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let config = CollectionConfigOf::::get(collection) - .ok_or(Error::::UnknownCollection)?; - - let settings = config.values(); - ensure!( - !settings.contains(CollectionSetting::NonTransferableItems), - Error::::ItemsNotTransferable - ); - let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let action_allowed = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::NonTransferableItems, + )?; + ensure!(action_allowed, Error::::ItemsNotTransferable); + if let Some(check) = maybe_check { let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); @@ -1156,8 +1157,7 @@ pub mod pallet { /// - `admin`: The new Admin of this collection. /// - `freezer`: The new Freezer of this collection. /// - `free_holding`: Whether a deposit is taken for holding an item in this collection. - /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin - /// instructions. + /// - `config`: Collection's config. /// /// Emits `CollectionStatusChanged` with the identity of the item. /// @@ -1171,7 +1171,7 @@ pub mod pallet { admin: AccountIdLookupOf, freezer: AccountIdLookupOf, free_holding: bool, - is_frozen: bool, + config: CollectionConfig, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -1185,10 +1185,10 @@ pub mod pallet { collection.admin = T::Lookup::lookup(admin)?; collection.freezer = T::Lookup::lookup(freezer)?; collection.free_holding = free_holding; - collection.is_frozen = is_frozen; *maybe_collection = Some(collection); CollectionAccount::::remove(&old_owner, &collection_id); CollectionAccount::::insert(&new_owner, &collection_id, ()); + CollectionConfigOf::::insert(&collection_id, config); Self::deposit_event(Event::CollectionStatusChanged { collection: collection_id }); Ok(()) @@ -1445,14 +1445,14 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let config = CollectionConfigOf::::get(collection) + /*let config = CollectionConfigOf::::get(collection) .ok_or(Error::::UnknownCollection)?; let settings = config.values(); ensure!( !settings.contains(CollectionSetting::LockedMetadata), Error::::CollectionIsLocked - ); + );*/ let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 66893b300405a..6a680289ced38 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -245,7 +245,10 @@ fn freezing_should_work() { assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(1), 0)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), + Error::::ItemsNotTransferable + ); assert_ok!(Nfts::force_collection_status( RuntimeOrigin::root(), @@ -1137,7 +1140,7 @@ fn buy_item_should_work() { Error::::NotForSale ); - // ensure we can't buy an item when the collection or an item is frozen + // ensure we can't buy an item when the collection or an item are frozen { assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1147,7 +1150,7 @@ fn buy_item_should_work() { None, )); - // freeze collection + // freeze the collection assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(user_1), collection_id)); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1157,10 +1160,22 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::Frozen + Error::::ItemsNotTransferable ); - // freeze item + // un-freeze the collection + assert_ok!(Nfts::force_collection_status( + RuntimeOrigin::root(), + collection_id, + user_1, + user_1, + user_1, + user_1, + false, + CollectionConfig::empty(), + )); + + // freeze the item assert_ok!(Nfts::freeze(RuntimeOrigin::signed(user_1), collection_id, item_3)); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1173,22 +1188,6 @@ fn buy_item_should_work() { Error::::Frozen ); } - - // ensure we can't buy an item when the collection has a NonTransferableItems flag - let collection_id = 1; - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_1, - CollectionConfig(CollectionSetting::NonTransferableItems.into()), - true - )); - - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - - assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_1, price_1.into()), - Error::::NotForSale - ); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index fad6b505d940f..7bf027968a879 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -63,8 +63,6 @@ pub struct CollectionDetails { pub(super) item_metadatas: u32, /// The total number of attributes for this collection. pub(super) attributes: u32, - /// Whether the collection is frozen for non-admin transfers. - pub(super) is_frozen: bool, } /// Witness data for the destroy transactions. From a9ab6f88a8b20fccee057ad3b126bca6fc33ba2f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 11:59:27 +0300 Subject: [PATCH 18/94] Allow to lock the collection in a new way --- frame/nfts/src/benchmarking.rs | 11 ++- frame/nfts/src/functions.rs | 26 ------- frame/nfts/src/lib.rs | 43 ++++++----- frame/nfts/src/tests.rs | 135 ++++++++++++++++++++------------- frame/nfts/src/weights.rs | 6 +- 5 files changed, 114 insertions(+), 107 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 26807a24cbc66..293c665ce33de 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -240,11 +240,16 @@ benchmarks_instance_pallet! { assert_last_event::(Event::Thawed { collection, item }.into()); } - freeze_collection { + lock_collection { let (collection, caller, caller_lookup) = create_collection::(); - }: _(SystemOrigin::Signed(caller.clone()), collection) + let lock_config = CollectionConfig( + CollectionSetting::NonTransferableItems | + CollectionSetting::LockedMetadata | + CollectionSetting::LockedAttributes, + ); + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_config) verify { - assert_last_event::(Event::CollectionFrozen { collection }.into()); + assert_last_event::(Event::CollectionLocked { collection }.into()); } transfer_ownership { diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 837e7ddaa07e2..85d7c14328eb1 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -303,32 +303,6 @@ impl, I: 'static> Pallet { Ok(()) } - /*pub fn do_change_collection_config( - id: T::CollectionId, - caller: T::AccountId, - current_config: CollectionConfig, - new_config: CollectionConfig, - ) -> DispatchResult { - let collection = Collection::::get(id).ok_or(Error::::UnknownCollection)?; - ensure!(collection.owner == caller, Error::::NoPermission); - - let settings = current_config.values(); - - if settings.contains(CollectionSetting::IsLocked) { - return Err(Error::::CollectionIsLocked.into()) - } - - CollectionConfigOf::::try_mutate(id, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; - - config.user_features = new_config; - - Self::deposit_event(Event::::CollectionConfigChanged { id }); - - Ok(()) - }) - }*/ - #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_next_id(id: T::CollectionId) { NextCollectionId::::set(Some(id)); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index af413fa439e0e..643d482357a3e 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -344,7 +344,7 @@ pub mod pallet { /// Some `item` was thawed. Thawed { collection: T::CollectionId, item: T::ItemId }, /// Some `collection` was frozen. - CollectionFrozen { collection: T::CollectionId }, + CollectionLocked { collection: T::CollectionId }, /// The owner changed. OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, /// The management team changed. @@ -582,19 +582,6 @@ pub mod pallet { ) } - /*#[pallet::weight(0)] - pub fn change_collection_config( - origin: OriginFor, - id: T::CollectionId, - new_config: CollectionConfig, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - let current_config = - CollectionConfigOf::::get(id).ok_or(Error::::UnknownCollection)?; - Self::do_change_collection_config(id, sender, current_config, new_config)?; - Ok(()) - }*/ - /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the @@ -862,19 +849,22 @@ pub mod pallet { Ok(()) } - /// Disallow further unprivileged transfers for a whole collection. + /// Disallows specified settings for the whole collection. /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `collection`: The collection to be frozen. + /// - `collection`: The collection to be locked. + /// - `lock_config`: The config with the settings to be locked. /// - /// Emits `CollectionFrozen`. + /// Note: it's possible to only lock(set) the setting, but not to unset it. + /// Emits `CollectionLocked`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze_collection())] - pub fn freeze_collection( + #[pallet::weight(T::WeightInfo::lock_collection())] + pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, + lock_config: CollectionConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -885,10 +875,21 @@ pub mod pallet { CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; let mut settings = config.values(); - settings.insert(CollectionSetting::NonTransferableItems); + let lock_settings = lock_config.values(); + + if lock_settings.contains(CollectionSetting::NonTransferableItems) { + settings.insert(CollectionSetting::NonTransferableItems); + } + if lock_settings.contains(CollectionSetting::LockedMetadata) { + settings.insert(CollectionSetting::LockedMetadata); + } + if lock_settings.contains(CollectionSetting::LockedAttributes) { + settings.insert(CollectionSetting::LockedAttributes); + } + config.0 = settings; - Self::deposit_event(Event::::CollectionFrozen { collection }); + Self::deposit_event(Event::::CollectionLocked { collection }); Ok(()) }) } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 6a680289ced38..bc093d3c4081d 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -110,30 +110,6 @@ fn basic_minting_should_work() { }); } -/*#[test] -fn collection_locking_should_work() { - new_test_ext().execute_with(|| { - let user_id = 1; - let collection_id = 0; - - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_id, - CollectionConfig::empty(), - true - )); - - let new_config = CollectionConfig(CollectionSetting::IsLocked.into()); - - assert_ok!(Nfts::change_collection_config(RuntimeOrigin::signed(user_id), collection_id, new_config)); - - let collection_config = CollectionConfigOf::::get(collection_id); - - let expected_config = CollectionConfig(new_config); - assert_eq!(Some(expected_config), collection_config); - }); -}*/ - #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { @@ -244,7 +220,11 @@ fn freezing_should_work() { assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); - assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(1), 0)); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionConfig(CollectionSetting::NonTransferableItems.into()) + )); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemsNotTransferable @@ -1150,8 +1130,12 @@ fn buy_item_should_work() { None, )); - // freeze the collection - assert_ok!(Nfts::freeze_collection(RuntimeOrigin::signed(user_1), collection_id)); + // lock the collection + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_1), + collection_id, + CollectionConfig(CollectionSetting::NonTransferableItems.into()) + )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { collection: collection_id, @@ -1191,33 +1175,6 @@ fn buy_item_should_work() { }); } -#[test] -fn various_collection_settings() { - new_test_ext().execute_with(|| { - // when we set only one value it's required to call .into() on it - let config = CollectionConfig(CollectionSetting::NonTransferableItems.into()); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); - - let config = CollectionConfigOf::::get(0).unwrap(); - let stored_settings = config.values(); - assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); - assert!(!stored_settings.contains(CollectionSetting::LockedMetadata)); - - // no need to call .into() for multiple values - let config = CollectionConfig( - CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, - ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); - - let config = CollectionConfigOf::::get(1).unwrap(); - let stored_settings = config.values(); - assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); - assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); - - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); - }); -} - #[test] fn pay_tips_should_work() { new_test_ext().execute_with(|| { @@ -1262,3 +1219,73 @@ fn pay_tips_should_work() { })); }); } + +#[test] +fn various_collection_settings() { + new_test_ext().execute_with(|| { + // when we set only one value it's required to call .into() on it + let config = CollectionConfig(CollectionSetting::NonTransferableItems.into()); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); + + let config = CollectionConfigOf::::get(0).unwrap(); + let stored_settings = config.values(); + assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); + assert!(!stored_settings.contains(CollectionSetting::LockedMetadata)); + + // no need to call .into() for multiple values + let config = CollectionConfig( + CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, + ); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); + + let config = CollectionConfigOf::::get(1).unwrap(); + let stored_settings = config.values(); + assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); + assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + }); +} + +#[test] +fn collection_locking_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig::empty(), + false + )); + + // validate partial lock + let lock_config = CollectionConfig( + CollectionSetting::NonTransferableItems | CollectionSetting::LockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, lock_config); + + // validate full lock + let full_lock_config = CollectionConfig( + CollectionSetting::NonTransferableItems | + CollectionSetting::LockedMetadata | + CollectionSetting::LockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + CollectionConfig(CollectionSetting::LockedMetadata.into()), + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, full_lock_config); + }); +} diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index b92f1fce0deb0..27d770e7f9344 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -55,7 +55,7 @@ pub trait WeightInfo { fn redeposit(i: u32, ) -> Weight; fn freeze() -> Weight; fn thaw() -> Weight; - fn freeze_collection() -> Weight; + fn lock_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; fn force_collection_status() -> Weight; @@ -172,7 +172,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - fn freeze_collection() -> Weight { + fn lock_collection() -> Weight { Weight::from_ref_time(23_129_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) @@ -396,7 +396,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - fn freeze_collection() -> Weight { + fn lock_collection() -> Weight { Weight::from_ref_time(23_129_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) From 254816dce2540ef0372f2c950334b3d0c909d4e6 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 18:14:19 +0300 Subject: [PATCH 19/94] Move free_holding into settings --- frame/nfts/src/benchmarking.rs | 3 +- frame/nfts/src/functions.rs | 8 +- frame/nfts/src/impl_nonfungibles.rs | 10 ++- frame/nfts/src/lib.rs | 40 +++++++--- frame/nfts/src/tests.rs | 112 +++++++++++++--------------- frame/nfts/src/types.rs | 7 +- 6 files changed, 98 insertions(+), 82 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 293c665ce33de..0956158082ef4 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -288,8 +288,7 @@ benchmarks_instance_pallet! { issuer: caller_lookup.clone(), admin: caller_lookup.clone(), freezer: caller_lookup, - free_holding: true, - config: CollectionConfig::empty(), + config: CollectionConfig(CollectionSetting::FreeHolding), }; }: { call.dispatch_bypass_filter(origin)? } verify { diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 85d7c14328eb1..ecd06ddc0903e 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -77,7 +77,6 @@ impl, I: 'static> Pallet { admin: T::AccountId, config: CollectionConfig, deposit: DepositBalanceOf, - free_holding: bool, event: Event, ) -> DispatchResult { ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); @@ -92,7 +91,6 @@ impl, I: 'static> Pallet { admin: admin.clone(), freezer: admin, total_deposit: deposit, - free_holding, items: 0, item_metadatas: 0, attributes: 0, @@ -177,7 +175,11 @@ impl, I: 'static> Pallet { collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; - let deposit = match collection_details.free_holding { + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); + + let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), false => T::ItemDeposit::get(), }; diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 6b0d5a90fccac..7efeacbb07bcc 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -96,15 +96,19 @@ impl, I: 'static> Create<::AccountId, Collection collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, - config: &CollectionSettings, + settings: &CollectionSettings, ) -> DispatchResult { + let mut settings = *settings; + // FreeHolding could be set by calling the force_create() only + if settings.contains(CollectionSetting::FreeHolding) { + settings.remove(CollectionSetting::FreeHolding); + } Self::do_create_collection( *collection, who.clone(), admin.clone(), - CollectionConfig(*config), + CollectionConfig(settings), T::CollectionDeposit::get(), - false, Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, ) } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 643d482357a3e..669247303db94 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -531,13 +531,19 @@ pub mod pallet { let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; + let mut settings = config.values(); + // FreeHolding could be set by calling the force_create() only + if settings.contains(CollectionSetting::FreeHolding) { + settings.remove(CollectionSetting::FreeHolding); + } + let config = CollectionConfig(settings); + Self::do_create_collection( collection, owner.clone(), admin.clone(), config, T::CollectionDeposit::get(), - false, Event::Created { collection, creator: owner, owner: admin }, ) } @@ -563,7 +569,6 @@ pub mod pallet { origin: OriginFor, owner: AccountIdLookupOf, config: CollectionConfig, - free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; @@ -577,7 +582,6 @@ pub mod pallet { owner.clone(), config, Zero::zero(), - free_holding, Event::ForceCreated { collection, owner }, ) } @@ -748,7 +752,12 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); - let deposit = match collection_details.free_holding { + + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); + + let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), false => T::ItemDeposit::get(), }; @@ -1157,7 +1166,6 @@ pub mod pallet { /// - `issuer`: The new Issuer of this collection. /// - `admin`: The new Admin of this collection. /// - `freezer`: The new Freezer of this collection. - /// - `free_holding`: Whether a deposit is taken for holding an item in this collection. /// - `config`: Collection's config. /// /// Emits `CollectionStatusChanged` with the identity of the item. @@ -1171,7 +1179,6 @@ pub mod pallet { issuer: AccountIdLookupOf, admin: AccountIdLookupOf, freezer: AccountIdLookupOf, - free_holding: bool, config: CollectionConfig, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -1185,7 +1192,6 @@ pub mod pallet { collection.issuer = T::Lookup::lookup(issuer)?; collection.admin = T::Lookup::lookup(admin)?; collection.freezer = T::Lookup::lookup(freezer)?; - collection.free_holding = free_holding; *maybe_collection = Some(collection); CollectionAccount::::remove(&old_owner, &collection_id); CollectionAccount::::insert(&new_owner, &collection_id, ()); @@ -1227,6 +1233,11 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); + if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } @@ -1243,7 +1254,7 @@ pub mod pallet { let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !collection_details.free_holding && maybe_check_owner.is_some() { + if !settings.contains(CollectionSetting::FreeHolding) && maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); @@ -1339,6 +1350,10 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); + if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } @@ -1353,7 +1368,8 @@ pub mod pallet { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !collection_details.free_holding && maybe_check_owner.is_some() { + if !settings.contains(CollectionSetting::FreeHolding) && maybe_check_owner.is_some() + { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); @@ -1454,6 +1470,9 @@ pub mod pallet { !settings.contains(CollectionSetting::LockedMetadata), Error::::CollectionIsLocked );*/ + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -1468,7 +1487,8 @@ pub mod pallet { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && !details.free_holding { + if maybe_check_owner.is_some() && !settings.contains(CollectionSetting::FreeHolding) + { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index bc093d3c4081d..96852059f282c 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -88,6 +88,10 @@ fn events() -> Vec> { result } +fn default_config() -> CollectionConfig { + CollectionConfig(CollectionSetting::FreeHolding.into()) +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -98,12 +102,12 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); @@ -114,7 +118,7 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0], false)); @@ -158,7 +162,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); @@ -169,7 +173,7 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -180,7 +184,7 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); @@ -198,8 +202,9 @@ fn transfer_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig(CollectionSetting::NonTransferableItems.into()), - true + CollectionConfig( + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + ) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42)); @@ -214,7 +219,7 @@ fn transfer_should_work() { #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); @@ -237,7 +242,6 @@ fn freezing_should_work() { 1, 1, 1, - false, CollectionConfig::empty(), )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); @@ -247,7 +251,7 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -278,7 +282,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, CollectionConfig::empty())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), @@ -328,7 +332,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); @@ -347,7 +351,7 @@ fn set_collection_metadata_should_work() { Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); // Cannot add metadata to unowned item assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20], false), @@ -427,7 +431,7 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( @@ -494,7 +498,7 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -539,7 +543,7 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -574,7 +578,7 @@ fn force_collection_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); @@ -590,8 +594,7 @@ fn force_collection_status_should_work() { 1, 1, 1, - true, - CollectionConfig::empty(), + CollectionConfig(CollectionSetting::FreeHolding.into()), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); @@ -617,7 +620,7 @@ fn force_collection_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -647,7 +650,7 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); @@ -665,8 +668,9 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig(CollectionSetting::NonTransferableItems.into()), - true + CollectionConfig( + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + ) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1)); @@ -681,7 +685,7 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -728,7 +732,7 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); let current_block = 1; @@ -753,7 +757,7 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); for i in 3..13 { @@ -773,7 +777,11 @@ fn approval_deadline_works() { System::set_block_number(0); assert!(System::block_number().is_zero()); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + CollectionConfig(CollectionSetting::FreeHolding.into()) + )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. @@ -800,7 +808,7 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -828,7 +836,7 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -856,7 +864,7 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -895,12 +903,7 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_id, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_config())); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( @@ -950,12 +953,7 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_id, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); @@ -1010,8 +1008,9 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig(CollectionSetting::NonTransferableItems.into()), - true + CollectionConfig( + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + ) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); @@ -1041,12 +1040,7 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_1, - CollectionConfig::empty(), - true - )); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); @@ -1155,7 +1149,6 @@ fn buy_item_should_work() { user_1, user_1, user_1, - false, CollectionConfig::empty(), )); @@ -1225,7 +1218,7 @@ fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it let config = CollectionConfig(CollectionSetting::NonTransferableItems.into()); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); let stored_settings = config.values(); @@ -1236,14 +1229,14 @@ fn various_collection_settings() { let config = CollectionConfig( CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config, false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(1).unwrap(); let stored_settings = config.values(); assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty(), false)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); }); } @@ -1253,12 +1246,7 @@ fn collection_locking_should_work() { let user_id = 1; let collection_id = 0; - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_id, - CollectionConfig::empty(), - false - )); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, CollectionConfig::empty())); // validate partial lock let lock_config = CollectionConfig( @@ -1267,7 +1255,7 @@ fn collection_locking_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - lock_config + lock_config, )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 7bf027968a879..77136cd0df55a 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -55,8 +55,6 @@ pub struct CollectionDetails { /// The total balance deposited for the all storage associated with this collection. /// Used by `destroy`. pub(super) total_deposit: DepositBalance, - /// If `true`, then no deposit is needed to hold items of this collection. - pub(super) free_holding: bool, /// The total number of outstanding items of this collection. pub(super) items: u32, /// The total number of outstanding item metadata of this collection. @@ -152,9 +150,14 @@ pub struct ItemTip { #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum CollectionSetting { + /// Disallow to transfer items in this collection. NonTransferableItems, + /// Disallow to modify metadata of this collection. LockedMetadata, + /// Disallow to modify attributes of this collection. LockedAttributes, + /// When this is set then no deposit needed to hold items of this collection. + FreeHolding, } pub(super) type CollectionSettings = BitFlags; From 5878197efe2ecc982bb839a3163daa7084a10c9c Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 26 Sep 2022 18:58:40 +0300 Subject: [PATCH 20/94] Connect collection's metadata locker to feature flags --- frame/nfts/src/benchmarking.rs | 4 +- frame/nfts/src/features/common.rs | 4 +- frame/nfts/src/functions.rs | 6 +-- frame/nfts/src/lib.rs | 61 ++++++++++++++------------- frame/nfts/src/tests.rs | 70 +++++++++++++------------------ frame/nfts/src/types.rs | 4 +- 6 files changed, 69 insertions(+), 80 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 0956158082ef4..8ce967b476dec 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -340,9 +340,9 @@ benchmarks_instance_pallet! { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection, data.clone()) verify { - assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); + assert_last_event::(Event::CollectionMetadataSet { collection, data }.into()); } clear_collection_metadata { diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/common.rs index 48db43210e097..12cd6758aa4b2 100644 --- a/frame/nfts/src/features/common.rs +++ b/frame/nfts/src/features/common.rs @@ -22,11 +22,11 @@ impl, I: 'static> Pallet { pub fn is_collection_setting_disabled( collection_id: &T::CollectionId, setting: CollectionSetting, - ) -> Result { + ) -> Result<(bool, CollectionSettings), DispatchError> { let config = CollectionConfigOf::::get(&collection_id) .ok_or(Error::::UnknownCollection)?; let settings = config.values(); - Ok(!settings.contains(setting)) + Ok((!settings.contains(setting), settings)) } } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index ecd06ddc0903e..521de4aba62b7 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -38,14 +38,14 @@ impl, I: 'static> Pallet { Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); - let action_allowed = Self::is_collection_setting_disabled( + let (action_allowed, _) = Self::is_collection_setting_disabled( &collection, CollectionSetting::NonTransferableItems, )?; ensure!(action_allowed, Error::::ItemsNotTransferable); let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(!details.is_frozen, Error::::Frozen); with_details(&collection_details, &mut details)?; @@ -243,7 +243,7 @@ impl, I: 'static> Pallet { let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); - let action_allowed = Self::is_collection_setting_disabled( + let (action_allowed, _) = Self::is_collection_setting_disabled( &collection, CollectionSetting::NonTransferableItems, )?; diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 669247303db94..443e81b0c3c57 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -376,11 +376,7 @@ pub mod pallet { /// A `collection` has had its attributes changed by the `Force` origin. CollectionStatusChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. - CollectionMetadataSet { - collection: T::CollectionId, - data: BoundedVec, - is_frozen: bool, - }, + CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, /// Metadata has been cleared for a `collection`. CollectionMetadataCleared { collection: T::CollectionId }, /// New metadata has been set for an item. @@ -472,8 +468,10 @@ pub mod pallet { Unaccepted, /// The item is locked. Locked, - /// The collection is locked. - CollectionIsLocked, + /// The collection's metadata is locked. + CollectionMetadataIsLocked, + /// The collection's attributes are locked. + CollectionAttributesAreLocked, /// All items have been minted. MaxSupplyReached, /// The max supply has already been set. @@ -1020,7 +1018,7 @@ pub mod pallet { let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - let action_allowed = Self::is_collection_setting_disabled( + let (action_allowed, _) = Self::is_collection_setting_disabled( &collection, CollectionSetting::NonTransferableItems, )?; @@ -1242,7 +1240,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + None => Some(settings.contains(CollectionSetting::LockedAttributes)), Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); @@ -1302,8 +1300,13 @@ pub mod pallet { if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } + + let config = CollectionConfigOf::::get(&collection) + .ok_or(Error::::UnknownCollection)?; + let settings = config.values(); + let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + None => Some(settings.contains(CollectionSetting::LockedAttributes)), Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); @@ -1446,7 +1449,6 @@ pub mod pallet { /// /// - `collection`: The identifier of the item whose metadata to update. /// - `data`: The general information of this item. Limited in length by `StringLimit`. - /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `CollectionMetadataSet`. /// @@ -1456,23 +1458,19 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, data: BoundedVec, - is_frozen: bool, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - /*let config = CollectionConfigOf::::get(collection) - .ok_or(Error::::UnknownCollection)?; - - let settings = config.values(); + let (action_allowed, settings) = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::LockedMetadata, + )?; ensure!( - !settings.contains(CollectionSetting::LockedMetadata), - Error::::CollectionIsLocked - );*/ - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); + maybe_check_owner.is_none() || action_allowed, + Error::::CollectionMetadataIsLocked + ); let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -1481,9 +1479,6 @@ pub mod pallet { } CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); @@ -1502,9 +1497,9 @@ pub mod pallet { Collection::::insert(&collection, details); - *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); - Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen }); + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); Ok(()) }) } @@ -1536,10 +1531,16 @@ pub mod pallet { ensure!(check_owner == &details.owner, Error::::NoPermission); } - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + let (action_allowed, _) = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::LockedMetadata, + )?; + ensure!( + maybe_check_owner.is_none() || action_allowed, + Error::::CollectionMetadataIsLocked + ); + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; T::Currency::unreserve(&details.owner, deposit); Self::deposit_event(Event::CollectionMetadataCleared { collection }); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 96852059f282c..b279a7fa2c24a 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -121,7 +121,7 @@ fn lifecycle_should_work() { assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0])); assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); @@ -304,12 +304,7 @@ fn transfer_owner_should_work() { ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(2), - 0, - bvec![0u8; 20], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); @@ -348,70 +343,55 @@ fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), Error::::UnknownCollection, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 20], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20])); assert_eq!(Balances::free_balance(&1), 9); assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 15], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(1), - 0, - bvec![0u8; 25], - false - )); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 25])); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40], false), + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_collection_metadata( + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); + assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - bvec![0u8; 15], - true + CollectionConfig(CollectionSetting::LockedMetadata.into()) )); assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15], false), - Error::::Frozen, + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), + Error::::CollectionMetadataIsLocked, ); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), - Error::::Frozen + Error::::CollectionMetadataIsLocked ); // Clear Metadata - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15])); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(2), 0), Error::::NoPermission @@ -420,7 +400,11 @@ fn set_collection_metadata_should_work() { Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 1), Error::::UnknownCollection ); - assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0)); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::CollectionMetadataIsLocked + ); + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); assert!(!CollectionMetadataOf::::contains_key(0)); }); } @@ -558,7 +542,13 @@ fn set_attribute_should_respect_freeze() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![], true)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![])); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionConfig(CollectionSetting::LockedAttributes.into()) + )); + let e = Error::::Frozen; assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); @@ -581,7 +571,7 @@ fn force_collection_status_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); @@ -611,7 +601,7 @@ fn force_collection_status_should_work() { assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 0); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 77136cd0df55a..28c62a8611adb 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -113,8 +113,6 @@ pub struct CollectionMetadata> { /// will generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the collection's metadata may be changed by a non Force origin. - pub(super) is_frozen: bool, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] @@ -156,7 +154,7 @@ pub enum CollectionSetting { LockedMetadata, /// Disallow to modify attributes of this collection. LockedAttributes, - /// When this is set then no deposit needed to hold items of this collection. + /// When is set then no deposit needed to hold items of this collection. FreeHolding, } From 95e21fbba73a11c7cf03f612f107eb529cd9ac77 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 10:46:43 +0300 Subject: [PATCH 21/94] DRY --- frame/nfts/src/features/common.rs | 13 +++++++++---- frame/nfts/src/functions.rs | 4 +--- frame/nfts/src/lib.rs | 17 ++++------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/common.rs index 12cd6758aa4b2..3a46306a603b8 100644 --- a/frame/nfts/src/features/common.rs +++ b/frame/nfts/src/features/common.rs @@ -19,14 +19,19 @@ use crate::*; use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { - pub fn is_collection_setting_disabled( + pub fn get_collection_settings( collection_id: &T::CollectionId, - setting: CollectionSetting, - ) -> Result<(bool, CollectionSettings), DispatchError> { + ) -> Result { let config = CollectionConfigOf::::get(&collection_id) .ok_or(Error::::UnknownCollection)?; + Ok(config.values()) + } - let settings = config.values(); + pub fn is_collection_setting_disabled( + collection_id: &T::CollectionId, + setting: CollectionSetting, + ) -> Result<(bool, CollectionSettings), DispatchError> { + let settings = Self::get_collection_settings(&collection_id)?; Ok((!settings.contains(setting), settings)) } } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 521de4aba62b7..aa029334b8a8a 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -175,9 +175,7 @@ impl, I: 'static> Pallet { collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); + let settings = Self::get_collection_settings(&collection)?; let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 443e81b0c3c57..91be30495b3f0 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -751,10 +751,7 @@ pub mod pallet { Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); - + let settings = Self::get_collection_settings(&collection)?; let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), false => T::ItemDeposit::get(), @@ -1232,9 +1229,7 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); + let settings = Self::get_collection_settings(&collection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); @@ -1301,9 +1296,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); + let settings = Self::get_collection_settings(&collection)?; let maybe_is_frozen = match maybe_item { None => Some(settings.contains(CollectionSetting::LockedAttributes)), @@ -1353,9 +1346,7 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let config = CollectionConfigOf::::get(&collection) - .ok_or(Error::::UnknownCollection)?; - let settings = config.values(); + let settings = Self::get_collection_settings(&collection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); From 17b7c9e300a4b8856a1d8d6f992da0bf3c533f7d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 10:47:03 +0300 Subject: [PATCH 22/94] Chore --- frame/nfts/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 91be30495b3f0..d2862c8d14847 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -470,8 +470,6 @@ pub mod pallet { Locked, /// The collection's metadata is locked. CollectionMetadataIsLocked, - /// The collection's attributes are locked. - CollectionAttributesAreLocked, /// All items have been minted. MaxSupplyReached, /// The max supply has already been set. From dc6d2f043168fd457dfb1b3fa03ee6442c1c7a83 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 11:39:42 +0300 Subject: [PATCH 23/94] Connect pallet level feature flags --- bin/node/runtime/src/lib.rs | 2 +- frame/nfts/src/features/common.rs | 5 ++++ frame/nfts/src/lib.rs | 37 +++++++++++++++++++++++--- frame/nfts/src/mock.rs | 2 +- frame/nfts/src/types.rs | 43 +++++++++++++++++++++++++++++-- 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 4c5a4d748ae3c..0e52bd54dd912 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1498,7 +1498,7 @@ impl pallet_uniques::Config for Runtime { } parameter_types! { - pub FeatureFlags: SystemFeatures = SystemFeature::empty(); + pub FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); } impl pallet_nfts::Config for Runtime { diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/common.rs index 3a46306a603b8..805f254398c56 100644 --- a/frame/nfts/src/features/common.rs +++ b/frame/nfts/src/features/common.rs @@ -34,4 +34,9 @@ impl, I: 'static> Pallet { let settings = Self::get_collection_settings(&collection_id)?; Ok((!settings.contains(setting), settings)) } + + pub fn is_feature_flag_set(feature: SystemFeature) -> bool { + let features = T::FeatureFlags::get(); + return features.0.contains(feature) + } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index d2862c8d14847..34abf899dcd28 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -182,6 +182,8 @@ pub mod pallet { #[pallet::constant] type MaxTips: Get; + /// Disables some of pallet's features. + #[pallet::constant] type FeatureFlags: Get; #[cfg(feature = "runtime-benchmarks")] @@ -484,6 +486,8 @@ pub mod pallet { BidTooLow, /// The item has reached its approval limit. ReachedApprovalLimit, + /// The method is disabled by system settings. + MethodDisabled, } impl, I: 'static> Pallet { @@ -1002,6 +1006,10 @@ pub mod pallet { delegate: AccountIdLookupOf, maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoApprovals), + Error::::MethodDisabled + ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1066,6 +1074,10 @@ pub mod pallet { item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoApprovals), + Error::::MethodDisabled + ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1126,6 +1138,10 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoApprovals), + Error::::MethodDisabled + ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1220,6 +1236,10 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoAttributes), + Error::::MethodDisabled + ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; @@ -1227,11 +1247,11 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let settings = Self::get_collection_settings(&collection)?; - if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } + + let settings = Self::get_collection_settings(&collection)?; let maybe_is_frozen = match maybe_item { None => Some(settings.contains(CollectionSetting::LockedAttributes)), Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), @@ -1284,6 +1304,10 @@ pub mod pallet { maybe_item: Option, key: BoundedVec, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoAttributes), + Error::::MethodDisabled + ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; @@ -1295,7 +1319,6 @@ pub mod pallet { } let settings = Self::get_collection_settings(&collection)?; - let maybe_is_frozen = match maybe_item { None => Some(settings.contains(CollectionSetting::LockedAttributes)), Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), @@ -1631,6 +1654,10 @@ pub mod pallet { whitelisted_buyer: Option>, ) -> DispatchResult { let origin = ensure_signed(origin)?; + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoTrading), + Error::::MethodDisabled + ); let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; Self::do_set_price(collection, item, origin, price, whitelisted_buyer) } @@ -1653,6 +1680,10 @@ pub mod pallet { bid_price: ItemPrice, ) -> DispatchResult { let origin = ensure_signed(origin)?; + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoTrading), + Error::::MethodDisabled + ); Self::do_buy_item(collection, item, origin, bid_price) } diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index 3e9e8ae631836..e4667a7265acb 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -86,7 +86,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub FeatureFlags: SystemFeatures = SystemFeature::empty(); + pub FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); } impl Config for Test { diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 28c62a8611adb..c2517adbc7f30 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -207,13 +207,52 @@ impl TypeInfo for CollectionConfig { // Support for up to 64 system-enabled features on a collection. #[bitflags] #[repr(u64)] -#[derive(Copy, Clone, RuntimeDebug, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum SystemFeature { + /// Disallow trading operations. NoTrading, + /// Disallow setting attributes. NoAttributes, + /// Disallow transfer approvals. NoApprovals, + /// Disallow atomic items swap. NoSwaps, + /// Disallow public mints. NoPublicMints, } -pub type SystemFeatures = BitFlags; +pub type SystemFeatureFlags = BitFlags; + +/// Wrapper type for `SystemFeatureFlags` that implements `Codec`. +#[derive(Default, RuntimeDebug)] +pub struct SystemFeatures(pub SystemFeatureFlags); + +impl MaxEncodedLen for SystemFeatures { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +impl Encode for SystemFeatures { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl EncodeLike for SystemFeatures {} +impl Decode for SystemFeatures { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self(SystemFeatureFlags::from_bits(field as u64).map_err(|_| "invalid value")?)) + } +} + +impl TypeInfo for SystemFeatures { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("SystemFeature"))) + } +} From cc64873ca4d6dff1ca5c2d9a023895aa1940b878 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 13:13:00 +0300 Subject: [PATCH 24/94] Prepare tests for the new changes --- frame/nfts/src/tests.rs | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index b279a7fa2c24a..04419c4f82ae7 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -88,7 +88,7 @@ fn events() -> Vec> { result } -fn default_config() -> CollectionConfig { +fn default_collection_config() -> CollectionConfig { CollectionConfig(CollectionSetting::FreeHolding.into()) } @@ -102,12 +102,12 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); @@ -118,7 +118,7 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0])); @@ -162,7 +162,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); @@ -173,7 +173,7 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -184,7 +184,7 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); @@ -219,7 +219,7 @@ fn transfer_should_work() { #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); @@ -251,7 +251,7 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -282,7 +282,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_config())); + assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), @@ -327,7 +327,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); @@ -640,7 +640,7 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); @@ -675,7 +675,7 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -722,7 +722,7 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); let current_block = 1; @@ -747,7 +747,7 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); for i in 3..13 { @@ -798,7 +798,7 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -826,7 +826,7 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -854,7 +854,7 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); @@ -893,7 +893,7 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Nfts::set_collection_max_supply( @@ -943,7 +943,7 @@ fn set_price_should_work() { let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); @@ -1030,7 +1030,7 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); @@ -1226,7 +1226,7 @@ fn various_collection_settings() { assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_config())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); }); } From 5153080a4ffe763f8d308974bd9b88551e289f60 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 13:22:06 +0300 Subject: [PATCH 25/94] Implement Item settings --- frame/nfts/src/features/common.rs | 18 ++ frame/nfts/src/functions.rs | 22 ++- frame/nfts/src/impl_nonfungibles.rs | 16 +- frame/nfts/src/lib.rs | 57 ++++--- frame/nfts/src/tests.rs | 155 +++++++++++++----- frame/nfts/src/types.rs | 61 ++++++- .../support/src/traits/tokens/nonfungible.rs | 29 +++- .../support/src/traits/tokens/nonfungibles.rs | 3 +- 8 files changed, 274 insertions(+), 87 deletions(-) diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/common.rs index 805f254398c56..b4f7bff165597 100644 --- a/frame/nfts/src/features/common.rs +++ b/frame/nfts/src/features/common.rs @@ -27,6 +27,15 @@ impl, I: 'static> Pallet { Ok(config.values()) } + pub fn get_item_settings( + collection_id: &T::CollectionId, + item_id: &T::ItemId, + ) -> Result { + let config = ItemConfigOf::::get(&collection_id, &item_id) + .ok_or(Error::::UnknownItem)?; + Ok(config.values()) + } + pub fn is_collection_setting_disabled( collection_id: &T::CollectionId, setting: CollectionSetting, @@ -35,6 +44,15 @@ impl, I: 'static> Pallet { Ok((!settings.contains(setting), settings)) } + pub fn is_item_setting_disabled( + collection_id: &T::CollectionId, + item_id: &T::ItemId, + setting: ItemSetting, + ) -> Result<(bool, ItemSettings), DispatchError> { + let settings = Self::get_item_settings(&collection_id, &item_id)?; + Ok((!settings.contains(setting), settings)) + } + pub fn is_feature_flag_set(feature: SystemFeature) -> bool { let features = T::FeatureFlags::get(); return features.0.contains(feature) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index aa029334b8a8a..a3afa50c32321 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -44,9 +44,12 @@ impl, I: 'static> Pallet { )?; ensure!(action_allowed, Error::::ItemsNotTransferable); + let (action_allowed, _) = + Self::is_item_setting_disabled(&collection, &item, ItemSetting::NonTransferable)?; + ensure!(action_allowed, Error::::Locked); + let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(!details.is_frozen, Error::::Frozen); with_details(&collection_details, &mut details)?; Account::::remove((&details.owner, &collection, &item)); @@ -140,6 +143,8 @@ impl, I: 'static> Pallet { T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); CollectionMaxSupply::::remove(&collection); CollectionConfigOf::::remove(&collection); + #[allow(deprecated)] + ItemConfigOf::::remove_prefix(&collection, None); Self::deposit_event(Event::Destroyed { collection }); @@ -155,6 +160,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, + config: ItemConfig, with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -186,12 +192,9 @@ impl, I: 'static> Pallet { let owner = owner.clone(); Account::::insert((&owner, &collection, &item), ()); - let details = ItemDetails { - owner, - approvals: ApprovalsOf::::default(), - is_frozen: false, - deposit, - }; + ItemConfigOf::::insert(&collection, &item, config); + let details = + ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; Item::::insert(&collection, &item, details); Ok(()) }, @@ -226,6 +229,7 @@ impl, I: 'static> Pallet { Item::::remove(&collection, &item); Account::::remove((&owner, &collection, &item)); ItemPriceOf::::remove(&collection, &item); + ItemConfigOf::::remove(&collection, &item); Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) @@ -247,6 +251,10 @@ impl, I: 'static> Pallet { )?; ensure!(action_allowed, Error::::ItemsNotTransferable); + let (action_allowed, _) = + Self::is_item_setting_disabled(&collection, &item, ItemSetting::NonTransferable)?; + ensure!(action_allowed, Error::::Locked); + if let Some(ref price) = price { ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); Self::deposit_event(Event::ItemPriceSet { diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 7efeacbb07bcc..7c7b6554ae731 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -78,10 +78,13 @@ impl, I: 'static> Inspect<::AccountId> for Palle /// /// Default implementation is that all items are transferable. fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { - match (CollectionConfigOf::::get(collection), Item::::get(collection, item)) { - (Some(cc), Some(id)) + match ( + CollectionConfigOf::::get(collection), + ItemConfigOf::::get(collection, item), + ) { + (Some(cc), Some(ic)) if !cc.values().contains(CollectionSetting::NonTransferableItems) && - !id.is_frozen => + !ic.values().contains(ItemSetting::NonTransferable) => true, _ => false, } @@ -130,13 +133,16 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId> for Pallet { +impl, I: 'static> Mutate<::AccountId, ItemSettings> + for Pallet +{ fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, who: &T::AccountId, + settings: &ItemSettings, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings), |_| Ok(())) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 34abf899dcd28..66d41150ca0d8 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -316,10 +316,22 @@ pub mod pallet { pub(super) type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; - /// Maps a unique collection id to it's config. #[pallet::storage] + /// Config of a collection. pub(super) type CollectionConfigOf, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig>; + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig, OptionQuery>; + + #[pallet::storage] + /// Config of an item. + pub(super) type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemConfig, + OptionQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -642,11 +654,12 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, owner: AccountIdLookupOf, + config: ItemConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, |collection_details| { + Self::do_mint(collection, item, owner, config, |collection_details| { ensure!(collection_details.issuer == origin, Error::::NoPermission); Ok(()) }) @@ -811,14 +824,15 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.freezer == origin, Error::::NoPermission); - details.is_frozen = true; - Item::::insert(&collection, &item, &details); + let mut settings = Self::get_item_settings(&collection, &item)?; + if !settings.contains(ItemSetting::NonTransferable) { + settings.insert(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); Self::deposit_event(Event::::Frozen { collection, item }); Ok(()) @@ -842,14 +856,15 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.admin == origin, Error::::NoPermission); + ensure!(collection_details.freezer == origin, Error::::NoPermission); - details.is_frozen = false; - Item::::insert(&collection, &item, &details); + let mut settings = Self::get_item_settings(&collection, &item)?; + if settings.contains(ItemSetting::NonTransferable) { + settings.remove(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); Self::deposit_event(Event::::Thawed { collection, item }); Ok(()) @@ -1251,10 +1266,11 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let settings = Self::get_collection_settings(&collection)?; + let collection_settings = Self::get_collection_settings(&collection)?; let maybe_is_frozen = match maybe_item { - None => Some(settings.contains(CollectionSetting::LockedAttributes)), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), + Some(item) => Self::get_item_settings(&collection, &item) + .map(|v| v.contains(ItemSetting::NonTransferable)), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); @@ -1265,7 +1281,9 @@ pub mod pallet { let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !settings.contains(CollectionSetting::FreeHolding) && maybe_check_owner.is_some() { + if !collection_settings.contains(CollectionSetting::FreeHolding) && + maybe_check_owner.is_some() + { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); @@ -1318,10 +1336,11 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let settings = Self::get_collection_settings(&collection)?; + let collection_settings = Self::get_collection_settings(&collection)?; let maybe_is_frozen = match maybe_item { - None => Some(settings.contains(CollectionSetting::LockedAttributes)), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), + Some(item) => Self::get_item_settings(&collection, &item) + .map(|v| v.contains(ItemSetting::NonTransferable)), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 04419c4f82ae7..0da5b1b8ab03d 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -92,6 +92,10 @@ fn default_collection_config() -> CollectionConfig { CollectionConfig(CollectionSetting::FreeHolding.into()) } +fn default_item_config() -> ItemConfig { + ItemConfig::empty() +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -104,12 +108,12 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -125,9 +129,9 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); @@ -165,7 +169,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -174,7 +178,7 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -185,7 +189,7 @@ fn mint_should_work() { fn transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); @@ -207,7 +211,7 @@ fn transfer_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), @@ -220,9 +224,9 @@ fn transfer_should_work() { fn freezing_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Locked); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); assert_ok!(Nfts::lock_collection( @@ -252,7 +256,7 @@ fn freezing_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -266,7 +270,10 @@ fn origin_guards_should_work() { ); assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2), Error::::NoPermission); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, default_item_config()), + Error::::NoPermission + ); assert_noop!( Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission @@ -305,7 +312,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); @@ -330,9 +337,9 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, default_item_config())); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); - assert_ok!(Nfts::thaw(RuntimeOrigin::signed(3), 0, 42)); + assert_ok!(Nfts::thaw(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 42, None)); }); @@ -416,7 +423,7 @@ fn set_item_metadata_should_work() { // Cannot add metadata to unknown item assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false), @@ -554,12 +561,12 @@ fn set_attribute_should_respect_freeze() { assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 0, bvec![], true)); - let e = Error::::Frozen; + /*let e = Error::::Frozen; assert_noop!( Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), e ); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1]));*/ }); } @@ -569,8 +576,8 @@ fn force_collection_status_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); @@ -586,8 +593,8 @@ fn force_collection_status_should_work() { 1, CollectionConfig(CollectionSetting::FreeHolding.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); @@ -618,8 +625,8 @@ fn burn_works() { Error::::UnknownCollection ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); assert_eq!(Balances::reserved_balance(1), 2); assert_noop!( @@ -641,7 +648,7 @@ fn burn_works() { fn approval_lifecycle_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_noop!( @@ -663,7 +670,13 @@ fn approval_lifecycle_works() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(1), + 1, + collection_id, + 1, + default_item_config() + )); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -676,7 +689,7 @@ fn approval_lifecycle_works() { fn cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -704,7 +717,7 @@ fn cancel_approval_works() { let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); // approval expires after 2 blocks. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( @@ -723,7 +736,7 @@ fn cancel_approval_works() { fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); let current_block = 1; System::set_block_number(current_block); @@ -748,7 +761,7 @@ fn approving_multiple_accounts_works() { fn approvals_limit_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); for i in 3..13 { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); @@ -772,7 +785,7 @@ fn approval_deadline_works() { 1, CollectionConfig(CollectionSetting::FreeHolding.into()) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); // the approval expires after the 2nd block. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); @@ -799,7 +812,7 @@ fn approval_deadline_works() { fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -827,7 +840,7 @@ fn cancel_approval_works_with_admin() { fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -855,7 +868,7 @@ fn cancel_approval_works_with_force() { fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); @@ -918,10 +931,28 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 0, + user_id, + default_item_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 1, + user_id, + default_item_config() + )); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), + Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + 2, + user_id, + default_item_config() + ), Error::::MaxSupplyReached ); @@ -945,8 +976,20 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + default_item_config() + )); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1003,7 +1046,13 @@ fn set_price_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config() + )); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1032,9 +1081,27 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + user_1, + default_item_config(), + )); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1152,7 +1219,7 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::Frozen + Error::::Locked ); } }); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index c2517adbc7f30..a3c147760a6e8 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -94,8 +94,6 @@ pub struct ItemDetails { pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. pub(super) approvals: Approvals, - /// Whether the item can be transferred or not. - pub(super) is_frozen: bool, /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. pub(super) deposit: DepositBalance, @@ -204,6 +202,65 @@ impl TypeInfo for CollectionConfig { } } +// Support for up to 64 user-enabled features on an item. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum ItemSetting { + /// Disallow transferring this item. + NonTransferable, + /// Disallow to modify metadata of this item. + LockedMetadata, + /// Disallow to modify attributes of this item. + LockedAttributes, +} + +pub(super) type ItemSettings = BitFlags; + +/// Wrapper type for `ItemSettings` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct ItemConfig(pub ItemSettings); + +impl ItemConfig { + pub fn empty() -> Self { + Self(BitFlags::EMPTY) + } + + pub fn values(&self) -> ItemSettings { + self.0 + } +} + +impl MaxEncodedLen for ItemConfig { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +impl Encode for ItemConfig { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl EncodeLike for ItemConfig {} +impl Decode for ItemConfig { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self(ItemSettings::from_bits(field as u64).map_err(|_| "invalid value")?)) + } +} + +impl TypeInfo for ItemConfig { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("ItemSetting"))) + } +} + // Support for up to 64 system-enabled features on a collection. #[bitflags] #[repr(u64)] diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index fe0d2e729930e..f46979a9abad9 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -74,11 +74,11 @@ pub trait InspectEnumerable: Inspect { /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -158,26 +158,37 @@ impl< } impl< - F: nonfungibles::Mutate, + F: nonfungibles::Mutate, A: Get<>::CollectionId>, AccountId, - > Mutate for ItemOf + ItemConfig, + > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { - >::mint_into(&A::get(), item, who) + fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { + >::mint_into(&A::get(), item, who, config) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), item, maybe_check_owner) + >::burn(&A::get(), item, maybe_check_owner) } fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute(&A::get(), item, key, value) + >::set_attribute( + &A::get(), + item, + key, + value, + ) } fn set_typed_attribute( item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute(&A::get(), item, key, value) + >::set_typed_attribute( + &A::get(), + item, + key, + value, + ) } } diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index edad84e7a2179..d23e6d67573c7 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -159,7 +159,7 @@ pub trait Destroy: Inspect { /// Trait for providing an interface for multiple collections of NFT-like items which may be /// minted, burned and/or have attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. @@ -167,6 +167,7 @@ pub trait Mutate: Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _who: &AccountId, + _config: &ItemConfig, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } From 08a20480fcfe8c08979abaf66041336ceffa19bb Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 14:47:50 +0300 Subject: [PATCH 26/94] Allow to lock the metadata or attributes of an item --- frame/nfts/src/benchmarking.rs | 4 +- frame/nfts/src/lib.rs | 102 ++++++++++++++++++++++++++------- frame/nfts/src/tests.rs | 67 ++++++++++------------ frame/nfts/src/types.rs | 2 - 4 files changed, 114 insertions(+), 61 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 8ce967b476dec..dbb257b74c5ad 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -322,9 +322,9 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection, item, data.clone()) verify { - assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { collection, item, data }.into()); } clear_metadata { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 66d41150ca0d8..cff0500e84ecc 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -357,7 +357,14 @@ pub mod pallet { Frozen { collection: T::CollectionId, item: T::ItemId }, /// Some `item` was thawed. Thawed { collection: T::CollectionId, item: T::ItemId }, - /// Some `collection` was frozen. + /// Some `item` was locked. + ItemLocked { + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + }, + /// Some `collection` was locked. CollectionLocked { collection: T::CollectionId }, /// The owner changed. OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, @@ -398,7 +405,6 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, - is_frozen: bool, }, /// Metadata has been cleared for an item. MetadataCleared { collection: T::CollectionId, item: T::ItemId }, @@ -1226,6 +1232,61 @@ pub mod pallet { }) } + /// Disallows changing the metadata of attributes of the item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// - `collection`: The collection if the `item`. + /// - `item`: An item to be locked. + /// - `lock_config`: The config with the settings to be locked. + /// + /// Note: when the metadata or attributes are locked, it won't be possible the unlock them. + /// Emits `ItemLocked`. + /// + /// Weight: `O(1)` + #[pallet::weight(0)] + pub fn lock_item( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; + let mut settings = config.values(); + + if lock_metadata { + settings.insert(ItemSetting::LockedMetadata); + } + if lock_attributes { + settings.insert(ItemSetting::LockedAttributes); + } + + config.0 = settings; + + Self::deposit_event(Event::::ItemLocked { + collection, + item, + lock_metadata, + lock_attributes, + }); + Ok(()) + }) + } + /// Set an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the @@ -1270,9 +1331,9 @@ pub mod pallet { let maybe_is_frozen = match maybe_item { None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), Some(item) => Self::get_item_settings(&collection, &item) - .map(|v| v.contains(ItemSetting::NonTransferable)), - }; - ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); + .map(|v| v.contains(ItemSetting::LockedAttributes)), + }?; + ensure!(!maybe_is_frozen, Error::::Frozen); let attribute = Attribute::::get((collection, maybe_item, &key)); if attribute.is_none() { @@ -1340,9 +1401,9 @@ pub mod pallet { let maybe_is_frozen = match maybe_item { None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), Some(item) => Self::get_item_settings(&collection, &item) - .map(|v| v.contains(ItemSetting::NonTransferable)), - }; - ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); + .map(|v| v.contains(ItemSetting::LockedAttributes)), + }?; + ensure!(!maybe_is_frozen, Error::::Frozen); if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); @@ -1366,7 +1427,6 @@ pub mod pallet { /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `item`: The identifier of the item whose metadata to set. /// - `data`: The general information of this item. Limited in length by `StringLimit`. - /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `MetadataSet`. /// @@ -1377,7 +1437,6 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, - is_frozen: bool, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1386,23 +1445,25 @@ pub mod pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let settings = Self::get_collection_settings(&collection)?; + let (action_allowed, _) = + Self::is_item_setting_disabled(&collection, &item, ItemSetting::LockedMetadata)?; + ensure!(maybe_check_owner.is_none() || action_allowed, Error::::Frozen); + + let collection_settings = Self::get_collection_settings(&collection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); - if metadata.is_none() { collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !settings.contains(CollectionSetting::FreeHolding) && maybe_check_owner.is_some() + if !collection_settings.contains(CollectionSetting::FreeHolding) && + maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) @@ -1415,10 +1476,10 @@ pub mod pallet { } collection_details.total_deposit.saturating_accrue(deposit); - *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen }); + Self::deposit_event(Event::MetadataSet { collection, item, data }); Ok(()) }) } @@ -1452,10 +1513,11 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); - ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); + let (action_allowed, _) = + Self::is_item_setting_disabled(&collection, &item, ItemSetting::LockedMetadata)?; + ensure!(maybe_check_owner.is_none() || action_allowed, Error::::Frozen); + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { if metadata.is_some() { collection_details.item_metadatas.saturating_dec(); } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 0da5b1b8ab03d..8f596cf78c9b8 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -137,10 +137,10 @@ fn lifecycle_should_work() { assert_eq!(Collection::::get(0).unwrap().items, 2); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69])); assert_eq!(Balances::reserved_balance(&1), 13); assert!(ItemMetadataOf::::contains_key(0, 69)); @@ -313,7 +313,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); @@ -426,40 +426,41 @@ fn set_item_metadata_should_work() { assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false), + Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20])); assert_eq!(Balances::free_balance(&1), 8); assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. - assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25])); assert_eq!(Balances::free_balance(&1), 3); // Cannot over-reserve assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40], false), + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); + assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 42, true, false)); assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false), + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15]), Error::::Frozen, ); assert_noop!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), Error::::Frozen); // Clear Metadata - assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); assert_noop!( Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission @@ -468,19 +469,8 @@ fn set_item_metadata_should_work() { Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), Error::::UnknownCollection ); - assert_ok!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); - - // collection's metadata can't be changed after the collection gets locked - /*assert_ok!(Nfts::change_collection_config( - Origin::signed(1), - 0, - CollectionConfig(CollectionSetting::LockedMetadata.into()) - )); - assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), - Error::::CollectionIsLocked - );*/ }); } @@ -490,6 +480,7 @@ fn set_attribute_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -502,7 +493,7 @@ fn set_attribute_should_work() { (Some(0), bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(Balances::reserved_balance(1), 10); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0; 10])); assert_eq!( @@ -513,14 +504,14 @@ fn set_attribute_should_work() { (Some(0), bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 18); + assert_eq!(Balances::reserved_balance(1), 19); assert_ok!(Nfts::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] ); - assert_eq!(Balances::reserved_balance(1), 15); + assert_eq!(Balances::reserved_balance(1), 16); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); @@ -535,6 +526,8 @@ fn set_attribute_should_respect_freeze() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -547,7 +540,7 @@ fn set_attribute_should_respect_freeze() { (Some(1), bvec![0], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(Balances::reserved_balance(1), 11); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![])); assert_ok!(Nfts::lock_collection( @@ -560,13 +553,13 @@ fn set_attribute_should_respect_freeze() { assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 0, bvec![], true)); - /*let e = Error::::Frozen; + assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 0, false, true)); + let e = Error::::Frozen; assert_noop!( Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), e ); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1]));*/ + assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1])); }); } @@ -579,8 +572,8 @@ fn force_collection_status_should_work() { assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding @@ -595,17 +588,17 @@ fn force_collection_status_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); assert_eq!(Balances::reserved_balance(1), 63); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 42); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 21); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index a3c147760a6e8..a5b49c653be96 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -125,8 +125,6 @@ pub struct ItemMetadata> { /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the item metadata may be changed by a non Force origin. - pub(super) is_frozen: bool, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] From 155171408a7a411b7bc34cf6bb5b949de71851dc Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 14:48:53 +0300 Subject: [PATCH 27/94] Common -> Settings --- frame/nfts/src/features/mod.rs | 2 +- frame/nfts/src/features/{common.rs => settings.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename frame/nfts/src/features/{common.rs => settings.rs} (100%) diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index f4a455d772c94..9d16476251524 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -16,4 +16,4 @@ // limitations under the License. pub mod buy_sell; -pub mod common; +pub mod settings; diff --git a/frame/nfts/src/features/common.rs b/frame/nfts/src/features/settings.rs similarity index 100% rename from frame/nfts/src/features/common.rs rename to frame/nfts/src/features/settings.rs From 8830a4c3c8f9729252e5de769f4d3ba20b354c5a Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 27 Sep 2022 15:04:35 +0300 Subject: [PATCH 28/94] Extract settings related code to a separate file --- frame/nfts/src/features/settings.rs | 78 +++++++++++++++++++++++++++++ frame/nfts/src/lib.rs | 61 ++-------------------- 2 files changed, 82 insertions(+), 57 deletions(-) diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index b4f7bff165597..d43d8bfebbfe6 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -19,6 +19,84 @@ use crate::*; use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { + pub fn do_lock_collection( + collection: T::CollectionId, + lock_config: CollectionConfig, + ) -> DispatchResult { + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; + let mut settings = config.values(); + let lock_settings = lock_config.values(); + + if lock_settings.contains(CollectionSetting::NonTransferableItems) { + settings.insert(CollectionSetting::NonTransferableItems); + } + if lock_settings.contains(CollectionSetting::LockedMetadata) { + settings.insert(CollectionSetting::LockedMetadata); + } + if lock_settings.contains(CollectionSetting::LockedAttributes) { + settings.insert(CollectionSetting::LockedAttributes); + } + + config.0 = settings; + + Self::deposit_event(Event::::CollectionLocked { collection }); + Ok(()) + }) + } + + pub fn do_lock_item( + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; + let mut settings = config.values(); + + if lock_metadata { + settings.insert(ItemSetting::LockedMetadata); + } + if lock_attributes { + settings.insert(ItemSetting::LockedAttributes); + } + + config.0 = settings; + + Self::deposit_event(Event::::ItemLocked { + collection, + item, + lock_metadata, + lock_attributes, + }); + Ok(()) + }) + } + + pub fn do_freeze_item(collection: T::CollectionId, item: T::ItemId) -> DispatchResult { + let mut settings = Self::get_item_settings(&collection, &item)?; + if !settings.contains(ItemSetting::NonTransferable) { + settings.insert(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + + Self::deposit_event(Event::::Frozen { collection, item }); + Ok(()) + } + + pub fn do_thaw_item(collection: T::CollectionId, item: T::ItemId) -> DispatchResult { + let mut settings = Self::get_item_settings(&collection, &item)?; + if settings.contains(ItemSetting::NonTransferable) { + settings.remove(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + + Self::deposit_event(Event::::Thawed { collection, item }); + Ok(()) + } + + // helpers pub fn get_collection_settings( collection_id: &T::CollectionId, ) -> Result { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index cff0500e84ecc..3c3e4c00b6278 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -834,14 +834,7 @@ pub mod pallet { Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.freezer == origin, Error::::NoPermission); - let mut settings = Self::get_item_settings(&collection, &item)?; - if !settings.contains(ItemSetting::NonTransferable) { - settings.insert(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); - - Self::deposit_event(Event::::Frozen { collection, item }); - Ok(()) + Self::do_freeze_item(collection, item) } /// Re-allow unprivileged transfer of an item. @@ -866,14 +859,7 @@ pub mod pallet { Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.freezer == origin, Error::::NoPermission); - let mut settings = Self::get_item_settings(&collection, &item)?; - if settings.contains(ItemSetting::NonTransferable) { - settings.remove(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); - - Self::deposit_event(Event::::Thawed { collection, item }); - Ok(()) + Self::do_thaw_item(collection, item) } /// Disallows specified settings for the whole collection. @@ -899,26 +885,7 @@ pub mod pallet { Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(origin == details.freezer, Error::::NoPermission); - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; - let mut settings = config.values(); - let lock_settings = lock_config.values(); - - if lock_settings.contains(CollectionSetting::NonTransferableItems) { - settings.insert(CollectionSetting::NonTransferableItems); - } - if lock_settings.contains(CollectionSetting::LockedMetadata) { - settings.insert(CollectionSetting::LockedMetadata); - } - if lock_settings.contains(CollectionSetting::LockedAttributes) { - settings.insert(CollectionSetting::LockedAttributes); - } - - config.0 = settings; - - Self::deposit_event(Event::::CollectionLocked { collection }); - Ok(()) - }) + Self::do_lock_collection(collection, lock_config) } /// Change the Owner of a collection. @@ -1264,27 +1231,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemConfigOf::::try_mutate(collection, item, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; - let mut settings = config.values(); - - if lock_metadata { - settings.insert(ItemSetting::LockedMetadata); - } - if lock_attributes { - settings.insert(ItemSetting::LockedAttributes); - } - - config.0 = settings; - - Self::deposit_event(Event::::ItemLocked { - collection, - item, - lock_metadata, - lock_attributes, - }); - Ok(()) - }) + Self::do_lock_item(collection, item, lock_metadata, lock_attributes) } /// Set an attribute for a collection or item. From f8800dffedff40145bd0b5c003b8e3cd4a9cbb22 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 28 Sep 2022 10:29:29 +0300 Subject: [PATCH 29/94] Move feature flag checks inside the do_* methods --- frame/nfts/src/features/settings.rs | 33 +++++++++++++++++++++++-- frame/nfts/src/functions.rs | 10 ++++++++ frame/nfts/src/lib.rs | 38 +++-------------------------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index d43d8bfebbfe6..6385f339ab1b5 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -20,9 +20,14 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { pub fn do_lock_collection( + origin: T::AccountId, collection: T::CollectionId, lock_config: CollectionConfig, ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); + CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; let mut settings = config.values(); @@ -46,11 +51,19 @@ impl, I: 'static> Pallet { } pub fn do_lock_item( + maybe_check_owner: Option, collection: T::CollectionId, item: T::ItemId, lock_metadata: bool, lock_attributes: bool, ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; let mut settings = config.values(); @@ -74,7 +87,15 @@ impl, I: 'static> Pallet { }) } - pub fn do_freeze_item(collection: T::CollectionId, item: T::ItemId) -> DispatchResult { + pub fn do_freeze_item( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + let mut settings = Self::get_item_settings(&collection, &item)?; if !settings.contains(ItemSetting::NonTransferable) { settings.insert(ItemSetting::NonTransferable); @@ -85,7 +106,15 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn do_thaw_item(collection: T::CollectionId, item: T::ItemId) -> DispatchResult { + pub fn do_thaw_item( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + let mut settings = Self::get_item_settings(&collection, &item)?; if settings.contains(ItemSetting::NonTransferable) { settings.remove(ItemSetting::NonTransferable); diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index a3afa50c32321..e952dc04cd68a 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -242,6 +242,11 @@ impl, I: 'static> Pallet { price: Option>, whitelisted_buyer: Option, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoTrading), + Error::::MethodDisabled + ); + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); @@ -277,6 +282,11 @@ impl, I: 'static> Pallet { buyer: T::AccountId, bid_price: ItemPrice, ) -> DispatchResult { + ensure!( + !Self::is_feature_flag_set(SystemFeature::NoTrading), + Error::::MethodDisabled + ); + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; ensure!(details.owner != buyer, Error::::NoPermission); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 3c3e4c00b6278..d72d200b29588 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -829,12 +829,7 @@ pub mod pallet { item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - Self::do_freeze_item(collection, item) + Self::do_freeze_item(origin, collection, item) } /// Re-allow unprivileged transfer of an item. @@ -854,12 +849,7 @@ pub mod pallet { item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - Self::do_thaw_item(collection, item) + Self::do_thaw_item(origin, collection, item) } /// Disallows specified settings for the whole collection. @@ -880,12 +870,7 @@ pub mod pallet { lock_config: CollectionConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.freezer, Error::::NoPermission); - - Self::do_lock_collection(collection, lock_config) + Self::do_lock_collection(origin, collection, lock_config) } /// Change the Owner of a collection. @@ -1224,14 +1209,7 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - Self::do_lock_item(collection, item, lock_metadata, lock_attributes) + Self::do_lock_item(maybe_check_owner, collection, item, lock_metadata, lock_attributes) } /// Set an attribute for a collection or item. @@ -1682,10 +1660,6 @@ pub mod pallet { whitelisted_buyer: Option>, ) -> DispatchResult { let origin = ensure_signed(origin)?; - ensure!( - !Self::is_feature_flag_set(SystemFeature::NoTrading), - Error::::MethodDisabled - ); let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; Self::do_set_price(collection, item, origin, price, whitelisted_buyer) } @@ -1708,10 +1682,6 @@ pub mod pallet { bid_price: ItemPrice, ) -> DispatchResult { let origin = ensure_signed(origin)?; - ensure!( - !Self::is_feature_flag_set(SystemFeature::NoTrading), - Error::::MethodDisabled - ); Self::do_buy_item(collection, item, origin, bid_price) } From 3ccf04ef5f7205839122deb7901508f3024c9f4e Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 28 Sep 2022 10:32:03 +0300 Subject: [PATCH 30/94] Split settings.rs into parts --- frame/nfts/src/features/freeze.rs | 59 +++++++++++++++ frame/nfts/src/features/lock.rs | 89 +++++++++++++++++++++++ frame/nfts/src/features/mod.rs | 2 + frame/nfts/src/features/settings.rs | 107 ---------------------------- 4 files changed, 150 insertions(+), 107 deletions(-) create mode 100644 frame/nfts/src/features/freeze.rs create mode 100644 frame/nfts/src/features/lock.rs diff --git a/frame/nfts/src/features/freeze.rs b/frame/nfts/src/features/freeze.rs new file mode 100644 index 0000000000000..6884911321ab5 --- /dev/null +++ b/frame/nfts/src/features/freeze.rs @@ -0,0 +1,59 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_freeze_item( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + + let mut settings = Self::get_item_settings(&collection, &item)?; + if !settings.contains(ItemSetting::NonTransferable) { + settings.insert(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + + Self::deposit_event(Event::::Frozen { collection, item }); + Ok(()) + } + + pub fn do_thaw_item( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); + + let mut settings = Self::get_item_settings(&collection, &item)?; + if settings.contains(ItemSetting::NonTransferable) { + settings.remove(ItemSetting::NonTransferable); + } + ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + + Self::deposit_event(Event::::Thawed { collection, item }); + Ok(()) + } +} diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs new file mode 100644 index 0000000000000..2721d64ef11c3 --- /dev/null +++ b/frame/nfts/src/features/lock.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_lock_collection( + origin: T::AccountId, + collection: T::CollectionId, + lock_config: CollectionConfig, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; + let mut settings = config.values(); + let lock_settings = lock_config.values(); + + if lock_settings.contains(CollectionSetting::NonTransferableItems) { + settings.insert(CollectionSetting::NonTransferableItems); + } + if lock_settings.contains(CollectionSetting::LockedMetadata) { + settings.insert(CollectionSetting::LockedMetadata); + } + if lock_settings.contains(CollectionSetting::LockedAttributes) { + settings.insert(CollectionSetting::LockedAttributes); + } + + config.0 = settings; + + Self::deposit_event(Event::::CollectionLocked { collection }); + Ok(()) + }) + } + + pub fn do_lock_item( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; + let mut settings = config.values(); + + if lock_metadata { + settings.insert(ItemSetting::LockedMetadata); + } + if lock_attributes { + settings.insert(ItemSetting::LockedAttributes); + } + + config.0 = settings; + + Self::deposit_event(Event::::ItemLocked { + collection, + item, + lock_metadata, + lock_attributes, + }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 9d16476251524..11e424f813b2c 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -16,4 +16,6 @@ // limitations under the License. pub mod buy_sell; +pub mod freeze; +pub mod lock; pub mod settings; diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 6385f339ab1b5..b4f7bff165597 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -19,113 +19,6 @@ use crate::*; use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { - pub fn do_lock_collection( - origin: T::AccountId, - collection: T::CollectionId, - lock_config: CollectionConfig, - ) -> DispatchResult { - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.freezer, Error::::NoPermission); - - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; - let mut settings = config.values(); - let lock_settings = lock_config.values(); - - if lock_settings.contains(CollectionSetting::NonTransferableItems) { - settings.insert(CollectionSetting::NonTransferableItems); - } - if lock_settings.contains(CollectionSetting::LockedMetadata) { - settings.insert(CollectionSetting::LockedMetadata); - } - if lock_settings.contains(CollectionSetting::LockedAttributes) { - settings.insert(CollectionSetting::LockedAttributes); - } - - config.0 = settings; - - Self::deposit_event(Event::::CollectionLocked { collection }); - Ok(()) - }) - } - - pub fn do_lock_item( - maybe_check_owner: Option, - collection: T::CollectionId, - item: T::ItemId, - lock_metadata: bool, - lock_attributes: bool, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - ItemConfigOf::::try_mutate(collection, item, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; - let mut settings = config.values(); - - if lock_metadata { - settings.insert(ItemSetting::LockedMetadata); - } - if lock_attributes { - settings.insert(ItemSetting::LockedAttributes); - } - - config.0 = settings; - - Self::deposit_event(Event::::ItemLocked { - collection, - item, - lock_metadata, - lock_attributes, - }); - Ok(()) - }) - } - - pub fn do_freeze_item( - origin: T::AccountId, - collection: T::CollectionId, - item: T::ItemId, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - let mut settings = Self::get_item_settings(&collection, &item)?; - if !settings.contains(ItemSetting::NonTransferable) { - settings.insert(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); - - Self::deposit_event(Event::::Frozen { collection, item }); - Ok(()) - } - - pub fn do_thaw_item( - origin: T::AccountId, - collection: T::CollectionId, - item: T::ItemId, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - let mut settings = Self::get_item_settings(&collection, &item)?; - if settings.contains(ItemSetting::NonTransferable) { - settings.remove(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); - - Self::deposit_event(Event::::Thawed { collection, item }); - Ok(()) - } - - // helpers pub fn get_collection_settings( collection_id: &T::CollectionId, ) -> Result { From f83fb734098aa390766f680fe9fb3d63a285e9df Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 28 Sep 2022 11:49:15 +0300 Subject: [PATCH 31/94] Extract repeated code into macro --- frame/nfts/src/types.rs | 137 ++++++++++++---------------------------- 1 file changed, 41 insertions(+), 96 deletions(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index a5b49c653be96..56192233be76f 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -139,6 +139,44 @@ pub struct ItemTip { pub(super) amount: Amount, } +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl EncodeLike for $wrapper {} + impl Decode for $wrapper { + fn decode( + input: &mut I, + ) -> sp_std::result::Result { + let field = <$size>::decode(input)?; + Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) + } + } + + impl TypeInfo for $wrapper { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))]) + .composite( + Fields::unnamed() + .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), + ) + } + } + }; +} + // Support for up to 64 user-enabled features on a collection. #[bitflags] #[repr(u64)] @@ -153,7 +191,6 @@ pub enum CollectionSetting { /// When is set then no deposit needed to hold items of this collection. FreeHolding, } - pub(super) type CollectionSettings = BitFlags; /// Wrapper type for `CollectionSettings` that implements `Codec`. @@ -164,41 +201,11 @@ impl CollectionConfig { pub fn empty() -> Self { Self(BitFlags::EMPTY) } - pub fn values(&self) -> CollectionSettings { self.0 } } - -impl MaxEncodedLen for CollectionConfig { - fn max_encoded_len() -> usize { - u64::max_encoded_len() - } -} - -impl Encode for CollectionConfig { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl EncodeLike for CollectionConfig {} -impl Decode for CollectionConfig { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u64::decode(input)?; - Ok(Self(CollectionSettings::from_bits(field as u64).map_err(|_| "invalid value")?)) - } -} - -impl TypeInfo for CollectionConfig { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("CollectionSetting"))) - } -} +impl_codec_bitflags!(CollectionConfig, u64, CollectionSetting); // Support for up to 64 user-enabled features on an item. #[bitflags] @@ -212,52 +219,20 @@ pub enum ItemSetting { /// Disallow to modify attributes of this item. LockedAttributes, } - pub(super) type ItemSettings = BitFlags; /// Wrapper type for `ItemSettings` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] pub struct ItemConfig(pub ItemSettings); - impl ItemConfig { pub fn empty() -> Self { Self(BitFlags::EMPTY) } - pub fn values(&self) -> ItemSettings { self.0 } } - -impl MaxEncodedLen for ItemConfig { - fn max_encoded_len() -> usize { - u64::max_encoded_len() - } -} - -impl Encode for ItemConfig { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl EncodeLike for ItemConfig {} -impl Decode for ItemConfig { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u64::decode(input)?; - Ok(Self(ItemSettings::from_bits(field as u64).map_err(|_| "invalid value")?)) - } -} - -impl TypeInfo for ItemConfig { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("ItemSetting"))) - } -} +impl_codec_bitflags!(ItemConfig, u64, ItemSetting); // Support for up to 64 system-enabled features on a collection. #[bitflags] @@ -275,39 +250,9 @@ pub enum SystemFeature { /// Disallow public mints. NoPublicMints, } - pub type SystemFeatureFlags = BitFlags; /// Wrapper type for `SystemFeatureFlags` that implements `Codec`. #[derive(Default, RuntimeDebug)] pub struct SystemFeatures(pub SystemFeatureFlags); - -impl MaxEncodedLen for SystemFeatures { - fn max_encoded_len() -> usize { - u64::max_encoded_len() - } -} - -impl Encode for SystemFeatures { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl EncodeLike for SystemFeatures {} -impl Decode for SystemFeatures { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u64::decode(input)?; - Ok(Self(SystemFeatureFlags::from_bits(field as u64).map_err(|_| "invalid value")?)) - } -} - -impl TypeInfo for SystemFeatures { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("SystemFeature"))) - } -} +impl_codec_bitflags!(SystemFeatures, u64, SystemFeature); From df97cce39c123b7f7b43ab82f3d2b6d36317d573 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 28 Sep 2022 11:49:45 +0300 Subject: [PATCH 32/94] Extract macros into their own file --- frame/nfts/src/features/macros.rs | 72 +++++++++++++++++++++++++++++++ frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/lib.rs | 23 ---------- frame/nfts/src/types.rs | 45 +++---------------- 4 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 frame/nfts/src/features/macros.rs diff --git a/frame/nfts/src/features/macros.rs b/frame/nfts/src/features/macros.rs new file mode 100644 index 0000000000000..d006e82bd2bea --- /dev/null +++ b/frame/nfts/src/features/macros.rs @@ -0,0 +1,72 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +macro_rules! impl_incrementable { + ($($type:ty),+) => { + $( + impl Incrementable for $type { + fn increment(&self) -> Self { + self.saturating_add(1) + } + + fn initial_value() -> Self { + 0 + } + } + )+ + }; +} +pub(crate) use impl_incrementable; + +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl EncodeLike for $wrapper {} + impl Decode for $wrapper { + fn decode( + input: &mut I, + ) -> sp_std::result::Result { + let field = <$size>::decode(input)?; + Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) + } + } + + impl TypeInfo for $wrapper { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))]) + .composite( + Fields::unnamed() + .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), + ) + } + } + }; +} +pub(crate) use impl_codec_bitflags; diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 11e424f813b2c..0657968b07673 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -18,4 +18,5 @@ pub mod buy_sell; pub mod freeze; pub mod lock; +pub mod macros; pub mod settings; diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index d72d200b29588..f39c53ad9a32d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -62,29 +62,6 @@ pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -pub trait Incrementable { - fn increment(&self) -> Self; - fn initial_value() -> Self; -} - -macro_rules! impl_incrementable { - ($($type:ty),+) => { - $( - impl Incrementable for $type { - fn increment(&self) -> Self { - self.saturating_add(1) - } - - fn initial_value() -> Self { - 0 - } - } - )+ - }; -} - -impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); - #[frame_support::pallet] pub mod pallet { use super::*; diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 56192233be76f..d3d7c4069c99a 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -18,6 +18,7 @@ //! Various basic types for use in the Nfts pallet. use super::*; +use crate::features::macros::*; use codec::EncodeLike; use enumflags2::{bitflags, BitFlags}; use frame_support::{ @@ -42,6 +43,12 @@ pub(super) type ItemTipOf = ItemTip< BalanceOf, >; +pub trait Incrementable { + fn increment(&self) -> Self; + fn initial_value() -> Self; +} +impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. @@ -139,44 +146,6 @@ pub struct ItemTip { pub(super) amount: Amount, } -macro_rules! impl_codec_bitflags { - ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { - impl MaxEncodedLen for $wrapper { - fn max_encoded_len() -> usize { - <$size>::max_encoded_len() - } - } - impl Encode for $wrapper { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } - } - impl EncodeLike for $wrapper {} - impl Decode for $wrapper { - fn decode( - input: &mut I, - ) -> sp_std::result::Result { - let field = <$size>::decode(input)?; - Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) - } - } - - impl TypeInfo for $wrapper { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))]) - .composite( - Fields::unnamed() - .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), - ) - } - } - }; -} - // Support for up to 64 user-enabled features on a collection. #[bitflags] #[repr(u64)] From 3bb6eb5ccb26c70ba1787ab5cea3f890ad1329bf Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 28 Sep 2022 12:03:50 +0300 Subject: [PATCH 33/94] Chore --- frame/nfts/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index d3d7c4069c99a..2774d2a6d1445 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -193,6 +193,7 @@ pub(super) type ItemSettings = BitFlags; /// Wrapper type for `ItemSettings` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] pub struct ItemConfig(pub ItemSettings); + impl ItemConfig { pub fn empty() -> Self { Self(BitFlags::EMPTY) From 8563a312a8998caec4498b78183ea19ac1335d75 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:30:04 +0300 Subject: [PATCH 34/94] Fix traits --- frame/support/src/traits/tokens.rs | 2 + .../support/src/traits/tokens/nonfungible.rs | 29 +-- .../src/traits/tokens/nonfungible_v2.rs | 204 +++++++++++++++ .../support/src/traits/tokens/nonfungibles.rs | 6 +- .../src/traits/tokens/nonfungibles_v2.rs | 243 ++++++++++++++++++ 5 files changed, 460 insertions(+), 24 deletions(-) create mode 100644 frame/support/src/traits/tokens/nonfungible_v2.rs create mode 100644 frame/support/src/traits/tokens/nonfungibles_v2.rs diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 77eb83adfbfb0..b3b3b4b7d90b1 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -23,7 +23,9 @@ pub mod fungibles; pub mod imbalance; mod misc; pub mod nonfungible; +pub mod nonfungible_v2; pub mod nonfungibles; +pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index f46979a9abad9..fe0d2e729930e 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -74,11 +74,11 @@ pub trait InspectEnumerable: Inspect { /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -158,37 +158,26 @@ impl< } impl< - F: nonfungibles::Mutate, + F: nonfungibles::Mutate, A: Get<>::CollectionId>, AccountId, - ItemConfig, - > Mutate for ItemOf + > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { - >::mint_into(&A::get(), item, who, config) + fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { + >::mint_into(&A::get(), item, who) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), item, maybe_check_owner) + >::burn(&A::get(), item, maybe_check_owner) } fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute( - &A::get(), - item, - key, - value, - ) + >::set_attribute(&A::get(), item, key, value) } fn set_typed_attribute( item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute( - &A::get(), - item, - key, - value, - ) + >::set_typed_attribute(&A::get(), item, key, value) } } diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs new file mode 100644 index 0000000000000..850195852cf72 --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -0,0 +1,204 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for dealing with a single non-fungible collection of items. +//! +//! This assumes a single level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets which wants to expose a single collection of NFT-like +//! objects. +//! +//! For an NFT API which has dual-level namespacing, the traits in `nonfungibles` are better to +//! use. + +use super::nonfungibles_v2 as nonfungibles; +use crate::{dispatch::DispatchResult, traits::Get}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to a read-only NFT-like set of items. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Returns the owner of `item`, or `None` if the item doesn't exist or has no + /// owner. + fn owner(item: &Self::ItemId) -> Option; + + /// Returns the attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns `true` if the `item` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(_item: &Self::ItemId) -> bool { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over a collection +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// Returns an iterator of the items within a `collection` in existence. + fn items() -> Box>; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; +} + +/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have +/// attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item`. + /// + /// By default, this is not a supported operation. + fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) + } +} + +/// Trait for providing a non-fungible set of items which can only be transferred. +pub trait Transfer: Inspect { + /// Transfer `item` into `destination` account. + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; +} + +/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying +/// a single item. +pub struct ItemOf< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, +>(sp_std::marker::PhantomData<(F, A, AccountId)>); + +impl< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, + > Inspect for ItemOf +{ + type ItemId = >::ItemId; + fn owner(item: &Self::ItemId) -> Option { + >::owner(&A::get(), item) + } + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) + } + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) + } + fn can_transfer(item: &Self::ItemId) -> bool { + >::can_transfer(&A::get(), item) + } +} + +impl< + F: nonfungibles::InspectEnumerable, + A: Get<>::CollectionId>, + AccountId, + > InspectEnumerable for ItemOf +{ + fn items() -> Box> { + >::items(&A::get()) + } + fn owned(who: &AccountId) -> Box> { + >::owned_in_collection(&A::get(), who) + } +} + +impl< + F: nonfungibles::Mutate, + A: Get<>::CollectionId>, + AccountId, + ItemConfig, + > Mutate for ItemOf +{ + fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { + >::mint_into(&A::get(), item, who, config) + } + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), item, maybe_check_owner) + } + fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { + >::set_attribute( + &A::get(), + item, + key, + value, + ) + } + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + >::set_typed_attribute( + &A::get(), + item, + key, + value, + ) + } +} + +impl< + F: nonfungibles::Transfer, + A: Get<>::CollectionId>, + AccountId, + > Transfer for ItemOf +{ + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { + >::transfer(&A::get(), item, destination) + } +} diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index d23e6d67573c7..d043a87ce7c10 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -122,13 +122,12 @@ pub trait InspectEnumerable: Inspect { } /// Trait for providing the ability to create collections of nonfungible items. -pub trait Create: Inspect { +pub trait Create: Inspect { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. fn create_collection( collection: &Self::CollectionId, who: &AccountId, admin: &AccountId, - config: &CollectionConfig, ) -> DispatchResult; } @@ -159,7 +158,7 @@ pub trait Destroy: Inspect { /// Trait for providing an interface for multiple collections of NFT-like items which may be /// minted, burned and/or have attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. @@ -167,7 +166,6 @@ pub trait Mutate: Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _who: &AccountId, - _config: &ItemConfig, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs new file mode 100644 index 0000000000000..d23e6d67573c7 --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for dealing with multiple collections of non-fungible items. +//! +//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets which want to expose multiple independent collections of +//! NFT-like objects. +//! +//! For an NFT API which has single-level namespacing, the traits in `nonfungible` are better to +//! use. +//! +//! Implementations of these traits may be converted to implementations of corresponding +//! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. + +use crate::dispatch::{DispatchError, DispatchResult}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to many read-only NFT-like sets of items. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Type for identifying a collection (an identifier for an independent collection of + /// items). + type CollectionId; + + /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist + /// (or somehow has no owner). + fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option; + + /// Returns the owner of the `collection`, if there is one. For many NFTs this may not + /// make any sense, so users of this API should not be surprised to find a collection + /// results in `None` here. + fn collection_owner(_collection: &Self::CollectionId) -> Option { + None + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::attribute(collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the attribute value of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `collection` corresponding to `key`. + /// + /// By default this just attempts to use `collection_attribute`. + fn typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::collection_attribute(collection, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns `true` if the `item` of `collection` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over many collections +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// Returns an iterator of the collections in existence. + fn collections() -> Box>; + + /// Returns an iterator of the items of a `collection` in existence. + fn items(collection: &Self::CollectionId) -> Box>; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; + + /// Returns an iterator of the items of `collection` owned by `who`. + fn owned_in_collection( + collection: &Self::CollectionId, + who: &AccountId, + ) -> Box>; +} + +/// Trait for providing the ability to create collections of nonfungible items. +pub trait Create: Inspect { + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, + who: &AccountId, + admin: &AccountId, + config: &CollectionConfig, + ) -> DispatchResult; +} + +/// Trait for providing the ability to destroy collections of nonfungible items. +pub trait Destroy: Inspect { + /// The witness data needed to destroy an item. + type DestroyWitness; + + /// Provide the appropriate witness data needed to destroy an item. + fn get_destroy_witness(collection: &Self::CollectionId) -> Option; + + /// Destroy an existing fungible item. + /// * `collection`: The `CollectionId` to be destroyed. + /// * `witness`: Any witness data that needs to be provided to complete the operation + /// successfully. + /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// command. If not provided, we will not do any authorization checks before destroying the + /// item. + /// + /// If successful, this function will return the actual witness data from the destroyed item. + /// This may be different than the witness data provided, and can be used to refund weight. + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result; +} + +/// Trait for providing an interface for multiple collections of NFT-like items which may be +/// minted, burned and/or have attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` of `collection` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item` of `collection`. + /// + /// By default, this is not a supported operation. + fn burn( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v))) + } + + /// Set attribute `value` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_collection_attribute( + _collection: &Self::CollectionId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) + }) + } +} + +/// Trait for providing a non-fungible sets of items which can only be transferred. +pub trait Transfer: Inspect { + /// Transfer `item` of `collection` into `destination` account. + fn transfer( + collection: &Self::CollectionId, + item: &Self::ItemId, + destination: &AccountId, + ) -> DispatchResult; +} From 09aca134b746ef4aa5776fb470d4d5d0f6cbdf06 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:30:26 +0300 Subject: [PATCH 35/94] Fix traits --- frame/nfts/src/impl_nonfungibles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 7c7b6554ae731..45e544d460d2f 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -19,7 +19,7 @@ use super::*; use frame_support::{ - traits::{tokens::nonfungibles::*, Get}, + traits::{tokens::nonfungibles_v2::*, Get}, BoundedSlice, }; use sp_runtime::{DispatchError, DispatchResult}; From 114e51925cc586b36dbff56c51eede3263ed6040 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:30:50 +0300 Subject: [PATCH 36/94] Test SystemFeatures --- frame/nfts/src/mock.rs | 2 +- frame/nfts/src/tests.rs | 67 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index e4667a7265acb..ce5d24a13ad1a 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -86,7 +86,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); + pub storage FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); } impl Config for Test { diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 8f596cf78c9b8..52be09756dee2 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1327,3 +1327,70 @@ fn collection_locking_should_work() { assert_eq!(stored_config, full_lock_config); }); } + +#[test] +fn pallet_level_feature_flags_should_work() { + new_test_ext().execute_with(|| { + FeatureFlags::set(&SystemFeatures( + SystemFeature::NoTrading | SystemFeature::NoApprovals | SystemFeature::NoAttributes, + )); + + let user_id = 1; + let collection_id = 0; + let item_id = 1; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + default_item_config(), + )); + + // SystemFeature::NoTrading + assert_noop!( + Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_id, Some(1), None), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_id), collection_id, item_id, 1), + Error::::MethodDisabled + ); + + // SystemFeature::NoApprovals + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(user_id), collection_id, item_id, 2), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::clear_all_transfer_approvals( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + ), + Error::::MethodDisabled + ); + + // SystemFeature::NoAttributes + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(user_id), + collection_id, + None, + bvec![0], + bvec![0] + ), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::clear_attribute(RuntimeOrigin::signed(user_id), collection_id, None, bvec![0]), + Error::::MethodDisabled + ); + }) +} From d6536fc3041c01e4459fe0d1352406e678d8748b Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:31:05 +0300 Subject: [PATCH 37/94] Fix benchmarks --- frame/nfts/src/benchmarking.rs | 50 ++++++++++++++-------------------- frame/nfts/src/lib.rs | 28 +++++++++---------- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index dbb257b74c5ad..21dcadf9b6a91 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -24,6 +24,7 @@ use frame_benchmarking::{ account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; use frame_support::{ + assert_ok, dispatch::UnfilteredDispatchable, traits::{EnsureOrigin, Get}, BoundedVec, @@ -42,8 +43,11 @@ fn create_collection, I: 'static>( let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Nfts::::force_create(SystemOrigin::Root.into(), caller_lookup.clone(), false,) - .is_ok()); + assert_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + CollectionConfig::empty() + )); (collection, caller, caller_lookup) } @@ -53,13 +57,11 @@ fn add_collection_metadata, I: 'static>() -> (T::AccountId, Account whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Nfts::::set_collection_metadata( + assert_ok!(Nfts::::set_collection_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), - false, - ) - .is_ok()); + )); (caller, caller_lookup) } @@ -72,13 +74,13 @@ fn mint_item, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let item = T::Helper::item(index); - assert!(Nfts::::mint( + assert_ok!(Nfts::::mint( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, caller_lookup.clone(), - ) - .is_ok()); + ItemConfig::empty(), + )); (item, caller, caller_lookup) } @@ -90,14 +92,12 @@ fn add_item_metadata, I: 'static>( whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Nfts::::set_metadata( + assert_ok!(Nfts::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, vec![0; T::StringLimit::get() as usize].try_into().unwrap(), - false, - ) - .is_ok()); + )); (caller, caller_lookup) } @@ -110,14 +110,13 @@ fn add_item_attribute, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); - assert!(Nfts::::set_attribute( + assert_ok!(Nfts::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), Some(item), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), - ) - .is_ok()); + )); (key, caller, caller_lookup) } @@ -137,7 +136,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { admin }; + let call = Call::::create { admin, config: CollectionConfig::empty() }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -146,25 +145,19 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, caller_lookup, true) + }: _(SystemOrigin::Root, caller_lookup, CollectionConfig::empty()) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } destroy { let n in 0 .. 1_000; - let m in 0 .. 1_000; - let a in 0 .. 1_000; let (collection, caller, caller_lookup) = create_collection::(); add_collection_metadata::(); for i in 0..n { mint_item::(i as u16); - } - for i in 0..m { add_item_metadata::(T::Helper::item(i as u16)); - } - for i in 0..a { add_item_attribute::(T::Helper::item(i as u16)); } let witness = Collection::::get(collection).unwrap().destroy_witness(); @@ -176,7 +169,7 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, ItemConfig::empty()) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -211,8 +204,7 @@ benchmarks_instance_pallet! { caller_lookup.clone(), caller_lookup.clone(), caller_lookup, - true, - CollectionConfig::empty(), + CollectionConfig(CollectionSetting::FreeHolding.into()), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { @@ -288,11 +280,11 @@ benchmarks_instance_pallet! { issuer: caller_lookup.clone(), admin: caller_lookup.clone(), freezer: caller_lookup, - config: CollectionConfig(CollectionSetting::FreeHolding), + config: CollectionConfig(CollectionSetting::FreeHolding.into()), }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::ItemStatusChanged { collection }.into()); + assert_last_event::(Event::CollectionStatusChanged { collection }.into()); } set_attribute { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index f39c53ad9a32d..bda4b0d541d6e 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1133,7 +1133,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_collection_status())] pub fn force_collection_status( origin: OriginFor, - collection_id: T::CollectionId, + collection: T::CollectionId, owner: AccountIdLookupOf, issuer: AccountIdLookupOf, admin: AccountIdLookupOf, @@ -1142,21 +1142,21 @@ pub mod pallet { ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Collection::::try_mutate(collection_id, |maybe_collection| { - let mut collection = + Collection::::try_mutate(collection, |maybe_collection| { + let mut collection_info = maybe_collection.take().ok_or(Error::::UnknownCollection)?; - let old_owner = collection.owner; + let old_owner = collection_info.owner; let new_owner = T::Lookup::lookup(owner)?; - collection.owner = new_owner.clone(); - collection.issuer = T::Lookup::lookup(issuer)?; - collection.admin = T::Lookup::lookup(admin)?; - collection.freezer = T::Lookup::lookup(freezer)?; - *maybe_collection = Some(collection); - CollectionAccount::::remove(&old_owner, &collection_id); - CollectionAccount::::insert(&new_owner, &collection_id, ()); - CollectionConfigOf::::insert(&collection_id, config); - - Self::deposit_event(Event::CollectionStatusChanged { collection: collection_id }); + collection_info.owner = new_owner.clone(); + collection_info.issuer = T::Lookup::lookup(issuer)?; + collection_info.admin = T::Lookup::lookup(admin)?; + collection_info.freezer = T::Lookup::lookup(freezer)?; + *maybe_collection = Some(collection_info); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + CollectionConfigOf::::insert(&collection, config); + + Self::deposit_event(Event::CollectionStatusChanged { collection }); Ok(()) }) } From 09a4ed6ec016da70ed33acd7b97a7cde880af058 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:39:28 +0300 Subject: [PATCH 38/94] Add missing benchmark --- frame/nfts/src/benchmarking.rs | 10 ++++++++++ frame/nfts/src/lib.rs | 2 +- frame/nfts/src/weights.rs | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 21dcadf9b6a91..9d9fd6f7c3958 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -287,6 +287,16 @@ benchmarks_instance_pallet! { assert_last_event::(Event::CollectionStatusChanged { collection }.into()); } + lock_item { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + let lock_metadata = true; + let lock_attributes = true; + }: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes) + verify { + assert_last_event::(Event::ItemLocked { collection, item, lock_metadata, lock_attributes }.into()); + } + set_attribute { let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index bda4b0d541d6e..b925b8f079bb6 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1174,7 +1174,7 @@ pub mod pallet { /// Emits `ItemLocked`. /// /// Weight: `O(1)` - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::lock_item())] pub fn lock_item( origin: OriginFor, collection: T::CollectionId, diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 27d770e7f9344..751dab1c0b718 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -59,6 +59,7 @@ pub trait WeightInfo { fn transfer_ownership() -> Weight; fn set_team() -> Weight; fn force_collection_status() -> Weight; + fn lock_item() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; fn set_metadata() -> Weight; @@ -199,6 +200,13 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + fn lock_item() -> Weight { + Weight::from_ref_time(25_684_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { @@ -423,6 +431,13 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + fn lock_item() -> Weight { + Weight::from_ref_time(25_684_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts InstanceMetadataOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { From d408b32adf422913ec7e7a78087f3525accf3ad2 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 29 Sep 2022 14:53:45 +0300 Subject: [PATCH 39/94] Fix node/runtime/lib.rs --- bin/node/runtime/src/lib.rs | 4 ++-- frame/nfts/src/types.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0e52bd54dd912..289d0e34be999 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -53,7 +53,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; -use pallet_nfts::{SystemFeature, SystemFeatures}; +use pallet_nfts::SystemFeatures; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; @@ -1498,7 +1498,7 @@ impl pallet_uniques::Config for Runtime { } parameter_types! { - pub FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); + pub FeatureFlags: SystemFeatures = SystemFeatures::empty(); } impl pallet_nfts::Config for Runtime { diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 2774d2a6d1445..ee1541dc1c049 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -225,4 +225,10 @@ pub type SystemFeatureFlags = BitFlags; /// Wrapper type for `SystemFeatureFlags` that implements `Codec`. #[derive(Default, RuntimeDebug)] pub struct SystemFeatures(pub SystemFeatureFlags); + +impl SystemFeatures { + pub fn empty() -> Self { + Self(BitFlags::EMPTY) + } +} impl_codec_bitflags!(SystemFeatures, u64, SystemFeature); From fd86e419c1c393f289df783da42d210582619fab Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Mon, 3 Oct 2022 18:42:41 +0000 Subject: [PATCH 40/94] ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts --- frame/nfts/src/weights.rs | 328 +++++++++++++++++++++----------------- 1 file changed, 182 insertions(+), 146 deletions(-) diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 751dab1c0b718..94da540014eab 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -48,7 +48,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(n: u32, m: u32, a: u32, ) -> Weight; + fn destroy(n: u32, ) -> Weight; fn mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; @@ -82,229 +82,247 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(37_627_000 as u64) + Weight::from_ref_time(39_252_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_748_000 as u64) + Weight::from_ref_time(27_479_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:1000) // Storage: Nfts ClassMetadataOf (r:0 w:1) - // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:20) + // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_449_817_000 as u64) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) + fn destroy(n: u32, ) -> Weight { + Weight::from_ref_time(55_419_000 as u64) + // Standard Error: 18_623 + .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(2004 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + .saturating_add(T::DbWeight::get().writes((5 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_014_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(44_421_000 as u64) + Weight::from_ref_time(47_193_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(34_315_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_305_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(22_836_000 as u64) - // Standard Error: 9_131 - .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + Weight::from_ref_time(26_327_000 as u64) + // Standard Error: 10_090 + .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(T::DbWeight::get().writes(1 as u64)) .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn freeze() -> Weight { - Weight::from_ref_time(27_329_000 as u64) + Weight::from_ref_time(28_194_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn thaw() -> Weight { - Weight::from_ref_time(27_842_000 as u64) + Weight::from_ref_time(28_821_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - Weight::from_ref_time(23_129_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + Weight::from_ref_time(25_896_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_684_000 as u64) + Weight::from_ref_time(32_728_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_143_000 as u64) + Weight::from_ref_time(24_805_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_status() -> Weight { - Weight::from_ref_time(25_684_000 as u64) + Weight::from_ref_time(28_468_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item() -> Weight { - Weight::from_ref_time(25_684_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + Weight::from_ref_time(27_377_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_159_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_824_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(39_968_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(48_054_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_182_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(46_590_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_330_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(44_281_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_351_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_355_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_530_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + Weight::from_ref_time(33_170_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_417_000 as u64) + Weight::from_ref_time(31_121_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_482_000 as u64) + Weight::from_ref_time(30_133_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(25_851_000 as u64) + Weight::from_ref_time(26_421_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_836_000 as u64) + Weight::from_ref_time(26_358_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(25_665_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + Weight::from_ref_time(33_607_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(47_502_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + Weight::from_ref_time(54_511_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_417_000 as u64) - // Standard Error: 32_526 - .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + Weight::from_ref_time(6_015_000 as u64) + // Standard Error: 34_307 + .saturating_add(Weight::from_ref_time(4_308_600 as u64).saturating_mul(n as u64)) } } @@ -313,228 +331,246 @@ impl WeightInfo for () { // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(37_627_000 as u64) + Weight::from_ref_time(39_252_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts NextCollectionId (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_create() -> Weight { - Weight::from_ref_time(25_748_000 as u64) + Weight::from_ref_time(27_479_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts ClassAccount (r:0 w:1) - // Storage: Nfts Attribute (r:0 w:1000) // Storage: Nfts ClassMetadataOf (r:0 w:1) - // Storage: Nfts InstanceMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionConfigOf (r:0 w:1) // Storage: Nfts CollectionMaxSupply (r:0 w:1) + // Storage: Nfts Attribute (r:0 w:20) + // Storage: Nfts InstanceMetadataOf (r:0 w:20) + // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - Weight::from_ref_time(2_449_817_000 as u64) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(8_423_500 as u64).saturating_mul(n as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) + fn destroy(n: u32, ) -> Weight { + Weight::from_ref_time(55_419_000 as u64) + // Standard Error: 18_623 + .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(2004 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + .saturating_add(RocksDbWeight::get().writes((5 as u64).saturating_mul(n as u64))) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) fn mint() -> Weight { - Weight::from_ref_time(43_014_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1) // Storage: Nfts Account (r:0 w:1) // Storage: Nfts ItemPriceOf (r:0 w:1) fn burn() -> Weight { - Weight::from_ref_time(44_421_000 as u64) + Weight::from_ref_time(47_193_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Account (r:0 w:2) // Storage: Nfts ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - Weight::from_ref_time(34_315_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_305_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - Weight::from_ref_time(22_836_000 as u64) - // Standard Error: 9_131 - .saturating_add(Weight::from_ref_time(10_894_264 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + Weight::from_ref_time(26_327_000 as u64) + // Standard Error: 10_090 + .saturating_add(Weight::from_ref_time(10_876_864 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) .saturating_add(RocksDbWeight::get().writes(1 as u64)) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn freeze() -> Weight { - Weight::from_ref_time(27_329_000 as u64) + Weight::from_ref_time(28_194_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn thaw() -> Weight { - Weight::from_ref_time(27_842_000 as u64) + Weight::from_ref_time(28_821_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) fn lock_collection() -> Weight { - Weight::from_ref_time(23_129_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + Weight::from_ref_time(25_896_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - Weight::from_ref_time(31_684_000 as u64) + Weight::from_ref_time(32_728_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Nfts Class (r:1 w:1) fn set_team() -> Weight { - Weight::from_ref_time(23_143_000 as u64) + Weight::from_ref_time(24_805_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) fn force_collection_status() -> Weight { - Weight::from_ref_time(25_684_000 as u64) + Weight::from_ref_time(28_468_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) fn lock_item() -> Weight { - Weight::from_ref_time(25_684_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + Weight::from_ref_time(27_377_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn set_attribute() -> Weight { - Weight::from_ref_time(50_159_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) - // Storage: Nfts InstanceMetadataOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { - Weight::from_ref_time(47_824_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - Weight::from_ref_time(39_968_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(48_054_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - Weight::from_ref_time(42_182_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(46_590_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - Weight::from_ref_time(39_330_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(44_281_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - Weight::from_ref_time(38_351_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(42_355_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) fn approve_transfer() -> Weight { - Weight::from_ref_time(29_530_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + Weight::from_ref_time(33_170_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn cancel_approval() -> Weight { - Weight::from_ref_time(29_417_000 as u64) + Weight::from_ref_time(31_121_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Class (r:1 w:0) // Storage: Nfts Asset (r:1 w:1) fn clear_all_transfer_approvals() -> Weight { - Weight::from_ref_time(28_482_000 as u64) + Weight::from_ref_time(30_133_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - Weight::from_ref_time(25_851_000 as u64) + Weight::from_ref_time(26_421_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts CollectionMaxSupply (r:1 w:1) // Storage: Nfts Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - Weight::from_ref_time(24_836_000 as u64) + Weight::from_ref_time(26_358_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - Weight::from_ref_time(25_665_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + Weight::from_ref_time(33_607_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemPriceOf (r:1 w:1) // Storage: Nfts Class (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Account (r:0 w:2) fn buy_item() -> Weight { - Weight::from_ref_time(47_502_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + Weight::from_ref_time(54_511_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - Weight::from_ref_time(5_417_000 as u64) - // Standard Error: 32_526 - .saturating_add(Weight::from_ref_time(4_304_363 as u64).saturating_mul(n as u64)) + Weight::from_ref_time(6_015_000 as u64) + // Standard Error: 34_307 + .saturating_add(Weight::from_ref_time(4_308_600 as u64).saturating_mul(n as u64)) } } From c002f8b58815128e13cd2f623d2eafe10c1f9e30 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 5 Oct 2022 14:21:02 +0300 Subject: [PATCH 41/94] Keep item's config on burn if it's not empty --- frame/nfts/src/functions.rs | 16 ++++++++++++++-- frame/nfts/src/lib.rs | 23 ++++++++++++++--------- frame/nfts/src/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index e952dc04cd68a..99e747662baef 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -192,7 +192,13 @@ impl, I: 'static> Pallet { let owner = owner.clone(); Account::::insert((&owner, &collection, &item), ()); - ItemConfigOf::::insert(&collection, &item, config); + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, config); + } + let details = ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; Item::::insert(&collection, &item, details); @@ -229,7 +235,13 @@ impl, I: 'static> Pallet { Item::::remove(&collection, &item); Account::::remove((&owner, &collection, &item)); ItemPriceOf::::remove(&collection, &item); - ItemConfigOf::::remove(&collection, &item); + + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let settings = Self::get_item_settings(&collection, &item)?; + if settings.is_empty() { + ItemConfigOf::::remove(&collection, &item); + } Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index b925b8f079bb6..a69f22ca63a46 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -483,6 +483,8 @@ pub mod pallet { ReachedApprovalLimit, /// The method is disabled by system settings. MethodDisabled, + /// Item's config already exists and should be equal to the provided one. + InconsistentItemConfig, } impl, I: 'static> Pallet { @@ -1301,11 +1303,12 @@ pub mod pallet { let collection_settings = Self::get_collection_settings(&collection)?; let maybe_is_frozen = match maybe_item { - None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), + None => collection_settings.contains(CollectionSetting::LockedAttributes), Some(item) => Self::get_item_settings(&collection, &item) - .map(|v| v.contains(ItemSetting::LockedAttributes)), - }?; - ensure!(!maybe_is_frozen, Error::::Frozen); + .map_or(false, |v| v.contains(ItemSetting::LockedAttributes)), + // NOTE: if the item was previously burned, the ItemSettings record might not exists + }; + ensure!(maybe_check_owner.is_none() || !maybe_is_frozen, Error::::Frozen); if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); @@ -1389,7 +1392,7 @@ pub mod pallet { /// Clear the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// `item`. + /// `collection`. /// /// Any deposit is freed for the collection's owner. /// @@ -1415,15 +1418,17 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let (action_allowed, _) = - Self::is_item_setting_disabled(&collection, &item, ItemSetting::LockedMetadata)?; - ensure!(maybe_check_owner.is_none() || action_allowed, Error::::Frozen); + // NOTE: if the item was previously burned, the ItemSettings record might not exists + let is_frozen = Self::get_item_settings(&collection, &item) + .map_or(false, |v| v.contains(ItemSetting::LockedMetadata)); + + ensure!(maybe_check_owner.is_none() || !is_frozen, Error::::Frozen); ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { if metadata.is_some() { collection_details.item_metadatas.saturating_dec(); } - let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; T::Currency::unreserve(&collection_details.owner, deposit); collection_details.total_deposit.saturating_reduce(deposit); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 52be09756dee2..49b712bf963b2 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -563,6 +563,40 @@ fn set_attribute_should_respect_freeze() { }); } +#[test] +fn preserve_config_for_frozen_items() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + + // if the item is not locked/frozen then the config gets deleted on item burn + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); + assert!(!ItemConfigOf::::contains_key(0, 1)); + + // lock the item and ensure the config stays unchanged + assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 0, true, true)); + + let expect_config = ItemConfig(ItemSetting::LockedAttributes | ItemSetting::LockedMetadata); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 0, Some(1))); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + // can't mint with the different config + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Error::::InconsistentItemConfig + ); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); + }); +} + #[test] fn force_collection_status_should_work() { new_test_ext().execute_with(|| { From d0ee824bdb8d0cc3133197e5d339aa6612eb3a58 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 5 Oct 2022 14:42:23 +0300 Subject: [PATCH 42/94] Fix the merge artifacts --- frame/nfts/src/tests.rs | 30 +++++++++++++++++++++--------- frame/nfts/src/weights.rs | 18 +++++++++++++++--- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index e4bad2eafc1c0..dfc01bba84449 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1315,10 +1315,22 @@ fn create_cancel_swap_should_work() { let duration = 2; let expect_deadline = 3; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + default_item_config(), + )); // validate desired item and the collection exists assert_noop!( @@ -1453,13 +1465,13 @@ fn claim_swap_should_work() { Balances::make_free_balance_be(&user_1, initial_balance); Balances::make_free_balance_be(&user_2, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, true)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_2)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_2)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_4, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_5, user_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_4, user_1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_5, user_2, default_item_config())); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1), diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index d00cc1957cdfa..8d74683834571 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -48,7 +48,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(n: u32, ) -> Weight; + fn destroy(n: u32, m: u32, a: u32, ) -> Weight; fn mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; @@ -111,10 +111,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - fn destroy(n: u32, ) -> Weight { + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { Weight::from_ref_time(55_419_000 as u64) // Standard Error: 18_623 .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(T::DbWeight::get().writes(5 as u64)) @@ -387,10 +393,16 @@ impl WeightInfo for () { // Storage: Nfts ItemConfigOf (r:0 w:20) // Storage: Nfts Account (r:0 w:20) /// The range of component `n` is `[0, 1000]`. - fn destroy(n: u32, ) -> Weight { + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { Weight::from_ref_time(55_419_000 as u64) // Standard Error: 18_623 .saturating_add(Weight::from_ref_time(12_843_237 as u64).saturating_mul(n as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(315_839 as u64).saturating_mul(m as u64)) + // Standard Error: 27_329 + .saturating_add(Weight::from_ref_time(217_497 as u64).saturating_mul(a as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) .saturating_add(RocksDbWeight::get().writes(5 as u64)) From 0eb568ed06508cc7921a19cba7e7e65ece11b828 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 5 Oct 2022 14:44:38 +0300 Subject: [PATCH 43/94] Fmt --- frame/nfts/src/tests.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index dfc01bba84449..0db08262835a1 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1467,11 +1467,41 @@ fn claim_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_2, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_2, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_4, user_1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_5, user_2, default_item_config())); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_4, + user_1, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_5, + user_2, + default_item_config(), + )); assert_ok!(Nfts::create_swap( RuntimeOrigin::signed(user_1), From 998691ea358b1b1091b04993d02b435da285628b Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 6 Oct 2022 10:39:47 +0300 Subject: [PATCH 44/94] Add SystemFeature::NoSwaps check --- frame/nfts/src/features/atomic_swap.rs | 3 +++ frame/nfts/src/lib.rs | 12 ------------ frame/nfts/src/tests.rs | 16 ---------------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs index 116da57477f4e..79b9fa9bee1e8 100644 --- a/frame/nfts/src/features/atomic_swap.rs +++ b/frame/nfts/src/features/atomic_swap.rs @@ -31,6 +31,7 @@ impl, I: 'static> Pallet { maybe_price: Option>>, duration: ::BlockNumber, ) -> DispatchResult { + ensure!(!Self::is_feature_flag_set(SystemFeature::NoSwaps), Error::::MethodDisabled); ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); let item = Item::::get(&offered_collection_id, &offered_item_id) @@ -111,6 +112,8 @@ impl, I: 'static> Pallet { receive_item_id: T::ItemId, witness_price: Option>>, ) -> DispatchResult { + ensure!(!Self::is_feature_flag_set(SystemFeature::NoSwaps), Error::::MethodDisabled); + let send_item = Item::::get(&send_collection_id, &send_item_id) .ok_or(Error::::UnknownItem)?; let receive_item = Item::::get(&receive_collection_id, &receive_item_id) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index bc5eea62f78ac..f7a655805e993 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1082,10 +1082,6 @@ pub mod pallet { item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { - ensure!( - !Self::is_feature_flag_set(SystemFeature::NoApprovals), - Error::::MethodDisabled - ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1146,10 +1142,6 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - ensure!( - !Self::is_feature_flag_set(SystemFeature::NoApprovals), - Error::::MethodDisabled - ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; @@ -1343,10 +1335,6 @@ pub mod pallet { maybe_item: Option, key: BoundedVec, ) -> DispatchResult { - ensure!( - !Self::is_feature_flag_set(SystemFeature::NoAttributes), - Error::::MethodDisabled - ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 0db08262835a1..7a9c2cf34ea71 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1753,18 +1753,6 @@ fn pallet_level_feature_flags_should_work() { Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), Error::::MethodDisabled ); - assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(user_id), collection_id, item_id, 2), - Error::::MethodDisabled - ); - assert_noop!( - Nfts::clear_all_transfer_approvals( - RuntimeOrigin::signed(user_id), - collection_id, - item_id, - ), - Error::::MethodDisabled - ); // SystemFeature::NoAttributes assert_noop!( @@ -1777,9 +1765,5 @@ fn pallet_level_feature_flags_should_work() { ), Error::::MethodDisabled ); - assert_noop!( - Nfts::clear_attribute(RuntimeOrigin::signed(user_id), collection_id, None, bvec![0]), - Error::::MethodDisabled - ); }) } From b8c6f5ea922262b9f6cee87abbd0a1d6effe1f87 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 7 Oct 2022 10:41:20 +0300 Subject: [PATCH 45/94] Rename SystemFeatures to PalletFeatures --- bin/node/runtime/src/lib.rs | 6 +++--- frame/nfts/src/features/atomic_swap.rs | 10 ++++++++-- frame/nfts/src/features/settings.rs | 6 +++--- frame/nfts/src/functions.rs | 4 ++-- frame/nfts/src/lib.rs | 6 +++--- frame/nfts/src/mock.rs | 5 ++--- frame/nfts/src/tests.rs | 10 +++++----- frame/nfts/src/types.rs | 11 +++++------ 8 files changed, 31 insertions(+), 27 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 84efdad01e13d..f9c0686504507 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -53,7 +53,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; -use pallet_nfts::SystemFeatures; +use pallet_nfts::PalletFeatures; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; @@ -1499,7 +1499,7 @@ impl pallet_uniques::Config for Runtime { } parameter_types! { - pub FeatureFlags: SystemFeatures = SystemFeatures::empty(); + pub Features: PalletFeatures = PalletFeatures::empty(); } impl pallet_nfts::Config for Runtime { @@ -1519,7 +1519,7 @@ impl pallet_nfts::Config for Runtime { type ApprovalsLimit = ApprovalsLimit; type MaxTips = MaxTips; type MaxDeadlineDuration = MaxDeadlineDuration; - type FeatureFlags = FeatureFlags; + type Features = Features; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs index 79b9fa9bee1e8..fc8d321d21094 100644 --- a/frame/nfts/src/features/atomic_swap.rs +++ b/frame/nfts/src/features/atomic_swap.rs @@ -31,7 +31,10 @@ impl, I: 'static> Pallet { maybe_price: Option>>, duration: ::BlockNumber, ) -> DispatchResult { - ensure!(!Self::is_feature_flag_set(SystemFeature::NoSwaps), Error::::MethodDisabled); + ensure!( + Self::is_pallet_feature_disabled(PalletFeature::NoSwaps), + Error::::MethodDisabled + ); ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); let item = Item::::get(&offered_collection_id, &offered_item_id) @@ -112,7 +115,10 @@ impl, I: 'static> Pallet { receive_item_id: T::ItemId, witness_price: Option>>, ) -> DispatchResult { - ensure!(!Self::is_feature_flag_set(SystemFeature::NoSwaps), Error::::MethodDisabled); + ensure!( + Self::is_pallet_feature_disabled(PalletFeature::NoSwaps), + Error::::MethodDisabled + ); let send_item = Item::::get(&send_collection_id, &send_item_id) .ok_or(Error::::UnknownItem)?; diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index b4f7bff165597..5b3326265c202 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -53,8 +53,8 @@ impl, I: 'static> Pallet { Ok((!settings.contains(setting), settings)) } - pub fn is_feature_flag_set(feature: SystemFeature) -> bool { - let features = T::FeatureFlags::get(); - return features.0.contains(feature) + pub fn is_pallet_feature_disabled(feature: PalletFeature) -> bool { + let features = T::Features::get(); + return !features.0.contains(feature) } } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 1c0371617dd13..44e7e5df98d61 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -259,7 +259,7 @@ impl, I: 'static> Pallet { whitelisted_buyer: Option, ) -> DispatchResult { ensure!( - !Self::is_feature_flag_set(SystemFeature::NoTrading), + Self::is_pallet_feature_disabled(PalletFeature::NoTrading), Error::::MethodDisabled ); @@ -299,7 +299,7 @@ impl, I: 'static> Pallet { bid_price: ItemPrice, ) -> DispatchResult { ensure!( - !Self::is_feature_flag_set(SystemFeature::NoTrading), + Self::is_pallet_feature_disabled(PalletFeature::NoTrading), Error::::MethodDisabled ); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index f7a655805e993..badaf33c9bba7 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -165,7 +165,7 @@ pub mod pallet { /// Disables some of pallet's features. #[pallet::constant] - type FeatureFlags: Get; + type Features: Get; #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. @@ -1015,7 +1015,7 @@ pub mod pallet { maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { ensure!( - !Self::is_feature_flag_set(SystemFeature::NoApprovals), + Self::is_pallet_feature_disabled(PalletFeature::NoApprovals), Error::::MethodDisabled ); let maybe_check: Option = T::ForceOrigin::try_origin(origin) @@ -1265,7 +1265,7 @@ pub mod pallet { value: BoundedVec, ) -> DispatchResult { ensure!( - !Self::is_feature_flag_set(SystemFeature::NoAttributes), + Self::is_pallet_feature_disabled(PalletFeature::NoAttributes), Error::::MethodDisabled ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index b7e5d4be4c336..431d05a762be9 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -20,7 +20,6 @@ use super::*; use crate as pallet_nfts; -use enumflags2::BitFlag; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, @@ -86,7 +85,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub storage FeatureFlags: SystemFeatures = SystemFeatures(SystemFeature::empty()); + pub storage Features: PalletFeatures = PalletFeatures::empty(); } impl Config for Test { @@ -108,7 +107,7 @@ impl Config for Test { type ApprovalsLimit = ConstU32<10>; type MaxTips = ConstU32<10>; type MaxDeadlineDuration = ConstU64<10000>; - type FeatureFlags = FeatureFlags; + type Features = Features; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 7a9c2cf34ea71..fc8c990848cf9 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1720,8 +1720,8 @@ fn collection_locking_should_work() { #[test] fn pallet_level_feature_flags_should_work() { new_test_ext().execute_with(|| { - FeatureFlags::set(&SystemFeatures( - SystemFeature::NoTrading | SystemFeature::NoApprovals | SystemFeature::NoAttributes, + Features::set(&PalletFeatures( + PalletFeature::NoTrading | PalletFeature::NoApprovals | PalletFeature::NoAttributes, )); let user_id = 1; @@ -1738,7 +1738,7 @@ fn pallet_level_feature_flags_should_work() { default_item_config(), )); - // SystemFeature::NoTrading + // PalletFeature::NoTrading assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_id, Some(1), None), Error::::MethodDisabled @@ -1748,13 +1748,13 @@ fn pallet_level_feature_flags_should_work() { Error::::MethodDisabled ); - // SystemFeature::NoApprovals + // PalletFeature::NoApprovals assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), Error::::MethodDisabled ); - // SystemFeature::NoAttributes + // PalletFeature::NoAttributes assert_noop!( Nfts::set_attribute( RuntimeOrigin::signed(user_id), diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 7c3e3c9d17060..dc4d76ff0ad46 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -234,7 +234,7 @@ impl_codec_bitflags!(ItemConfig, u64, ItemSetting); #[bitflags] #[repr(u64)] #[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] -pub enum SystemFeature { +pub enum PalletFeature { /// Disallow trading operations. NoTrading, /// Disallow setting attributes. @@ -246,15 +246,14 @@ pub enum SystemFeature { /// Disallow public mints. NoPublicMints, } -pub type SystemFeatureFlags = BitFlags; -/// Wrapper type for `SystemFeatureFlags` that implements `Codec`. +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Default, RuntimeDebug)] -pub struct SystemFeatures(pub SystemFeatureFlags); +pub struct PalletFeatures(pub BitFlags); -impl SystemFeatures { +impl PalletFeatures { pub fn empty() -> Self { Self(BitFlags::EMPTY) } } -impl_codec_bitflags!(SystemFeatures, u64, SystemFeature); +impl_codec_bitflags!(PalletFeatures, u64, PalletFeature); From 8bef776916c4ecab537133302cca23d253ab11f0 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 7 Oct 2022 11:17:19 +0300 Subject: [PATCH 46/94] Rename errors --- frame/nfts/src/functions.rs | 6 ++-- frame/nfts/src/lib.rs | 70 +++++++++++++++++++++++++------------ frame/nfts/src/tests.rs | 27 +++++++------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 44e7e5df98d61..ed9f351bceade 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -36,7 +36,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); let (action_allowed, _) = Self::is_collection_setting_disabled( &collection, @@ -46,7 +46,7 @@ impl, I: 'static> Pallet { let (action_allowed, _) = Self::is_item_setting_disabled(&collection, &item, ItemSetting::NonTransferable)?; - ensure!(action_allowed, Error::::Locked); + ensure!(action_allowed, Error::::ItemLocked); let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; @@ -274,7 +274,7 @@ impl, I: 'static> Pallet { let (action_allowed, _) = Self::is_item_setting_disabled(&collection, &item, ItemSetting::NonTransferable)?; - ensure!(action_allowed, Error::::Locked); + ensure!(action_allowed, Error::::ItemLocked); if let Some(ref price) = price { ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index badaf33c9bba7..f142b13a5bbe8 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -514,9 +514,15 @@ pub mod pallet { /// The named owner has not signed ownership of the collection is acceptable. Unaccepted, /// The item is locked. - Locked, - /// The collection's metadata is locked. - CollectionMetadataIsLocked, + ItemLocked, + /// Item's attributes are locked. + LockedItemAttributes, + /// Collection's attributes are locked. + LockedCollectionAttributes, + /// Item's metadata is locked. + LockedItemMetadata, + /// Collection's metadata is locked. + LockedCollectionMetadata, /// All items have been minted. MaxSupplyReached, /// The max supply has already been set. @@ -1280,12 +1286,19 @@ pub mod pallet { } let collection_settings = Self::get_collection_settings(&collection)?; - let maybe_is_frozen = match maybe_item { - None => Ok(collection_settings.contains(CollectionSetting::LockedAttributes)), - Some(item) => Self::get_item_settings(&collection, &item) - .map(|v| v.contains(ItemSetting::LockedAttributes)), - }?; - ensure!(!maybe_is_frozen, Error::::Frozen); + match maybe_item { + None => { + ensure!( + !collection_settings.contains(CollectionSetting::LockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + let maybe_is_locked = Self::get_item_settings(&collection, &item) + .map(|v| v.contains(ItemSetting::LockedAttributes))?; + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; let attribute = Attribute::::get((collection, maybe_item, &key)); if attribute.is_none() { @@ -1345,14 +1358,24 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let collection_settings = Self::get_collection_settings(&collection)?; - let maybe_is_frozen = match maybe_item { - None => collection_settings.contains(CollectionSetting::LockedAttributes), - Some(item) => Self::get_item_settings(&collection, &item) - .map_or(false, |v| v.contains(ItemSetting::LockedAttributes)), - // NOTE: if the item was previously burned, the ItemSettings record might not exists - }; - ensure!(maybe_check_owner.is_none() || !maybe_is_frozen, Error::::Frozen); + if maybe_check_owner.is_some() { + match maybe_item { + None => { + let collection_settings = Self::get_collection_settings(&collection)?; + ensure!( + !collection_settings.contains(CollectionSetting::LockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemSettings record might + // not exists. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_settings(&collection, &item) + .map_or(false, |v| v.contains(ItemSetting::LockedAttributes)); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + } if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); @@ -1396,7 +1419,10 @@ pub mod pallet { let (action_allowed, _) = Self::is_item_setting_disabled(&collection, &item, ItemSetting::LockedMetadata)?; - ensure!(maybe_check_owner.is_none() || action_allowed, Error::::Frozen); + ensure!( + maybe_check_owner.is_none() || action_allowed, + Error::::LockedItemMetadata + ); let collection_settings = Self::get_collection_settings(&collection)?; @@ -1463,10 +1489,10 @@ pub mod pallet { } // NOTE: if the item was previously burned, the ItemSettings record might not exists - let is_frozen = Self::get_item_settings(&collection, &item) + let is_locked = Self::get_item_settings(&collection, &item) .map_or(false, |v| v.contains(ItemSetting::LockedMetadata)); - ensure!(maybe_check_owner.is_none() || !is_frozen, Error::::Frozen); + ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { if metadata.is_some() { @@ -1513,7 +1539,7 @@ pub mod pallet { )?; ensure!( maybe_check_owner.is_none() || action_allowed, - Error::::CollectionMetadataIsLocked + Error::::LockedCollectionMetadata ); let mut details = @@ -1581,7 +1607,7 @@ pub mod pallet { )?; ensure!( maybe_check_owner.is_none() || action_allowed, - Error::::CollectionMetadataIsLocked + Error::::LockedCollectionMetadata ); CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index fc8c990848cf9..3ed75af423102 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -230,7 +230,7 @@ fn freezing_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Locked); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(1), 0, 42)); assert_ok!(Nfts::lock_collection( @@ -394,11 +394,11 @@ fn set_collection_metadata_should_work() { )); assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), - Error::::CollectionMetadataIsLocked, + Error::::LockedCollectionMetadata, ); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), - Error::::CollectionMetadataIsLocked + Error::::LockedCollectionMetadata ); // Clear Metadata @@ -413,7 +413,7 @@ fn set_collection_metadata_should_work() { ); assert_noop!( Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), - Error::::CollectionMetadataIsLocked + Error::::LockedCollectionMetadata ); assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); assert!(!CollectionMetadataOf::::contains_key(0)); @@ -459,19 +459,22 @@ fn set_item_metadata_should_work() { assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 42, true, false)); assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15]), - Error::::Frozen, + Error::::LockedItemMetadata, + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), + Error::::LockedItemMetadata, ); - assert_noop!(Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), Error::::Frozen); // Clear Metadata assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); assert_noop!( Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), - Error::::NoPermission + Error::::NoPermission, ); assert_noop!( Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), - Error::::UnknownCollection + Error::::UnknownCollection, ); assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); @@ -525,7 +528,7 @@ fn set_attribute_should_work() { } #[test] -fn set_attribute_should_respect_freeze() { +fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); @@ -553,12 +556,12 @@ fn set_attribute_should_respect_freeze() { CollectionConfig(CollectionSetting::LockedAttributes.into()) )); - let e = Error::::Frozen; + let e = Error::::LockedCollectionAttributes; assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 0, false, true)); - let e = Error::::Frozen; + let e = Error::::LockedItemAttributes; assert_noop!( Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), e @@ -1250,7 +1253,7 @@ fn buy_item_should_work() { }); assert_noop!( buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), - Error::::Locked + Error::::ItemLocked ); } }); From ddaa884f99df2b4a3bfdd4117fcd6c16cf365c54 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 7 Oct 2022 11:32:49 +0300 Subject: [PATCH 47/94] Add docs --- frame/nfts/src/features/freeze.rs | 3 +++ frame/nfts/src/features/lock.rs | 3 +++ frame/nfts/src/features/settings.rs | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/frame/nfts/src/features/freeze.rs b/frame/nfts/src/features/freeze.rs index 6884911321ab5..264fdf77e6a73 100644 --- a/frame/nfts/src/features/freeze.rs +++ b/frame/nfts/src/features/freeze.rs @@ -18,6 +18,9 @@ use crate::*; use frame_support::pallet_prelude::*; +/// Freeze functions allow to make particular items non-transferable. +/// It's also possible to revert the setting back. +/// An origin must have a `Freezer` role in order to call those methods. impl, I: 'static> Pallet { pub fn do_freeze_item( origin: T::AccountId, diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 2721d64ef11c3..f9c24711fe9f0 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -18,6 +18,9 @@ use crate::*; use frame_support::pallet_prelude::*; +/// Lock functions allow to lock collection/items metadata and attributes. +/// Additionally, it's possible to make all collection items non-transferable. +/// Those settings are irreversible. impl, I: 'static> Pallet { pub fn do_lock_collection( origin: T::AccountId, diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 5b3326265c202..c5e906ce1b29e 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -18,6 +18,10 @@ use crate::*; use frame_support::pallet_prelude::*; +/// The helper methods bellow allow to read and validate different +/// collection/item/pallet settings. +/// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular +/// collection, or for a specific item. impl, I: 'static> Pallet { pub fn get_collection_settings( collection_id: &T::CollectionId, From 4c01fc51aeb1f6ce739d2fbd218bd67a49764882 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 7 Oct 2022 15:37:48 +0300 Subject: [PATCH 48/94] Change error message --- frame/nfts/src/features/lock.rs | 2 +- frame/nfts/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index f9c24711fe9f0..9e5cb727cc081 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -32,7 +32,7 @@ impl, I: 'static> Pallet { ensure!(origin == details.freezer, Error::::NoPermission); CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::UnknownCollection)?; + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; let mut settings = config.values(); let lock_settings = lock_config.values(); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index f142b13a5bbe8..76a5664889ec6 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -547,6 +547,8 @@ pub mod pallet { MethodDisabled, /// Item's config already exists and should be equal to the provided one. InconsistentItemConfig, + /// Config for a collection or an item can't be found. + NoConfig, } impl, I: 'static> Pallet { From 30d2bde33504513ebef6733202cd52ecb591bdfa Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 10 Oct 2022 16:38:00 +0300 Subject: [PATCH 49/94] Change the format of CollectionConfig to store more data --- frame/nfts/src/features/lock.rs | 8 ++-- frame/nfts/src/features/settings.rs | 7 ++-- frame/nfts/src/impl_nonfungibles.rs | 10 ++--- frame/nfts/src/lib.rs | 8 ++-- frame/nfts/src/tests.rs | 57 ++++++++++++++++++----------- frame/nfts/src/types.rs | 17 ++++++--- 6 files changed, 64 insertions(+), 43 deletions(-) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 9e5cb727cc081..362d3ee94c035 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -25,7 +25,7 @@ impl, I: 'static> Pallet { pub fn do_lock_collection( origin: T::AccountId, collection: T::CollectionId, - lock_config: CollectionConfig, + lock_settings: CollectionSettings, ) -> DispatchResult { let details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -33,8 +33,8 @@ impl, I: 'static> Pallet { CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - let mut settings = config.values(); - let lock_settings = lock_config.values(); + let mut settings = config.settings.values(); + let lock_settings = lock_settings.values(); if lock_settings.contains(CollectionSetting::NonTransferableItems) { settings.insert(CollectionSetting::NonTransferableItems); @@ -46,7 +46,7 @@ impl, I: 'static> Pallet { settings.insert(CollectionSetting::LockedAttributes); } - config.0 = settings; + config.settings = CollectionSettings(settings); Self::deposit_event(Event::::CollectionLocked { collection }); Ok(()) diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index c5e906ce1b29e..2f1fea2c39f7e 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::*; +use enumflags2::BitFlags; use frame_support::pallet_prelude::*; /// The helper methods bellow allow to read and validate different @@ -25,10 +26,10 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { pub fn get_collection_settings( collection_id: &T::CollectionId, - ) -> Result { + ) -> Result, DispatchError> { let config = CollectionConfigOf::::get(&collection_id) .ok_or(Error::::UnknownCollection)?; - Ok(config.values()) + Ok(config.settings.values()) } pub fn get_item_settings( @@ -43,7 +44,7 @@ impl, I: 'static> Pallet { pub fn is_collection_setting_disabled( collection_id: &T::CollectionId, setting: CollectionSetting, - ) -> Result<(bool, CollectionSettings), DispatchError> { + ) -> Result<(bool, BitFlags), DispatchError> { let settings = Self::get_collection_settings(&collection_id)?; Ok((!settings.contains(setting), settings)) } diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 45e544d460d2f..dcbf041174bcd 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -83,7 +83,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle ItemConfigOf::::get(collection, item), ) { (Some(cc), Some(ic)) - if !cc.values().contains(CollectionSetting::NonTransferableItems) && + if !cc.settings.values().contains(CollectionSetting::NonTransferableItems) && !ic.values().contains(ItemSetting::NonTransferable) => true, _ => false, @@ -91,7 +91,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } -impl, I: 'static> Create<::AccountId, CollectionSettings> +impl, I: 'static> Create<::AccountId, CollectionConfig> for Pallet { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. @@ -99,9 +99,9 @@ impl, I: 'static> Create<::AccountId, Collection collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, - settings: &CollectionSettings, + config: &CollectionConfig, ) -> DispatchResult { - let mut settings = *settings; + let mut settings = config.settings.values(); // FreeHolding could be set by calling the force_create() only if settings.contains(CollectionSetting::FreeHolding) { settings.remove(CollectionSetting::FreeHolding); @@ -110,7 +110,7 @@ impl, I: 'static> Create<::AccountId, Collection *collection, who.clone(), admin.clone(), - CollectionConfig(settings), + CollectionConfig { settings: CollectionSettings(settings), ..*config }, T::CollectionDeposit::get(), Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, ) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 76a5664889ec6..4c1553468e273 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -592,12 +592,12 @@ pub mod pallet { let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; - let mut settings = config.values(); + let mut settings = config.settings.values(); // FreeHolding could be set by calling the force_create() only if settings.contains(CollectionSetting::FreeHolding) { settings.remove(CollectionSetting::FreeHolding); } - let config = CollectionConfig(settings); + let config = CollectionConfig { settings: CollectionSettings(settings), ..config }; Self::do_create_collection( collection, @@ -910,10 +910,10 @@ pub mod pallet { pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, - lock_config: CollectionConfig, + lock_settings: CollectionSettings, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::do_lock_collection(origin, collection, lock_config) + Self::do_lock_collection(origin, collection, lock_settings) } /// Change the Owner of a collection. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 3ed75af423102..efb698cd6a1e4 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -18,6 +18,7 @@ //! Tests for Nfts pallet. use crate::{mock::*, Event, *}; +use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, @@ -93,13 +94,23 @@ fn events() -> Vec> { } fn default_collection_config() -> CollectionConfig { - CollectionConfig(CollectionSetting::FreeHolding.into()) + CollectionConfig { settings: CollectionSettings(CollectionSetting::FreeHolding.into()) } } fn default_item_config() -> ItemConfig { ItemConfig::empty() } +fn make_collection_config(settings: BitFlags) -> CollectionConfig { + CollectionConfig { settings: CollectionSettings(settings) } +} + +impl CollectionConfig { + pub fn empty() -> Self { + Self { settings: CollectionSettings::empty() } + } +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -210,7 +221,7 @@ fn transfer_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig( + make_collection_config( CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -236,7 +247,7 @@ fn freezing_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig(CollectionSetting::NonTransferableItems.into()) + CollectionSettings(CollectionSetting::NonTransferableItems.into()) )); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), @@ -390,7 +401,7 @@ fn set_collection_metadata_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig(CollectionSetting::LockedMetadata.into()) + CollectionSettings(CollectionSetting::LockedMetadata.into()) )); assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), @@ -553,7 +564,7 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig(CollectionSetting::LockedAttributes.into()) + CollectionSettings(CollectionSetting::LockedAttributes.into()) )); let e = Error::::LockedCollectionAttributes; @@ -625,7 +636,7 @@ fn force_collection_status_should_work() { 1, 1, 1, - CollectionConfig(CollectionSetting::FreeHolding.into()), + make_collection_config(CollectionSetting::FreeHolding.into()), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); @@ -699,7 +710,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig( + make_collection_config( CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -817,7 +828,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig(CollectionSetting::FreeHolding.into()) + make_collection_config(CollectionSetting::FreeHolding.into()) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); @@ -1075,7 +1086,7 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig( + make_collection_config( CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -1219,7 +1230,7 @@ fn buy_item_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_1), collection_id, - CollectionConfig(CollectionSetting::NonTransferableItems.into()) + CollectionSettings(CollectionSetting::NonTransferableItems.into()) )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1659,22 +1670,22 @@ fn claim_swap_should_work() { fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it - let config = CollectionConfig(CollectionSetting::NonTransferableItems.into()); + let config = make_collection_config(CollectionSetting::NonTransferableItems.into()); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); - let stored_settings = config.values(); + let stored_settings = config.settings.values(); assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); assert!(!stored_settings.contains(CollectionSetting::LockedMetadata)); // no need to call .into() for multiple values - let config = CollectionConfig( + let config = make_collection_config( CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(1).unwrap(); - let stored_settings = config.values(); + let stored_settings = config.settings.values(); assert!(stored_settings.contains(CollectionSetting::NonTransferableItems)); assert!(stored_settings.contains(CollectionSetting::LockedMetadata)); @@ -1688,23 +1699,27 @@ fn collection_locking_should_work() { let user_id = 1; let collection_id = 0; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, CollectionConfig::empty())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig { settings: CollectionSettings::empty() } + )); // validate partial lock - let lock_config = CollectionConfig( + let lock_settings = CollectionSettings( CollectionSetting::NonTransferableItems | CollectionSetting::LockedAttributes, ); assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - lock_config, + lock_settings, )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); - assert_eq!(stored_config, lock_config); + assert_eq!(stored_config.settings, lock_settings); // validate full lock - let full_lock_config = CollectionConfig( + let all_settings_locked = CollectionSettings( CollectionSetting::NonTransferableItems | CollectionSetting::LockedMetadata | CollectionSetting::LockedAttributes, @@ -1712,11 +1727,11 @@ fn collection_locking_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - CollectionConfig(CollectionSetting::LockedMetadata.into()), + CollectionSettings(CollectionSetting::LockedMetadata.into()), )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); - assert_eq!(stored_config, full_lock_config); + assert_eq!(stored_config.settings, all_settings_locked); }); } diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index dc4d76ff0ad46..ad30a91c76ccd 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -186,21 +186,26 @@ pub enum CollectionSetting { /// When is set then no deposit needed to hold items of this collection. FreeHolding, } -pub(super) type CollectionSettings = BitFlags; -/// Wrapper type for `CollectionSettings` that implements `Codec`. +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct CollectionConfig(pub CollectionSettings); +pub struct CollectionSettings(pub BitFlags); -impl CollectionConfig { +impl CollectionSettings { pub fn empty() -> Self { Self(BitFlags::EMPTY) } - pub fn values(&self) -> CollectionSettings { + pub fn values(&self) -> BitFlags { self.0 } } -impl_codec_bitflags!(CollectionConfig, u64, CollectionSetting); +impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); + +#[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +pub struct CollectionConfig { + /// Collection bitflag settings. + pub(super) settings: CollectionSettings, +} // Support for up to 64 user-enabled features on an item. #[bitflags] From 9449641ea0de40029641ed50367ba06d10590142 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 10 Oct 2022 18:27:45 +0300 Subject: [PATCH 50/94] Move max supply to the CollectionConfig and allow to change it --- frame/nfts/src/features/lock.rs | 3 ++ frame/nfts/src/functions.rs | 6 ++- frame/nfts/src/lib.rs | 29 +++++++------- frame/nfts/src/tests.rs | 67 +++++++++++++++++++-------------- frame/nfts/src/types.rs | 8 +++- 5 files changed, 65 insertions(+), 48 deletions(-) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 362d3ee94c035..ce1c7b1a4b235 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -45,6 +45,9 @@ impl, I: 'static> Pallet { if lock_settings.contains(CollectionSetting::LockedAttributes) { settings.insert(CollectionSetting::LockedAttributes); } + if lock_settings.contains(CollectionSetting::LockedMaxSupply) { + settings.insert(CollectionSetting::LockedMaxSupply); + } config.settings = CollectionSettings(settings); diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index ed9f351bceade..b63c794d2cfb1 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -144,7 +144,6 @@ impl, I: 'static> Pallet { Attribute::::remove_prefix((&collection,), None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); - CollectionMaxSupply::::remove(&collection); CollectionConfigOf::::remove(&collection); #[allow(deprecated)] ItemConfigOf::::remove_prefix(&collection, None); @@ -176,7 +175,10 @@ impl, I: 'static> Pallet { with_details(collection_details)?; - if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { + let collection_config = + CollectionConfigOf::::get(&collection).ok_or(Error::::NoConfig)?; + + if let Some(max_supply) = collection_config.max_supply { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 4c1553468e273..39bd9f4742ac6 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -286,11 +286,6 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] - /// Keeps track of the number of items a collection might have. - pub(super) type CollectionMaxSupply, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; - #[pallet::storage] /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented by 1 whenever a new collection is created. @@ -525,8 +520,8 @@ pub mod pallet { LockedCollectionMetadata, /// All items have been minted. MaxSupplyReached, - /// The max supply has already been set. - MaxSupplyAlreadySet, + /// The max supply is locked and can't be changed. + MaxSupplyLocked, /// The provided max supply is less to the amount of items a collection already has. MaxSupplyTooSmall, /// The given item ID is unknown. @@ -1660,8 +1655,6 @@ pub mod pallet { /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of /// the `collection`. /// - /// Note: This function can only succeed once per collection. - /// /// - `collection`: The identifier of the collection to change. /// - `max_supply`: The maximum amount of items a collection could have. /// @@ -1676,10 +1669,11 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - ensure!( - !CollectionMaxSupply::::contains_key(&collection), - Error::::MaxSupplyAlreadySet - ); + let (action_allowed, _) = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::LockedMaxSupply, + )?; + ensure!(action_allowed, Error::::MaxSupplyLocked); let details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -1689,9 +1683,12 @@ pub mod pallet { ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); - CollectionMaxSupply::::insert(&collection, max_supply); - Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); - Ok(()) + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) } /// Set (or reset) the price for an item. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index efb698cd6a1e4..ded0bee0a540a 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -94,20 +94,26 @@ fn events() -> Vec> { } fn default_collection_config() -> CollectionConfig { - CollectionConfig { settings: CollectionSettings(CollectionSetting::FreeHolding.into()) } + CollectionConfig { + settings: CollectionSettings(CollectionSetting::FreeHolding.into()), + ..Default::default() + } } fn default_item_config() -> ItemConfig { ItemConfig::empty() } -fn make_collection_config(settings: BitFlags) -> CollectionConfig { - CollectionConfig { settings: CollectionSettings(settings) } +fn make_collection_config( + settings: BitFlags, + max_supply: Option, +) -> CollectionConfig { + CollectionConfig { settings: CollectionSettings(settings), max_supply } } impl CollectionConfig { pub fn empty() -> Self { - Self { settings: CollectionSettings::empty() } + Self { settings: CollectionSettings::empty(), ..Default::default() } } } @@ -222,7 +228,8 @@ fn transfer_should_work() { RuntimeOrigin::root(), 1, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, + None ) )); @@ -636,7 +643,7 @@ fn force_collection_status_should_work() { 1, 1, 1, - make_collection_config(CollectionSetting::FreeHolding.into()), + make_collection_config(CollectionSetting::FreeHolding.into(), None), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); @@ -711,7 +718,8 @@ fn approval_lifecycle_works() { RuntimeOrigin::root(), 1, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, + None ) )); @@ -828,7 +836,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - make_collection_config(CollectionSetting::FreeHolding.into()) + make_collection_config(CollectionSetting::FreeHolding.into(), None) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); @@ -948,31 +956,44 @@ fn max_supply_should_work() { new_test_ext().execute_with(|| { let collection_id = 0; let user_id = 1; - let max_supply = 2; + let max_supply = 1; // validate set_collection_max_supply assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert!(!CollectionMaxSupply::::contains_key(collection_id)); + assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); assert_ok!(Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id), collection_id, max_supply )); - assert_eq!(CollectionMaxSupply::::get(collection_id).unwrap(), max_supply); + assert_eq!( + CollectionConfigOf::::get(collection_id).unwrap().max_supply, + Some(max_supply) + ); assert!(events().contains(&Event::::CollectionMaxSupplySet { collection: collection_id, max_supply, })); + assert_ok!(Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + 1 + )); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + CollectionSettings(CollectionSetting::LockedMaxSupply.into()) + )); assert_noop!( Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id), collection_id, - max_supply + 1 + max_supply + 2 ), - Error::::MaxSupplyAlreadySet + Error::::MaxSupplyLocked ); // validate we can't mint more to max supply @@ -1000,14 +1021,6 @@ fn max_supply_should_work() { ), Error::::MaxSupplyReached ); - - // validate we remove the CollectionMaxSupply record when we destroy the collection - assert_ok!(Nfts::destroy( - RuntimeOrigin::signed(user_id), - collection_id, - Collection::::get(collection_id).unwrap().destroy_witness() - )); - assert!(!CollectionMaxSupply::::contains_key(collection_id)); }); } @@ -1087,7 +1100,8 @@ fn set_price_should_work() { RuntimeOrigin::root(), user_id, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, + None ) )); @@ -1670,7 +1684,7 @@ fn claim_swap_should_work() { fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it - let config = make_collection_config(CollectionSetting::NonTransferableItems.into()); + let config = make_collection_config(CollectionSetting::NonTransferableItems.into(), None); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); @@ -1681,6 +1695,7 @@ fn various_collection_settings() { // no need to call .into() for multiple values let config = make_collection_config( CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, + None, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); @@ -1699,11 +1714,7 @@ fn collection_locking_should_work() { let user_id = 1; let collection_id = 0; - assert_ok!(Nfts::force_create( - RuntimeOrigin::root(), - user_id, - CollectionConfig { settings: CollectionSettings::empty() } - )); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, CollectionConfig::empty(),)); // validate partial lock let lock_settings = CollectionSettings( diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index ad30a91c76ccd..00042be715296 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -183,6 +183,8 @@ pub enum CollectionSetting { LockedMetadata, /// Disallow to modify attributes of this collection. LockedAttributes, + /// Disallow to modify the supply of this collection. + LockedMaxSupply, /// When is set then no deposit needed to hold items of this collection. FreeHolding, } @@ -201,10 +203,12 @@ impl CollectionSettings { } impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); -#[derive(Encode, Decode, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +#[derive(Encode, Decode, Default, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] pub struct CollectionConfig { - /// Collection bitflag settings. + /// Collection's bitflag settings. pub(super) settings: CollectionSettings, + /// Collection's max supply. + pub(super) max_supply: Option, } // Support for up to 64 user-enabled features on an item. From 443578d2f735401345efea13c08c47b61073efe3 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 11 Oct 2022 14:05:08 +0300 Subject: [PATCH 51/94] Remove ItemConfig from the mint() function and use the one set in mint settings --- frame/nfts/src/features/freeze.rs | 12 +- frame/nfts/src/features/lock.rs | 4 +- frame/nfts/src/features/settings.rs | 17 +- frame/nfts/src/functions.rs | 13 +- frame/nfts/src/impl_nonfungibles.rs | 9 +- frame/nfts/src/lib.rs | 7 +- frame/nfts/src/tests.rs | 208 +++++------------- frame/nfts/src/types.rs | 21 +- .../src/traits/tokens/nonfungible_v2.rs | 29 +-- .../src/traits/tokens/nonfungibles_v2.rs | 3 +- 10 files changed, 121 insertions(+), 202 deletions(-) diff --git a/frame/nfts/src/features/freeze.rs b/frame/nfts/src/features/freeze.rs index 264fdf77e6a73..22bc3967af158 100644 --- a/frame/nfts/src/features/freeze.rs +++ b/frame/nfts/src/features/freeze.rs @@ -35,7 +35,11 @@ impl, I: 'static> Pallet { if !settings.contains(ItemSetting::NonTransferable) { settings.insert(ItemSetting::NonTransferable); } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + ItemConfigOf::::insert( + &collection, + &item, + ItemConfig { settings: ItemSettings(settings) }, + ); Self::deposit_event(Event::::Frozen { collection, item }); Ok(()) @@ -54,7 +58,11 @@ impl, I: 'static> Pallet { if settings.contains(ItemSetting::NonTransferable) { settings.remove(ItemSetting::NonTransferable); } - ItemConfigOf::::insert(&collection, &item, ItemConfig(settings)); + ItemConfigOf::::insert( + &collection, + &item, + ItemConfig { settings: ItemSettings(settings) }, + ); Self::deposit_event(Event::::Thawed { collection, item }); Ok(()) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index ce1c7b1a4b235..6ab6a050c37cc 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -72,7 +72,7 @@ impl, I: 'static> Pallet { ItemConfigOf::::try_mutate(collection, item, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; - let mut settings = config.values(); + let mut settings = config.settings.values(); if lock_metadata { settings.insert(ItemSetting::LockedMetadata); @@ -81,7 +81,7 @@ impl, I: 'static> Pallet { settings.insert(ItemSetting::LockedAttributes); } - config.0 = settings; + config.settings = ItemSettings(settings); Self::deposit_event(Event::::ItemLocked { collection, diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 2f1fea2c39f7e..3a040fa29d858 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -24,21 +24,28 @@ use frame_support::pallet_prelude::*; /// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular /// collection, or for a specific item. impl, I: 'static> Pallet { + pub fn get_collection_config( + collection_id: &T::CollectionId, + ) -> Result { + let config = + CollectionConfigOf::::get(&collection_id).ok_or(Error::::NoConfig)?; + Ok(config) + } + pub fn get_collection_settings( collection_id: &T::CollectionId, ) -> Result, DispatchError> { - let config = CollectionConfigOf::::get(&collection_id) - .ok_or(Error::::UnknownCollection)?; + let config = Self::get_collection_config(collection_id)?; Ok(config.settings.values()) } pub fn get_item_settings( collection_id: &T::CollectionId, item_id: &T::ItemId, - ) -> Result { + ) -> Result, DispatchError> { let config = ItemConfigOf::::get(&collection_id, &item_id) .ok_or(Error::::UnknownItem)?; - Ok(config.values()) + Ok(config.settings.values()) } pub fn is_collection_setting_disabled( @@ -53,7 +60,7 @@ impl, I: 'static> Pallet { collection_id: &T::CollectionId, item_id: &T::ItemId, setting: ItemSetting, - ) -> Result<(bool, ItemSettings), DispatchError> { + ) -> Result<(bool, BitFlags), DispatchError> { let settings = Self::get_item_settings(&collection_id, &item_id)?; Ok((!settings.contains(setting), settings)) } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index b63c794d2cfb1..939f359e57028 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -162,7 +162,6 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, - config: ItemConfig, with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -175,8 +174,10 @@ impl, I: 'static> Pallet { with_details(collection_details)?; - let collection_config = - CollectionConfigOf::::get(&collection).ok_or(Error::::NoConfig)?; + let collection_config = Self::get_collection_config(&collection)?; + let settings = collection_config.settings.values(); + let mint_item_settings = collection_config.mint_item_settings; + let item_config = ItemConfig { settings: mint_item_settings }; if let Some(max_supply) = collection_config.max_supply { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); @@ -186,8 +187,6 @@ impl, I: 'static> Pallet { collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; - let settings = Self::get_collection_settings(&collection)?; - let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), false => T::ItemDeposit::get(), @@ -199,9 +198,9 @@ impl, I: 'static> Pallet { Account::::insert((&owner, &collection, &item), ()); if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { - ensure!(existing_config == config, Error::::InconsistentItemConfig); + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); } else { - ItemConfigOf::::insert(&collection, &item, config); + ItemConfigOf::::insert(&collection, &item, item_config); } let details = diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index dcbf041174bcd..7988d29677889 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -84,7 +84,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle ) { (Some(cc), Some(ic)) if !cc.settings.values().contains(CollectionSetting::NonTransferableItems) && - !ic.values().contains(ItemSetting::NonTransferable) => + !ic.settings.values().contains(ItemSetting::NonTransferable) => true, _ => false, } @@ -133,16 +133,13 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId, ItemSettings> - for Pallet -{ +impl, I: 'static> Mutate<::AccountId> for Pallet { fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, who: &T::AccountId, - settings: &ItemSettings, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), ItemConfig(*settings), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 39bd9f4742ac6..0aa8bc1b990ae 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -698,12 +698,11 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, owner: AccountIdLookupOf, - config: ItemConfig, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, config, |collection_details| { + Self::do_mint(collection, item, owner, |collection_details| { ensure!(collection_details.issuer == origin, Error::::NoPermission); Ok(()) }) @@ -1365,7 +1364,7 @@ pub mod pallet { ) }, Some(item) => { - // NOTE: if the item was previously burned, the ItemSettings record might + // NOTE: if the item was previously burned, the ItemConfigOf record might // not exists. In that case, we allow to clear the attribute. let maybe_is_locked = Self::get_item_settings(&collection, &item) .map_or(false, |v| v.contains(ItemSetting::LockedAttributes)); @@ -1485,7 +1484,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - // NOTE: if the item was previously burned, the ItemSettings record might not exists + // NOTE: if the item was previously burned, the ItemConfigOf record might not exists let is_locked = Self::get_item_settings(&collection, &item) .map_or(false, |v| v.contains(ItemSetting::LockedMetadata)); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index ded0bee0a540a..9f438553af0c4 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -96,19 +96,16 @@ fn events() -> Vec> { fn default_collection_config() -> CollectionConfig { CollectionConfig { settings: CollectionSettings(CollectionSetting::FreeHolding.into()), + mint_item_settings: ItemSettings::empty(), ..Default::default() } } -fn default_item_config() -> ItemConfig { - ItemConfig::empty() -} - fn make_collection_config( settings: BitFlags, max_supply: Option, ) -> CollectionConfig { - CollectionConfig { settings: CollectionSettings(settings), max_supply } + CollectionConfig { settings: CollectionSettings(settings), max_supply, ..Default::default() } } impl CollectionConfig { @@ -129,12 +126,12 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -150,9 +147,9 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10)); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20)); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); @@ -190,7 +187,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -199,7 +196,7 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -210,7 +207,7 @@ fn mint_should_work() { fn transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); @@ -233,7 +230,7 @@ fn transfer_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42)); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), @@ -246,7 +243,7 @@ fn transfer_should_work() { fn freezing_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); @@ -278,7 +275,7 @@ fn freezing_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -292,10 +289,7 @@ fn origin_guards_should_work() { ); assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, default_item_config()), - Error::::NoPermission - ); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2), Error::::NoPermission); assert_noop!( Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission @@ -334,7 +328,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); @@ -359,7 +353,7 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); @@ -373,7 +367,7 @@ fn set_collection_metadata_should_work() { // Cannot add metadata to unknown item assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), - Error::::UnknownCollection, + Error::::NoConfig, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); // Cannot add metadata to unowned item @@ -445,7 +439,7 @@ fn set_item_metadata_should_work() { // Cannot add metadata to unknown item assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), @@ -505,7 +499,7 @@ fn set_attribute_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -551,8 +545,8 @@ fn set_attribute_should_respect_lock() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -594,8 +588,8 @@ fn preserve_config_for_frozen_items() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1)); // if the item is not locked/frozen then the config gets deleted on item burn assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); @@ -604,7 +598,9 @@ fn preserve_config_for_frozen_items() { // lock the item and ensure the config stays unchanged assert_ok!(Nfts::lock_item(RuntimeOrigin::signed(1), 0, 0, true, true)); - let expect_config = ItemConfig(ItemSetting::LockedAttributes | ItemSetting::LockedMetadata); + let expect_config = ItemConfig { + settings: ItemSettings(ItemSetting::LockedAttributes | ItemSetting::LockedMetadata), + }; let config = ItemConfigOf::::get(0, 0).unwrap(); assert_eq!(config, expect_config); @@ -614,11 +610,12 @@ fn preserve_config_for_frozen_items() { // can't mint with the different config assert_noop!( - Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1), Error::::InconsistentItemConfig ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); + // TODO: + // assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); }); } @@ -628,8 +625,8 @@ fn force_collection_status_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); @@ -645,8 +642,8 @@ fn force_collection_status_should_work() { 1, make_collection_config(CollectionSetting::FreeHolding.into(), None), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); @@ -677,8 +674,8 @@ fn burn_works() { Error::::UnknownCollection ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5)); assert_eq!(Balances::reserved_balance(1), 2); assert_noop!( @@ -700,7 +697,7 @@ fn burn_works() { fn approval_lifecycle_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_noop!( @@ -723,13 +720,7 @@ fn approval_lifecycle_works() { ) )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(1), - 1, - collection_id, - 1, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1)); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -742,7 +733,7 @@ fn approval_lifecycle_works() { fn cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -770,7 +761,7 @@ fn cancel_approval_works() { let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); // approval expires after 2 blocks. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( @@ -789,7 +780,7 @@ fn cancel_approval_works() { fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); let current_block = 1; System::set_block_number(current_block); @@ -814,7 +805,7 @@ fn approving_multiple_accounts_works() { fn approvals_limit_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); for i in 3..13 { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); @@ -838,7 +829,7 @@ fn approval_deadline_works() { 1, make_collection_config(CollectionSetting::FreeHolding.into(), None) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); // the approval expires after the 2nd block. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); @@ -865,7 +856,7 @@ fn approval_deadline_works() { fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -893,7 +884,7 @@ fn cancel_approval_works_with_admin() { fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -921,7 +912,7 @@ fn cancel_approval_works_with_force() { fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); @@ -997,28 +988,10 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - 0, - user_id, - default_item_config() - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - 1, - user_id, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); assert_noop!( - Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - 2, - user_id, - default_item_config() - ), + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), Error::::MaxSupplyReached ); }); @@ -1034,20 +1007,8 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config() - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_2, - user_id, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1105,13 +1066,7 @@ fn set_price_should_work() { ) )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config() - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1140,27 +1095,9 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_1, - user_1, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_2, - user_1, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), - collection_id, - item_3, - user_1, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1345,20 +1282,8 @@ fn create_cancel_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_1, - user_id, - default_item_config(), - )); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_2, - user_id, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); // validate desired item and the collection exists assert_noop!( @@ -1499,36 +1424,31 @@ fn claim_swap_should_work() { RuntimeOrigin::signed(user_1), collection_id, item_1, - user_1, - default_item_config(), + user_1 )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, item_2, - user_2, - default_item_config(), + user_2 )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, item_3, - user_2, - default_item_config(), + user_2 )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, item_4, - user_1, - default_item_config(), + user_1 )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, item_5, - user_2, - default_item_config(), + user_2 )); assert_ok!(Nfts::create_swap( @@ -1759,13 +1679,7 @@ fn pallet_level_feature_flags_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), - collection_id, - item_id, - user_id, - default_item_config(), - )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); // PalletFeature::NoTrading assert_noop!( diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 00042be715296..5c2c324bb90e1 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -205,10 +205,12 @@ impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); #[derive(Encode, Decode, Default, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] pub struct CollectionConfig { - /// Collection's bitflag settings. + /// Collection's settings. pub(super) settings: CollectionSettings, /// Collection's max supply. pub(super) max_supply: Option, + /// Default settings each item will get during the mint. + pub(super) mint_item_settings: ItemSettings, } // Support for up to 64 user-enabled features on an item. @@ -223,21 +225,26 @@ pub enum ItemSetting { /// Disallow to modify attributes of this item. LockedAttributes, } -pub(super) type ItemSettings = BitFlags; -/// Wrapper type for `ItemSettings` that implements `Codec`. +/// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] -pub struct ItemConfig(pub ItemSettings); +pub struct ItemSettings(pub BitFlags); -impl ItemConfig { +impl ItemSettings { pub fn empty() -> Self { Self(BitFlags::EMPTY) } - pub fn values(&self) -> ItemSettings { + pub fn values(&self) -> BitFlags { self.0 } } -impl_codec_bitflags!(ItemConfig, u64, ItemSetting); +impl_codec_bitflags!(ItemSettings, u64, ItemSetting); + +#[derive(Encode, Decode, Default, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +pub struct ItemConfig { + /// Item's settings. + pub(super) settings: ItemSettings, +} // Support for up to 64 system-enabled features on a collection. #[bitflags] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 850195852cf72..2d923ee36c785 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -74,11 +74,11 @@ pub trait InspectEnumerable: Inspect { /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -158,37 +158,26 @@ impl< } impl< - F: nonfungibles::Mutate, + F: nonfungibles::Mutate, A: Get<>::CollectionId>, AccountId, - ItemConfig, - > Mutate for ItemOf + > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { - >::mint_into(&A::get(), item, who, config) + fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { + >::mint_into(&A::get(), item, who) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), item, maybe_check_owner) + >::burn(&A::get(), item, maybe_check_owner) } fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute( - &A::get(), - item, - key, - value, - ) + >::set_attribute(&A::get(), item, key, value) } fn set_typed_attribute( item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute( - &A::get(), - item, - key, - value, - ) + >::set_typed_attribute(&A::get(), item, key, value) } } diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index d23e6d67573c7..edad84e7a2179 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -159,7 +159,7 @@ pub trait Destroy: Inspect { /// Trait for providing an interface for multiple collections of NFT-like items which may be /// minted, burned and/or have attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. @@ -167,7 +167,6 @@ pub trait Mutate: Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _who: &AccountId, - _config: &ItemConfig, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } From 0efc4ec563101f51ae165a0726355db5dfa29c97 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 11 Oct 2022 15:46:38 +0300 Subject: [PATCH 52/94] Add different mint options --- frame/nfts/src/features/settings.rs | 2 +- frame/nfts/src/functions.rs | 6 ++-- frame/nfts/src/impl_nonfungibles.rs | 4 +-- frame/nfts/src/lib.rs | 49 ++++++++++++++++++++++++++--- frame/nfts/src/tests.rs | 28 ++++++----------- frame/nfts/src/types.rs | 48 +++++++++++++++++++++++++--- 6 files changed, 105 insertions(+), 32 deletions(-) diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 3a040fa29d858..c6d09413a83a0 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -26,7 +26,7 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { pub fn get_collection_config( collection_id: &T::CollectionId, - ) -> Result { + ) -> Result, DispatchError> { let config = CollectionConfigOf::::get(&collection_id).ok_or(Error::::NoConfig)?; Ok(config) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 939f359e57028..25ef23f9a2f6a 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -79,7 +79,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, owner: T::AccountId, admin: T::AccountId, - config: CollectionConfig, + config: CollectionConfigFor, deposit: DepositBalanceOf, event: Event, ) -> DispatchResult { @@ -176,8 +176,8 @@ impl, I: 'static> Pallet { let collection_config = Self::get_collection_config(&collection)?; let settings = collection_config.settings.values(); - let mint_item_settings = collection_config.mint_item_settings; - let item_config = ItemConfig { settings: mint_item_settings }; + let item_settings = collection_config.mint_settings.default_item_settings; + let item_config = ItemConfig { settings: item_settings }; if let Some(max_supply) = collection_config.max_supply { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 7988d29677889..b7a0c6ab1d8f4 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -91,7 +91,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } -impl, I: 'static> Create<::AccountId, CollectionConfig> +impl, I: 'static> Create<::AccountId, CollectionConfigFor> for Pallet { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. @@ -99,7 +99,7 @@ impl, I: 'static> Create<::AccountId, Collection collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, - config: &CollectionConfig, + config: &CollectionConfigFor, ) -> DispatchResult { let mut settings = config.settings.values(); // FreeHolding could be set by calling the force_create() only diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 0aa8bc1b990ae..1881fccf6623e 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -312,7 +312,7 @@ pub mod pallet { #[pallet::storage] /// Config of a collection. pub(super) type CollectionConfigOf, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfig, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; #[pallet::storage] /// Config of an item. @@ -579,7 +579,7 @@ pub mod pallet { pub fn create( origin: OriginFor, admin: AccountIdLookupOf, - config: CollectionConfig, + config: CollectionConfigFor, ) -> DispatchResult { let collection = NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); @@ -624,7 +624,7 @@ pub mod pallet { pub fn force_create( origin: OriginFor, owner: AccountIdLookupOf, - config: CollectionConfig, + config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; @@ -1190,7 +1190,7 @@ pub mod pallet { issuer: AccountIdLookupOf, admin: AccountIdLookupOf, freezer: AccountIdLookupOf, - config: CollectionConfig, + config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -1690,6 +1690,47 @@ pub mod pallet { }) } + /// Set the maximum amount of items a collection could have. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `max_supply`: The maximum amount of items a collection could have. + /// + /// Emits `CollectionMaxSupplySet` event when successful. + /*#[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn set_collection_max_supply( + origin: OriginFor, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + let (action_allowed, _) = Self::is_collection_setting_disabled( + &collection, + CollectionSetting::LockedMaxSupply, + )?; + ensure!(action_allowed, Error::::MaxSupplyLocked); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) + }*/ + /// Set (or reset) the price for an item. /// /// Origin must be Signed and must be the owner of the asset `item`. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 9f438553af0c4..b5f3202e9b834 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -93,22 +93,18 @@ fn events() -> Vec> { result } -fn default_collection_config() -> CollectionConfig { +fn default_collection_config() -> CollectionConfigFor { CollectionConfig { settings: CollectionSettings(CollectionSetting::FreeHolding.into()), - mint_item_settings: ItemSettings::empty(), ..Default::default() } } -fn make_collection_config( - settings: BitFlags, - max_supply: Option, -) -> CollectionConfig { - CollectionConfig { settings: CollectionSettings(settings), max_supply, ..Default::default() } +fn make_collection_config(settings: BitFlags) -> CollectionConfigFor { + CollectionConfig { settings: CollectionSettings(settings), ..Default::default() } } -impl CollectionConfig { +impl CollectionConfigFor { pub fn empty() -> Self { Self { settings: CollectionSettings::empty(), ..Default::default() } } @@ -225,8 +221,7 @@ fn transfer_should_work() { RuntimeOrigin::root(), 1, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, - None + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -640,7 +635,7 @@ fn force_collection_status_should_work() { 1, 1, 1, - make_collection_config(CollectionSetting::FreeHolding.into(), None), + make_collection_config(CollectionSetting::FreeHolding.into()), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); @@ -715,8 +710,7 @@ fn approval_lifecycle_works() { RuntimeOrigin::root(), 1, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, - None + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -827,7 +821,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - make_collection_config(CollectionSetting::FreeHolding.into(), None) + make_collection_config(CollectionSetting::FreeHolding.into()) )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); @@ -1061,8 +1055,7 @@ fn set_price_should_work() { RuntimeOrigin::root(), user_id, make_collection_config( - CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding, - None + CollectionSetting::NonTransferableItems | CollectionSetting::FreeHolding ) )); @@ -1604,7 +1597,7 @@ fn claim_swap_should_work() { fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it - let config = make_collection_config(CollectionSetting::NonTransferableItems.into(), None); + let config = make_collection_config(CollectionSetting::NonTransferableItems.into()); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); @@ -1615,7 +1608,6 @@ fn various_collection_settings() { // no need to call .into() for multiple values let config = make_collection_config( CollectionSetting::LockedMetadata | CollectionSetting::NonTransferableItems, - None, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 5c2c324bb90e1..caa3ba02d11a1 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -42,6 +42,11 @@ pub(super) type ItemTipOf = ItemTip< ::AccountId, BalanceOf, >; +pub(super) type CollectionConfigFor = CollectionConfig< + BalanceOf, + ::BlockNumber, + >::CollectionId, +>; pub trait Incrementable { fn increment(&self) -> Self; @@ -203,14 +208,47 @@ impl CollectionSettings { } impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); -#[derive(Encode, Decode, Default, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] -pub struct CollectionConfig { +#[derive( + Clone, Copy, Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub enum MintType { + #[default] + Private, + Public, + HolderOf(CollectionId), +} + +#[derive( + Clone, Copy, Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub struct MintSettings { + /// Mint type. + pub(super) mint_type: MintType, + /// An optional price per mint. + pub(super) price: Option, + /// When the mint starts. + pub(super) start_block: Option, + /// When the mint ends. + pub(super) end_block: Option, + /// Whether the mint is limited. If `true` then only one `item` per account is possible to + /// `mint`. + /// NOTE: In order this to work, put the `NonTransferable` setting into + /// `default_item_settings`. + pub(super) limited: bool, + /// Default settings each item will get during the mint. + pub(super) default_item_settings: ItemSettings, +} + +#[derive( + Clone, Copy, RuntimeDebug, Decode, Default, Encode, MaxEncodedLen, PartialEq, TypeInfo, +)] +pub struct CollectionConfig { /// Collection's settings. pub(super) settings: CollectionSettings, /// Collection's max supply. pub(super) max_supply: Option, /// Default settings each item will get during the mint. - pub(super) mint_item_settings: ItemSettings, + pub(super) mint_settings: MintSettings, } // Support for up to 64 user-enabled features on an item. @@ -240,7 +278,9 @@ impl ItemSettings { } impl_codec_bitflags!(ItemSettings, u64, ItemSetting); -#[derive(Encode, Decode, Default, PartialEq, Debug, Clone, Copy, MaxEncodedLen, TypeInfo)] +#[derive( + Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo, +)] pub struct ItemConfig { /// Item's settings. pub(super) settings: ItemSettings, From 2ab076f3c09e53b36aefc0a75b9db8cbd275de68 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 11 Oct 2022 17:01:54 +0300 Subject: [PATCH 53/94] Allow to change the mint settings --- frame/nfts/src/lib.rs | 32 +++++++++++++-------------- frame/nfts/src/tests.rs | 49 +++++++++++++++++++++++++++++++++++++++-- frame/nfts/src/types.rs | 2 -- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 1881fccf6623e..fccf5fa30e0dc 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -420,6 +420,8 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// Mint settings for a collection had changed. + CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emmited when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, /// The config of a collection has change. @@ -1690,46 +1692,42 @@ pub mod pallet { }) } - /// Set the maximum amount of items a collection could have. + /// Update mint settings. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of /// the `collection`. /// /// - `collection`: The identifier of the collection to change. - /// - `max_supply`: The maximum amount of items a collection could have. + /// - `mint_settings`: The new mint settings. /// - /// Emits `CollectionMaxSupplySet` event when successful. - /*#[pallet::weight(T::WeightInfo::set_collection_max_supply())] - pub fn set_collection_max_supply( + /// Emits `CollectionMintSettingsUpdated` event when successful. + #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn update_mint_settings( origin: OriginFor, collection: T::CollectionId, - max_supply: u32, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let (action_allowed, _) = Self::is_collection_setting_disabled( - &collection, - CollectionSetting::LockedMaxSupply, - )?; - ensure!(action_allowed, Error::::MaxSupplyLocked); - let details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } - ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); - CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - config.max_supply = Some(max_supply); - Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + config.mint_settings = mint_settings; + Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); Ok(()) }) - }*/ + } /// Set (or reset) the price for an item. /// diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index b5f3202e9b834..583df754f7bb2 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -609,8 +609,17 @@ fn preserve_config_for_frozen_items() { Error::::InconsistentItemConfig ); - // TODO: - // assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, expect_config)); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { + default_item_settings: ItemSettings( + ItemSetting::LockedAttributes | ItemSetting::LockedMetadata + ), + ..Default::default() + } + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); }); } @@ -991,6 +1000,42 @@ fn max_supply_should_work() { }); } +#[test] +fn mint_settings_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id).unwrap().settings, + ItemSettings::empty() + ); + + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig { + mint_settings: MintSettings { + default_item_settings: ItemSettings( + ItemSetting::NonTransferable | ItemSetting::LockedMetadata + ), + ..Default::default() + }, + ..default_collection_config() + } + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id).unwrap().settings, + ItemSettings(ItemSetting::NonTransferable | ItemSetting::LockedMetadata) + ); + }); +} + #[test] fn set_price_should_work() { new_test_ext().execute_with(|| { diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index caa3ba02d11a1..552ada65f4be6 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -299,8 +299,6 @@ pub enum PalletFeature { NoApprovals, /// Disallow atomic items swap. NoSwaps, - /// Disallow public mints. - NoPublicMints, } /// Wrapper type for `BitFlags` that implements `Codec`. From ad083ce08faaee41124ea0cc1b8f4c47a8ecdf1e Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 12 Oct 2022 11:33:28 +0300 Subject: [PATCH 54/94] Add a force_mint() method --- frame/nfts/src/functions.rs | 3 +- frame/nfts/src/impl_nonfungibles.rs | 5 +- frame/nfts/src/lib.rs | 46 ++++++- frame/nfts/src/tests.rs | 127 +++++++++--------- .../src/traits/tokens/nonfungible_v2.rs | 29 ++-- .../src/traits/tokens/nonfungibles_v2.rs | 3 +- 6 files changed, 133 insertions(+), 80 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 25ef23f9a2f6a..4f0e88f9d6acb 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -162,6 +162,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, + item_config: ItemConfig, with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -176,8 +177,6 @@ impl, I: 'static> Pallet { let collection_config = Self::get_collection_config(&collection)?; let settings = collection_config.settings.values(); - let item_settings = collection_config.mint_settings.default_item_settings; - let item_config = ItemConfig { settings: item_settings }; if let Some(max_supply) = collection_config.max_supply { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index b7a0c6ab1d8f4..8eac0f40fcf05 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -133,13 +133,14 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId> for Pallet { +impl, I: 'static> Mutate<::AccountId, ItemConfig> for Pallet { fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, who: &T::AccountId, + item_config: &ItemConfig, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), *item_config, |_| Ok(())) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index fccf5fa30e0dc..79ee5c5fc8787 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -688,8 +688,7 @@ pub mod pallet { /// The origin must be Signed and the sender must be the Issuer of the `collection`. /// /// - `collection`: The collection of the item to be minted. - /// - `item`: The item value of the item to be minted. - /// - `beneficiary`: The initial owner of the minted item. + /// - `item`: An identifier of the new item. /// /// Emits `Issued` event when successful. /// @@ -699,13 +698,50 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, item: T::ItemId, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + + let collection_config = Self::get_collection_config(&collection)?; + let item_settings = collection_config.mint_settings.default_item_settings; + let item_config = ItemConfig { settings: item_settings }; + + Self::do_mint(collection, item, caller.clone(), item_config, |collection_details| { + ensure!(collection_details.issuer == caller, Error::::NoPermission); + Ok(()) + }) + } + + /// Mint an item of a particular collection from a privileged origin. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the + /// Issuer of the `collection`. + /// + /// - `collection`: The collection of the item to be minted. + /// - `item`: An identifier of the new item. + /// - `owner`: An owner of the minted item. + /// - `item_config`: A config of the new item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::mint())] + pub fn force_mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, owner: AccountIdLookupOf, + item_config: ItemConfig, ) -> DispatchResult { - let origin = ensure_signed(origin)?; + let maybe_check_origin = match T::ForceOrigin::try_origin(origin) { + Ok(_) => None, + Err(origin) => Some(ensure_signed(origin)?), + }; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, |collection_details| { - ensure!(collection_details.issuer == origin, Error::::NoPermission); + Self::do_mint(collection, item, owner, item_config, |collection_details| { + if let Some(check_origin) = maybe_check_origin { + ensure!(collection_details.issuer == check_origin, Error::::NoPermission); + } Ok(()) }) } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 583df754f7bb2..d18f2439a0887 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -100,6 +100,10 @@ fn default_collection_config() -> CollectionConfigFor { } } +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::empty() } +} + fn make_collection_config(settings: BitFlags) -> CollectionConfigFor { CollectionConfig { settings: CollectionSettings(settings), ..Default::default() } } @@ -122,12 +126,12 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -143,9 +147,9 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 10)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 20)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 2); @@ -183,7 +187,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -192,7 +196,7 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -203,7 +207,7 @@ fn mint_should_work() { fn transfer_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_eq!(items(), vec![(3, 0, 42)]); @@ -225,7 +229,7 @@ fn transfer_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, 1, 42)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), @@ -238,7 +242,7 @@ fn transfer_should_work() { fn freezing_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); @@ -270,7 +274,7 @@ fn freezing_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -284,7 +288,7 @@ fn origin_guards_should_work() { ); assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2), Error::::NoPermission); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69), Error::::NoPermission); assert_noop!( Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission @@ -323,7 +327,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); @@ -348,7 +352,7 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); @@ -434,7 +438,7 @@ fn set_item_metadata_should_work() { // Cannot add metadata to unknown item assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), @@ -494,7 +498,7 @@ fn set_attribute_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -540,8 +544,8 @@ fn set_attribute_should_respect_lock() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -583,8 +587,8 @@ fn preserve_config_for_frozen_items() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1)); // if the item is not locked/frozen then the config gets deleted on item burn assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); @@ -605,7 +609,7 @@ fn preserve_config_for_frozen_items() { // can't mint with the different config assert_noop!( - Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1), + Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), Error::::InconsistentItemConfig ); @@ -619,7 +623,7 @@ fn preserve_config_for_frozen_items() { ..Default::default() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); }); } @@ -629,8 +633,8 @@ fn force_collection_status_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); @@ -646,8 +650,8 @@ fn force_collection_status_should_work() { 1, make_collection_config(CollectionSetting::FreeHolding.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 169, 2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); assert_eq!(Balances::reserved_balance(1), 65); @@ -678,8 +682,8 @@ fn burn_works() { Error::::UnknownCollection ); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 5)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 5)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); assert_eq!(Balances::reserved_balance(1), 2); assert_noop!( @@ -701,7 +705,7 @@ fn burn_works() { fn approval_lifecycle_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); assert_noop!( @@ -723,7 +727,7 @@ fn approval_lifecycle_works() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id)); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -736,7 +740,7 @@ fn approval_lifecycle_works() { fn cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -764,7 +768,7 @@ fn cancel_approval_works() { let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); // approval expires after 2 blocks. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); assert_noop!( @@ -783,7 +787,7 @@ fn cancel_approval_works() { fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); let current_block = 1; System::set_block_number(current_block); @@ -808,7 +812,7 @@ fn approving_multiple_accounts_works() { fn approvals_limit_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); for i in 3..13 { assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); @@ -832,7 +836,7 @@ fn approval_deadline_works() { 1, make_collection_config(CollectionSetting::FreeHolding.into()) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); // the approval expires after the 2nd block. assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); @@ -859,7 +863,7 @@ fn approval_deadline_works() { fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -887,7 +891,7 @@ fn cancel_approval_works_with_admin() { fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_noop!( @@ -915,7 +919,7 @@ fn cancel_approval_works_with_force() { fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); @@ -991,10 +995,10 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1)); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2), Error::::MaxSupplyReached ); }); @@ -1008,7 +1012,7 @@ fn mint_settings_should_work() { let item_id = 0; assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); assert_eq!( ItemConfigOf::::get(collection_id, item_id).unwrap().settings, ItemSettings::empty() @@ -1028,7 +1032,7 @@ fn mint_settings_should_work() { ..default_collection_config() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); assert_eq!( ItemConfigOf::::get(collection_id, item_id).unwrap().settings, ItemSettings(ItemSetting::NonTransferable | ItemSetting::LockedMetadata) @@ -1046,8 +1050,8 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1104,7 +1108,7 @@ fn set_price_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1133,9 +1137,9 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1320,8 +1324,8 @@ fn create_cancel_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2)); // validate desired item and the collection exists assert_noop!( @@ -1461,32 +1465,33 @@ fn claim_swap_should_work() { assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, - item_1, - user_1 + item_1 )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_2, - user_2 + user_2, + default_item_config(), )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_3, - user_2 + user_2, + default_item_config(), )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, - item_4, - user_1 + item_4 )); - assert_ok!(Nfts::mint( + assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), collection_id, item_5, - user_2 + user_2, + default_item_config(), )); assert_ok!(Nfts::create_swap( @@ -1716,7 +1721,7 @@ fn pallet_level_feature_flags_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, user_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); // PalletFeature::NoTrading assert_noop!( diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 2d923ee36c785..850195852cf72 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -74,11 +74,11 @@ pub trait InspectEnumerable: Inspect { /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -158,26 +158,37 @@ impl< } impl< - F: nonfungibles::Mutate, + F: nonfungibles::Mutate, A: Get<>::CollectionId>, AccountId, - > Mutate for ItemOf + ItemConfig, + > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { - >::mint_into(&A::get(), item, who) + fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { + >::mint_into(&A::get(), item, who, config) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), item, maybe_check_owner) + >::burn(&A::get(), item, maybe_check_owner) } fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute(&A::get(), item, key, value) + >::set_attribute( + &A::get(), + item, + key, + value, + ) } fn set_typed_attribute( item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute(&A::get(), item, key, value) + >::set_typed_attribute( + &A::get(), + item, + key, + value, + ) } } diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index edad84e7a2179..d23e6d67573c7 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -159,7 +159,7 @@ pub trait Destroy: Inspect { /// Trait for providing an interface for multiple collections of NFT-like items which may be /// minted, burned and/or have attributes set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. @@ -167,6 +167,7 @@ pub trait Mutate: Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _who: &AccountId, + _config: &ItemConfig, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } From 4ca6f001156c3b4a650f44ffde0f51505a6eee3a Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 12 Oct 2022 15:20:37 +0300 Subject: [PATCH 55/94] Check mint params --- frame/nfts/src/functions.rs | 10 +-- frame/nfts/src/impl_nonfungibles.rs | 2 +- frame/nfts/src/lib.rs | 65 +++++++++++++++++-- frame/nfts/src/tests.rs | 99 ++++++++++++++--------------- frame/nfts/src/types.rs | 18 ++++-- 5 files changed, 127 insertions(+), 67 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index 4f0e88f9d6acb..063e2744701e5 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -163,7 +163,10 @@ impl, I: 'static> Pallet { item: T::ItemId, owner: T::AccountId, item_config: ItemConfig, - with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, + with_details_and_config: impl FnOnce( + &CollectionDetailsFor, + &CollectionConfigFor, + ) -> DispatchResult, ) -> DispatchResult { ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); @@ -173,10 +176,8 @@ impl, I: 'static> Pallet { let collection_details = maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - with_details(collection_details)?; - let collection_config = Self::get_collection_config(&collection)?; - let settings = collection_config.settings.values(); + with_details_and_config(collection_details, &collection_config)?; if let Some(max_supply) = collection_config.max_supply { ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); @@ -186,6 +187,7 @@ impl, I: 'static> Pallet { collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; + let settings = collection_config.settings.values(); let deposit = match settings.contains(CollectionSetting::FreeHolding) { true => Zero::zero(), false => T::ItemDeposit::get(), diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 8eac0f40fcf05..6b885f22b1bc9 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -140,7 +140,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig who: &T::AccountId, item_config: &ItemConfig, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), *item_config, |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), *item_config, |_, _| Ok(())) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 79ee5c5fc8787..270f73d1502c8 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -65,7 +65,7 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -546,6 +546,10 @@ pub mod pallet { InconsistentItemConfig, /// Config for a collection or an item can't be found. NoConfig, + /// Mint has not started yet. + MintNotStated, + /// Mint has already ended. + MintEnded, } impl, I: 'static> Pallet { @@ -689,6 +693,8 @@ pub mod pallet { /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. + /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned + /// item_id from that collection needs to be provided within the witness data object. /// /// Emits `Issued` event when successful. /// @@ -698,6 +704,7 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, item: T::ItemId, + witness_data: Option>, ) -> DispatchResult { let caller = ensure_signed(origin)?; @@ -705,10 +712,56 @@ pub mod pallet { let item_settings = collection_config.mint_settings.default_item_settings; let item_config = ItemConfig { settings: item_settings }; - Self::do_mint(collection, item, caller.clone(), item_config, |collection_details| { - ensure!(collection_details.issuer == caller, Error::::NoPermission); - Ok(()) - }) + Self::do_mint( + collection, + item, + caller.clone(), + item_config, + |collection_details, collection_config| { + let mint_settings = collection_config.mint_settings; + let now = frame_system::Pallet::::block_number(); + + if let Some(start_block) = mint_settings.start_block { + ensure!(start_block >= now, Error::::MintNotStated); + } + if let Some(end_block) = mint_settings.end_block { + ensure!(end_block <= now, Error::::MintEnded); + } + + match mint_settings.mint_type { + MintType::Private => { + ensure!( + collection_details.issuer == caller, + Error::::NoPermission + ) + }, + MintType::HolderOf(collection_id) => { + let correct_witness = match witness_data { + Some(MintWitness { owner_of_item }) => + Account::::contains_key(( + &caller, + &collection_id, + &owner_of_item, + )), + None => false, + }; + ensure!(correct_witness, Error::::NoPermission) + }, + _ => {}, + } + + if let Some(price) = mint_settings.price { + T::Currency::transfer( + &caller, + &collection_details.owner, + price, + ExistenceRequirement::KeepAlive, + )?; + } + + Ok(()) + }, + ) } /// Mint an item of a particular collection from a privileged origin. @@ -738,7 +791,7 @@ pub mod pallet { }; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(collection, item, owner, item_config, |collection_details| { + Self::do_mint(collection, item, owner, item_config, |collection_details, _| { if let Some(check_origin) = maybe_check_origin { ensure!(collection_details.issuer == check_origin, Error::::NoPermission); } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index d18f2439a0887..2c8546007bb3f 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -108,12 +108,6 @@ fn make_collection_config(settings: BitFlags) -> CollectionCo CollectionConfig { settings: CollectionSettings(settings), ..Default::default() } } -impl CollectionConfigFor { - pub fn empty() -> Self { - Self { settings: CollectionSettings::empty(), ..Default::default() } - } -} - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -126,7 +120,7 @@ fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); @@ -187,7 +181,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create(RuntimeOrigin::signed(1), 1, default_collection_config())); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } @@ -196,7 +190,7 @@ fn destroy_with_bad_witness_should_not_work() { fn mint_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); @@ -242,7 +236,7 @@ fn transfer_should_work() { fn freezing_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(1), 0, 42)); assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); @@ -264,7 +258,7 @@ fn freezing_should_work() { 1, 1, 1, - CollectionConfig::empty(), + CollectionConfig::default(), )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); @@ -274,7 +268,7 @@ fn freezing_should_work() { fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); @@ -288,7 +282,10 @@ fn origin_guards_should_work() { ); assert_noop!(Nfts::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); assert_noop!(Nfts::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 0, 69), Error::::NoPermission); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, None), + Error::::NoPermission + ); assert_noop!( Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoPermission @@ -327,7 +324,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); @@ -352,7 +349,7 @@ fn set_team_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, None)); assert_ok!(Nfts::freeze(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::thaw(RuntimeOrigin::signed(4), 0, 42)); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); @@ -368,7 +365,7 @@ fn set_collection_metadata_should_work() { Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), Error::::NoConfig, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); // Cannot add metadata to unowned item assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20]), @@ -437,8 +434,8 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); // Cannot add metadata to unowned item assert_noop!( Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), @@ -497,8 +494,8 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -543,9 +540,9 @@ fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -586,9 +583,9 @@ fn preserve_config_for_frozen_items() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); // if the item is not locked/frozen then the config gets deleted on item burn assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); @@ -623,7 +620,7 @@ fn preserve_config_for_frozen_items() { ..Default::default() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); }); } @@ -632,8 +629,8 @@ fn force_collection_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); @@ -650,7 +647,7 @@ fn force_collection_status_should_work() { 1, make_collection_config(CollectionSetting::FreeHolding.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); @@ -674,7 +671,7 @@ fn force_collection_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::empty())); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, CollectionConfig::default())); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -727,7 +724,7 @@ fn approval_lifecycle_works() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, None)); assert_noop!( Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), @@ -995,10 +992,10 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, None)); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2), + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, None), Error::::MaxSupplyReached ); }); @@ -1012,7 +1009,7 @@ fn mint_settings_should_work() { let item_id = 0; assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); assert_eq!( ItemConfigOf::::get(collection_id, item_id).unwrap().settings, ItemSettings::empty() @@ -1032,7 +1029,7 @@ fn mint_settings_should_work() { ..default_collection_config() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); assert_eq!( ItemConfigOf::::get(collection_id, item_id).unwrap().settings, ItemSettings(ItemSetting::NonTransferable | ItemSetting::LockedMetadata) @@ -1050,8 +1047,8 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_id), @@ -1108,7 +1105,7 @@ fn set_price_should_work() { ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); assert_noop!( Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), @@ -1137,9 +1134,9 @@ fn buy_item_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, None)); assert_ok!(Nfts::set_price( RuntimeOrigin::signed(user_1), @@ -1244,7 +1241,7 @@ fn buy_item_should_work() { user_1, user_1, user_1, - CollectionConfig::empty(), + CollectionConfig::default(), )); // freeze the item @@ -1324,8 +1321,8 @@ fn create_cancel_swap_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, None)); // validate desired item and the collection exists assert_noop!( @@ -1465,7 +1462,8 @@ fn claim_swap_should_work() { assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, - item_1 + item_1, + None, )); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), @@ -1484,7 +1482,8 @@ fn claim_swap_should_work() { assert_ok!(Nfts::mint( RuntimeOrigin::signed(user_1), collection_id, - item_4 + item_4, + None, )); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(user_1), @@ -1676,7 +1675,7 @@ fn collection_locking_should_work() { let user_id = 1; let collection_id = 0; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, CollectionConfig::empty(),)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, CollectionConfig::default())); // validate partial lock let lock_settings = CollectionSettings( @@ -1721,7 +1720,7 @@ fn pallet_level_feature_flags_should_work() { assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, item_id, None)); // PalletFeature::NoTrading assert_noop!( diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 552ada65f4be6..eedfd377ad8a5 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -206,6 +206,11 @@ impl CollectionSettings { self.0 } } +impl From for CollectionSettings { + fn from(input: CollectionSetting) -> Self { + Self(input.into()) + } +} impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); #[derive( @@ -230,17 +235,18 @@ pub struct MintSettings { pub(super) start_block: Option, /// When the mint ends. pub(super) end_block: Option, - /// Whether the mint is limited. If `true` then only one `item` per account is possible to - /// `mint`. - /// NOTE: In order this to work, put the `NonTransferable` setting into - /// `default_item_settings`. - pub(super) limited: bool, /// Default settings each item will get during the mint. pub(super) default_item_settings: ItemSettings, } +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// The total number of outstanding items of this collection. + pub owner_of_item: ItemId, +} + #[derive( - Clone, Copy, RuntimeDebug, Decode, Default, Encode, MaxEncodedLen, PartialEq, TypeInfo, + Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, )] pub struct CollectionConfig { /// Collection's settings. From 340cb8137846f8daa53ac2b13bf6bb1788d39745 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 12 Oct 2022 15:40:41 +0300 Subject: [PATCH 56/94] Some optimisations --- frame/nfts/src/benchmarking.rs | 35 ++++++++++++++++++++-------------- frame/nfts/src/tests.rs | 9 +++------ frame/nfts/src/types.rs | 5 +++++ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 9715977b719d8..cca8644e244b4 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -38,7 +38,10 @@ use crate::Pallet as Nfts; const SEED: u32 = 0; fn create_collection, I: 'static>( -) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) { +) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) +where + >::CollectionId: sp_std::default::Default, +{ let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); @@ -46,7 +49,7 @@ fn create_collection, I: 'static>( assert_ok!(Nfts::::force_create( SystemOrigin::Root.into(), caller_lookup.clone(), - CollectionConfig::empty() + default_collection_config::() )); (collection, caller, caller_lookup) } @@ -78,8 +81,7 @@ fn mint_item, I: 'static>( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, - caller_lookup.clone(), - ItemConfig::empty(), + None, )); (item, caller, caller_lookup) } @@ -128,6 +130,13 @@ fn assert_last_event, I: 'static>(generic_event: >:: assert_eq!(event, &system_event); } +fn default_collection_config, I: 'static>() -> CollectionConfigFor +where + >::CollectionId: sp_std::default::Default, +{ + CollectionConfig::default() +} + benchmarks_instance_pallet! { create { let collection = T::Helper::collection(0); @@ -136,7 +145,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { admin, config: CollectionConfig::empty() }; + let call = Call::::create { admin, config: default_collection_config::() }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -145,7 +154,7 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, caller_lookup, CollectionConfig::empty()) + }: _(SystemOrigin::Root, caller_lookup, default_collection_config::()) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } @@ -169,7 +178,7 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, ItemConfig::empty()) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, None) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -204,7 +213,7 @@ benchmarks_instance_pallet! { caller_lookup.clone(), caller_lookup.clone(), caller_lookup, - CollectionConfig(CollectionSetting::FreeHolding.into()), + CollectionConfig { settings: CollectionSetting::FreeHolding.into(), ..Default::default() }, )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { @@ -234,12 +243,10 @@ benchmarks_instance_pallet! { lock_collection { let (collection, caller, caller_lookup) = create_collection::(); - let lock_config = CollectionConfig( - CollectionSetting::NonTransferableItems | + let lock_settings = CollectionSettings(CollectionSetting::NonTransferableItems | CollectionSetting::LockedMetadata | - CollectionSetting::LockedAttributes, - ); - }: _(SystemOrigin::Signed(caller.clone()), collection, lock_config) + CollectionSetting::LockedAttributes); + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) verify { assert_last_event::(Event::CollectionLocked { collection }.into()); } @@ -280,7 +287,7 @@ benchmarks_instance_pallet! { issuer: caller_lookup.clone(), admin: caller_lookup.clone(), freezer: caller_lookup, - config: CollectionConfig(CollectionSetting::FreeHolding.into()), + config: CollectionConfig { settings: CollectionSetting::FreeHolding.into(), ..Default::default() }, }; }: { call.dispatch_bypass_filter(origin)? } verify { diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 2c8546007bb3f..f26136dc48e0d 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -94,18 +94,15 @@ fn events() -> Vec> { } fn default_collection_config() -> CollectionConfigFor { - CollectionConfig { - settings: CollectionSettings(CollectionSetting::FreeHolding.into()), - ..Default::default() - } + CollectionConfig { settings: CollectionSetting::FreeHolding.into(), ..Default::default() } } fn default_item_config() -> ItemConfig { - ItemConfig { settings: ItemSettings::empty() } + ItemConfig { settings: ItemSettings::default() } } fn make_collection_config(settings: BitFlags) -> CollectionConfigFor { - CollectionConfig { settings: CollectionSettings(settings), ..Default::default() } + CollectionConfig { settings: settings.into(), ..Default::default() } } #[test] diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index eedfd377ad8a5..ef0e6b56c2786 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -211,6 +211,11 @@ impl From for CollectionSettings { Self(input.into()) } } +impl From> for CollectionSettings { + fn from(input: BitFlags) -> Self { + Self(input) + } +} impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); #[derive( From 025cfdd278616fe9a16cbd93b8fc92515029dc8a Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 12 Oct 2022 17:52:36 +0300 Subject: [PATCH 57/94] Cover with tests --- frame/nfts/src/lib.rs | 6 +++--- frame/nfts/src/tests.rs | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 270f73d1502c8..463e74d11e8f7 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -722,10 +722,10 @@ pub mod pallet { let now = frame_system::Pallet::::block_number(); if let Some(start_block) = mint_settings.start_block { - ensure!(start_block >= now, Error::::MintNotStated); + ensure!(start_block <= now, Error::::MintNotStated); } if let Some(end_block) = mint_settings.end_block { - ensure!(end_block <= now, Error::::MintEnded); + ensure!(end_block >= now, Error::::MintEnded); } match mint_settings.mint_type { @@ -745,7 +745,7 @@ pub mod pallet { )), None => false, }; - ensure!(correct_witness, Error::::NoPermission) + ensure!(correct_witness, Error::::BadWitness) }, _ => {}, } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index f26136dc48e0d..602f86d6ae2ec 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -191,6 +191,51 @@ fn mint_should_work() { assert_eq!(Nfts::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); assert_eq!(items(), vec![(1, 0, 42)]); + + // validate minting start and end settings + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { start_block: Some(2), end_block: Some(3), ..Default::default() } + )); + + System::set_block_number(1); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), + Error::::MintNotStated + ); + System::set_block_number(4); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(1), 0, 43, None), Error::::MintEnded); + + // validate price + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } + )); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, None)); + assert_eq!(Balances::total_balance(&2), 99); + + // validate types + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 1, + MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } + )); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(3), 1, 42, None), Error::::BadWitness); + assert_noop!(Nfts::mint(RuntimeOrigin::signed(2), 1, 42, None), Error::::BadWitness); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, Some(MintWitness { owner_of_item: 42 })), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(2), + 1, + 42, + Some(MintWitness { owner_of_item: 43 }) + )); }); } From 6b96856e16cdcdc30ba1fa3dec023038cdf57651 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 19 Oct 2022 18:02:23 +0300 Subject: [PATCH 58/94] Remove merge artifacts --- frame/nfts/src/features/freeze.rs | 70 ------------------------------ frame/nfts/src/features/macros.rs | 72 ------------------------------- 2 files changed, 142 deletions(-) delete mode 100644 frame/nfts/src/features/freeze.rs delete mode 100644 frame/nfts/src/features/macros.rs diff --git a/frame/nfts/src/features/freeze.rs b/frame/nfts/src/features/freeze.rs deleted file mode 100644 index 22bc3967af158..0000000000000 --- a/frame/nfts/src/features/freeze.rs +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::*; -use frame_support::pallet_prelude::*; - -/// Freeze functions allow to make particular items non-transferable. -/// It's also possible to revert the setting back. -/// An origin must have a `Freezer` role in order to call those methods. -impl, I: 'static> Pallet { - pub fn do_freeze_item( - origin: T::AccountId, - collection: T::CollectionId, - item: T::ItemId, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - let mut settings = Self::get_item_settings(&collection, &item)?; - if !settings.contains(ItemSetting::NonTransferable) { - settings.insert(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert( - &collection, - &item, - ItemConfig { settings: ItemSettings(settings) }, - ); - - Self::deposit_event(Event::::Frozen { collection, item }); - Ok(()) - } - - pub fn do_thaw_item( - origin: T::AccountId, - collection: T::CollectionId, - item: T::ItemId, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(collection_details.freezer == origin, Error::::NoPermission); - - let mut settings = Self::get_item_settings(&collection, &item)?; - if settings.contains(ItemSetting::NonTransferable) { - settings.remove(ItemSetting::NonTransferable); - } - ItemConfigOf::::insert( - &collection, - &item, - ItemConfig { settings: ItemSettings(settings) }, - ); - - Self::deposit_event(Event::::Thawed { collection, item }); - Ok(()) - } -} diff --git a/frame/nfts/src/features/macros.rs b/frame/nfts/src/features/macros.rs deleted file mode 100644 index d006e82bd2bea..0000000000000 --- a/frame/nfts/src/features/macros.rs +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -macro_rules! impl_incrementable { - ($($type:ty),+) => { - $( - impl Incrementable for $type { - fn increment(&self) -> Self { - self.saturating_add(1) - } - - fn initial_value() -> Self { - 0 - } - } - )+ - }; -} -pub(crate) use impl_incrementable; - -macro_rules! impl_codec_bitflags { - ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { - impl MaxEncodedLen for $wrapper { - fn max_encoded_len() -> usize { - <$size>::max_encoded_len() - } - } - impl Encode for $wrapper { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } - } - impl EncodeLike for $wrapper {} - impl Decode for $wrapper { - fn decode( - input: &mut I, - ) -> sp_std::result::Result { - let field = <$size>::decode(input)?; - Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) - } - } - - impl TypeInfo for $wrapper { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))]) - .composite( - Fields::unnamed() - .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), - ) - } - } - }; -} -pub(crate) use impl_codec_bitflags; From 45227f560118fd17974a8fa9e0627ddc9ca63b20 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 19 Oct 2022 18:34:16 +0300 Subject: [PATCH 59/94] Chore --- frame/nfts/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index e0af67219bf6b..b6eb0ecf9ec92 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -249,7 +249,7 @@ impl Default for MintSettings { - /// The total number of outstanding items of this collection. + /// Provide the id of the item in a required collection. pub owner_of_item: ItemId, } @@ -292,6 +292,7 @@ impl CollectionConfig Date: Thu, 20 Oct 2022 17:08:30 +0300 Subject: [PATCH 60/94] Use the new has_role() method --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8b7f85932c84c..7213719a54bcd 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -740,7 +740,7 @@ pub mod pallet { match mint_settings.mint_type { MintType::Private => { ensure!( - collection_details.issuer == caller, + Self::has_role(&collection, &caller, CollectionRole::Issuer), Error::::NoPermission ) }, From 9d1d11baa8ad975ab812e88862829c15b5b684f6 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 20 Oct 2022 18:59:21 +0300 Subject: [PATCH 61/94] Rework item deposits --- frame/nfts/src/functions.rs | 26 +++++++++++++++---- frame/nfts/src/impl_nonfungibles.rs | 10 ++++++- frame/nfts/src/lib.rs | 18 ++++++------- frame/nfts/src/tests.rs | 6 ++--- frame/nfts/src/types.rs | 17 +++++++++--- .../src/traits/tokens/nonfungible_v2.rs | 22 +++++++++++++--- .../src/traits/tokens/nonfungibles_v2.rs | 1 + 7 files changed, 75 insertions(+), 25 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index c40742527b84f..d438e92ce768b 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -54,6 +54,16 @@ impl, I: 'static> Pallet { Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; with_details(&collection_details, &mut details)?; + if details.deposit.account == details.owner { + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &dest, + details.deposit.amount, + Reserved, + )?; + } + Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); let origin = details.owner; @@ -138,6 +148,7 @@ impl, I: 'static> Pallet { for (item, details) in Item::::drain_prefix(&collection) { Account::::remove((&details.owner, &collection, &item)); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); } #[allow(deprecated)] ItemMetadataOf::::remove_prefix(&collection, None); @@ -169,6 +180,7 @@ impl, I: 'static> Pallet { item: T::ItemId, owner: T::AccountId, item_config: ItemConfig, + deposit_collection_owner: bool, with_details_and_config: impl FnOnce( &CollectionDetailsFor, &CollectionConfigFor, @@ -194,14 +206,16 @@ impl, I: 'static> Pallet { collection_details.items = items; let collection_config = Self::get_collection_config(&collection)?; - let deposit = match collection_config + let deposit_amount = match collection_config .is_setting_enabled(CollectionSetting::DepositRequired) { true => T::ItemDeposit::get(), false => Zero::zero(), }; - T::Currency::reserve(&collection_details.owner, deposit)?; - collection_details.total_deposit += deposit; + let deposit_account = match deposit_collection_owner { + true => collection_details.owner.clone(), + false => owner.clone(), + }; let owner = owner.clone(); Account::::insert((&owner, &collection, &item), ()); @@ -212,6 +226,9 @@ impl, I: 'static> Pallet { ItemConfigOf::::insert(&collection, &item, item_config); } + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { amount: deposit_amount, account: deposit_account }; let details = ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; Item::::insert(&collection, &item, details); @@ -238,8 +255,7 @@ impl, I: 'static> Pallet { with_details(&details)?; // Return the deposit. - T::Currency::unreserve(&collection_details.owner, details.deposit); - collection_details.total_deposit.saturating_reduce(details.deposit); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); collection_details.items.saturating_dec(); Ok(details.owner) }, diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index fd02ef830999c..b42147e6687d9 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -140,8 +140,16 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig item: &Self::ItemId, who: &T::AccountId, item_config: &ItemConfig, + deposit_collection_owner: bool, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), *item_config, |_, _| Ok(())) + Self::do_mint( + *collection, + *item, + who.clone(), + *item_config, + deposit_collection_owner, + |_, _| Ok(()), + ) } fn burn( diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 7213719a54bcd..08e0c13f89d1b 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -241,7 +241,7 @@ pub mod pallet { T::CollectionId, Blake2_128Concat, T::ItemId, - ItemDetails, ApprovalsOf>, + ItemDetails, ApprovalsOf>, OptionQuery, >; @@ -726,6 +726,7 @@ pub mod pallet { item, caller.clone(), item_config, + false, |collection_details, collection_config| { let mint_settings = collection_config.mint_settings; let now = frame_system::Pallet::::block_number(); @@ -806,7 +807,7 @@ pub mod pallet { Error::::NoPermission ); } - Self::do_mint(collection, item, owner, item_config, |_, _| Ok(())) + Self::do_mint(collection, item, owner, item_config, true, |_, _| Ok(())) } /// Destroy a single item. @@ -908,7 +909,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut collection_details = + let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); @@ -924,11 +925,11 @@ pub mod pallet { Some(x) => x, None => continue, }; - let old = details.deposit; + let old = details.deposit.amount; if old > deposit { - T::Currency::unreserve(&collection_details.owner, old - deposit); + T::Currency::unreserve(&details.deposit.account, old - deposit); } else if deposit > old { - if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() { + if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { // NOTE: No alterations made to collection_details in this iteration so far, // so this is OK to do. continue @@ -936,13 +937,10 @@ pub mod pallet { } else { continue } - collection_details.total_deposit.saturating_accrue(deposit); - collection_details.total_deposit.saturating_reduce(old); - details.deposit = deposit; + details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); successful.push(item); } - Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::::Redeposited { collection, diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 9dc81bf417e9c..b2debac213eb8 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -384,10 +384,10 @@ fn transfer_owner_should_work() { assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); assert_eq!(collections(), vec![(3, 0)]); - assert_eq!(Balances::total_balance(&2), 57); - assert_eq!(Balances::total_balance(&3), 145); + assert_eq!(Balances::total_balance(&2), 58); + assert_eq!(Balances::total_balance(&3), 144); assert_eq!(Balances::reserved_balance(&2), 0); - assert_eq!(Balances::reserved_balance(&3), 45); + assert_eq!(Balances::reserved_balance(&3), 44); // 2's acceptence from before is reset when it became owner, so it cannot be transfered // without a fresh acceptance. diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 836e728806cf4..65abcaac64855 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -36,8 +36,10 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< Option<::BlockNumber>, >::ApprovalsLimit, >; +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; pub(super) type ItemDetailsFor = - ItemDetails<::AccountId, DepositBalanceOf, ApprovalsOf>; + ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; pub(super) type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; pub(super) type ItemPrice = BalanceOf; @@ -100,14 +102,23 @@ impl CollectionDetails { /// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] -pub struct ItemDetails { +pub struct ItemDetails { /// The owner of this item. pub(super) owner: AccountId, /// The approved transferrer of this item, if one is set. pub(super) approvals: Approvals, /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. - pub(super) deposit: DepositBalance, + pub(super) deposit: Deposit, +} + +/// Information about the reserved item deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemDeposit { + /// An amount that gets reserved. + pub(super) amount: DepositBalance, + /// A depositor account. + pub(super) account: AccountId, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 850195852cf72..4f610d9b80a05 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -78,7 +78,12 @@ pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig) -> DispatchResult { + fn mint_into( + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + _deposit_collection_owner: bool, + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -164,8 +169,19 @@ impl< ItemConfig, > Mutate for ItemOf { - fn mint_into(item: &Self::ItemId, who: &AccountId, config: &ItemConfig) -> DispatchResult { - >::mint_into(&A::get(), item, who, config) + fn mint_into( + item: &Self::ItemId, + who: &AccountId, + config: &ItemConfig, + deposit_collection_owner: bool, + ) -> DispatchResult { + >::mint_into( + &A::get(), + item, + who, + config, + deposit_collection_owner, + ) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { >::burn(&A::get(), item, maybe_check_owner) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index d23e6d67573c7..0aec193f68fcb 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -168,6 +168,7 @@ pub trait Mutate: Inspect { _item: &Self::ItemId, _who: &AccountId, _config: &ItemConfig, + _deposit_collection_owner: bool, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } From 8ffe4fd03f9428d63ba96884a26d75834a988595 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 21 Oct 2022 14:47:31 +0300 Subject: [PATCH 62/94] More tests --- frame/nfts/src/functions.rs | 2 +- frame/nfts/src/tests.rs | 5 +++++ frame/nfts/src/types.rs | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index d438e92ce768b..d556bdd4b628f 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -228,7 +228,7 @@ impl, I: 'static> Pallet { T::Currency::reserve(&deposit_account, deposit_amount)?; - let deposit = ItemDeposit { amount: deposit_amount, account: deposit_account }; + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; let details = ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; Item::::insert(&collection, &item, details); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index b2debac213eb8..915ee2f26f932 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -380,6 +380,7 @@ fn transfer_owner_should_work() { // Mint and set metadata now and make sure that deposit gets transferred back. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); + assert_eq!(Balances::reserved_balance(&1), 1); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); @@ -389,6 +390,10 @@ fn transfer_owner_should_work() { assert_eq!(Balances::reserved_balance(&2), 0); assert_eq!(Balances::reserved_balance(&3), 44); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&2), 1); + // 2's acceptence from before is reset when it became owner, so it cannot be transfered // without a fresh acceptance. assert_noop!( diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 65abcaac64855..79520a0db9c84 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -115,10 +115,10 @@ pub struct ItemDetails { /// Information about the reserved item deposit. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemDeposit { - /// An amount that gets reserved. - pub(super) amount: DepositBalance, /// A depositor account. pub(super) account: AccountId, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] From c7dcdb09aa1d65f4c7bf8522693dcb245e34d3b6 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 21 Oct 2022 15:42:16 +0300 Subject: [PATCH 63/94] Refactoring --- frame/nfts/src/benchmarking.rs | 15 ++-- frame/nfts/src/features/lock.rs | 10 +-- frame/nfts/src/lib.rs | 6 +- frame/nfts/src/tests.rs | 119 +++++++++++++++++++------------- frame/nfts/src/types.rs | 55 ++++++--------- 5 files changed, 108 insertions(+), 97 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index ec26bd77fb19a..45371ae7fb610 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -130,11 +130,12 @@ fn assert_last_event, I: 'static>(generic_event: >:: assert_eq!(event, &system_event); } -fn default_collection_config, I: 'static>() -> CollectionConfigFor -where - >::CollectionId: sp_std::default::Default, -{ - CollectionConfig::all_settings_enabled() +fn default_collection_config, I: 'static>() -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } } benchmarks_instance_pallet! { @@ -243,12 +244,12 @@ benchmarks_instance_pallet! { lock_collection { let (collection, caller, caller_lookup) = create_collection::(); - let lock_config = CollectionConfig( + let lock_settings = CollectionSettings::from_disabled( CollectionSetting::TransferableItems | CollectionSetting::UnlockedMetadata | CollectionSetting::UnlockedAttributes, ); - }: _(SystemOrigin::Signed(caller.clone()), collection, lock_config) + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) verify { assert_last_event::(Event::CollectionLocked { collection }.into()); } diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 7bc3a39ee0f13..e388eb47aba1b 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -22,7 +22,7 @@ impl, I: 'static> Pallet { pub(crate) fn do_lock_collection( origin: T::AccountId, collection: T::CollectionId, - lock_config: CollectionConfigFor, + lock_settings: CollectionSettings, ) -> DispatchResult { ensure!( Self::has_role(&collection, &origin, CollectionRole::Freezer), @@ -31,16 +31,16 @@ impl, I: 'static> Pallet { CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - if lock_config.has_disabled_setting(CollectionSetting::TransferableItems) { + if lock_settings.is_disabled(CollectionSetting::TransferableItems) { config.disable_setting(CollectionSetting::TransferableItems); } - if lock_config.has_disabled_setting(CollectionSetting::UnlockedMetadata) { + if lock_settings.is_disabled(CollectionSetting::UnlockedMetadata) { config.disable_setting(CollectionSetting::UnlockedMetadata); } - if lock_config.has_disabled_setting(CollectionSetting::UnlockedAttributes) { + if lock_settings.is_disabled(CollectionSetting::UnlockedAttributes) { config.disable_setting(CollectionSetting::UnlockedAttributes); } - if lock_config.has_disabled_setting(CollectionSetting::UnlockedMaxSupply) { + if lock_settings.is_disabled(CollectionSetting::UnlockedMaxSupply) { config.disable_setting(CollectionSetting::UnlockedMaxSupply); } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 08e0c13f89d1b..b839a5664ca6f 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -995,7 +995,7 @@ pub mod pallet { /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// /// - `collection`: The collection to be locked. - /// - `lock_config`: The config with the settings to be locked. + /// - `lock_settings`: The settings to be locked. /// /// Note: it's possible to only lock(set) the setting, but not to unset it. /// Emits `CollectionLocked`. @@ -1005,10 +1005,10 @@ pub mod pallet { pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, - lock_config: CollectionConfigFor, + lock_settings: CollectionSettings, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::do_lock_collection(origin, collection, lock_config) + Self::do_lock_collection(origin, collection, lock_settings) } /// Change the Owner of a collection. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 915ee2f26f932..66da53c8db316 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -18,6 +18,7 @@ //! Tests for Nfts pallet. use crate::{mock::*, Event, *}; +use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, @@ -92,12 +93,34 @@ fn events() -> Vec> { result } +fn collection_config_from_disabled_settings( + settings: BitFlags, +) -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::from_disabled(settings), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn collection_config_with_all_settings_enabled() -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + fn default_collection_config() -> CollectionConfigFor { - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) } fn default_item_config() -> ItemConfig { - ItemConfig::all_settings_enabled() + ItemConfig { settings: ItemSettings::all_enabled() } +} + +fn item_config_from_disabled_settings(settings: BitFlags) -> ItemConfig { + ItemConfig { settings: ItemSettings::from_disabled(settings) } } #[test] @@ -129,7 +152,7 @@ fn lifecycle_should_work() { assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); @@ -177,7 +200,7 @@ fn destroy_with_bad_witness_should_not_work() { assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); let w = Collection::::get(0).unwrap().destroy_witness(); @@ -263,7 +286,7 @@ fn transfer_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); @@ -289,7 +312,7 @@ fn locking_transfer_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), @@ -303,7 +326,7 @@ fn locking_transfer_should_work() { 1, 1, 1, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); @@ -355,7 +378,7 @@ fn transfer_owner_should_work() { assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( @@ -428,7 +451,7 @@ fn set_collection_metadata_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); // Cannot add metadata to unowned item assert_noop!( @@ -462,7 +485,7 @@ fn set_collection_metadata_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()) + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()) )); assert_noop!( Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), @@ -501,7 +524,7 @@ fn set_item_metadata_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); // Cannot add metadata to unowned item @@ -565,7 +588,7 @@ fn set_attribute_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); @@ -615,7 +638,7 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); @@ -637,7 +660,7 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(1), 0, - CollectionConfig::disable_settings(CollectionSetting::UnlockedAttributes.into()) + CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into()) )); let e = Error::::LockedCollectionAttributes; @@ -662,7 +685,7 @@ fn preserve_config_for_frozen_items() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); @@ -674,7 +697,7 @@ fn preserve_config_for_frozen_items() { // lock the item and ensure the config stays unchanged assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, true, true)); - let expect_config = ItemConfig::disable_settings( + let expect_config = item_config_from_disabled_settings( ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata, ); let config = ItemConfigOf::::get(0, 0).unwrap(); @@ -694,7 +717,7 @@ fn preserve_config_for_frozen_items() { RuntimeOrigin::signed(1), 0, MintSettings { - default_item_settings: ItemSettings( + default_item_settings: ItemSettings::from_disabled( ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata ), ..Default::default() @@ -712,7 +735,7 @@ fn force_collection_status_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); @@ -729,7 +752,7 @@ fn force_collection_status_should_work() { 1, 1, 1, - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()), + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); @@ -757,7 +780,7 @@ fn force_collection_status_should_work() { 2, 3, 4, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); assert_eq!( CollectionRoleOf::::get(0, 2).unwrap(), @@ -779,7 +802,7 @@ fn force_collection_status_should_work() { 3, 2, 3, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); assert_eq!( @@ -800,7 +823,7 @@ fn burn_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); @@ -849,7 +872,7 @@ fn approval_lifecycle_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); @@ -961,7 +984,7 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, - CollectionConfig::disable_settings(CollectionSetting::DepositRequired.into()) + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) )); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); @@ -1110,7 +1133,7 @@ fn max_supply_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - CollectionConfig::disable_settings(CollectionSetting::UnlockedMaxSupply.into()) + CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into()) )); assert_noop!( Nfts::set_collection_max_supply( @@ -1143,8 +1166,9 @@ fn mint_settings_should_work() { assert_eq!( ItemConfigOf::::get(collection_id, item_id) .unwrap() - .get_disabled_settings(), - ItemSettings::all_settings_enabled().get_disabled_settings() + .settings + .get_disabled(), + ItemSettings::all_enabled().get_disabled() ); let collection_id = 1; @@ -1153,7 +1177,7 @@ fn mint_settings_should_work() { user_id, CollectionConfig { mint_settings: MintSettings { - default_item_settings: ItemSettings( + default_item_settings: ItemSettings::from_disabled( ItemSetting::Transferable | ItemSetting::UnlockedMetadata ), ..Default::default() @@ -1165,11 +1189,10 @@ fn mint_settings_should_work() { assert_eq!( ItemConfigOf::::get(collection_id, item_id) .unwrap() - .get_disabled_settings(), - ItemSettings::disable_settings( - ItemSetting::Transferable | ItemSetting::UnlockedMetadata - ) - .get_disabled_settings() + .settings + .get_disabled(), + ItemSettings::from_disabled(ItemSetting::Transferable | ItemSetting::UnlockedMetadata) + .get_disabled() ); }); } @@ -1237,7 +1260,7 @@ fn set_price_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig::disable_settings( + collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); @@ -1357,7 +1380,7 @@ fn buy_item_should_work() { assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_1), collection_id, - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()) + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { @@ -1378,7 +1401,7 @@ fn buy_item_should_work() { user_1, user_1, user_1, - CollectionConfig::all_settings_enabled(), + collection_config_with_all_settings_enabled(), )); // lock the transfer @@ -1788,7 +1811,7 @@ fn various_collection_settings() { new_test_ext().execute_with(|| { // when we set only one value it's required to call .into() on it let config = - CollectionConfig::disable_settings(CollectionSetting::TransferableItems.into()); + collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into()); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); let config = CollectionConfigOf::::get(0).unwrap(); @@ -1796,7 +1819,7 @@ fn various_collection_settings() { assert!(config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); // no need to call .into() for multiple values - let config = CollectionConfig::disable_settings( + let config = collection_config_from_disabled_settings( CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, ); assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); @@ -1818,35 +1841,35 @@ fn collection_locking_should_work() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), user_id, - CollectionConfig::all_settings_enabled() + collection_config_with_all_settings_enabled() )); // validate partial lock - let lock_config = CollectionConfig::disable_settings( + let lock_config = collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, ); assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - lock_config, + lock_config.settings, )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); assert_eq!(stored_config, lock_config); // validate full lock - let full_lock_config = CollectionConfig::disable_settings( - CollectionSetting::TransferableItems | - CollectionSetting::UnlockedMetadata | - CollectionSetting::UnlockedAttributes, - ); assert_ok!(Nfts::lock_collection( RuntimeOrigin::signed(user_id), collection_id, - CollectionConfig::disable_settings(CollectionSetting::UnlockedMetadata.into()), + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()), )); let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + let full_lock_config = collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes, + ); assert_eq!(stored_config, full_lock_config); }); } @@ -1854,7 +1877,7 @@ fn collection_locking_should_work() { #[test] fn pallet_level_feature_flags_should_work() { new_test_ext().execute_with(|| { - Features::set(&PalletFeatures::disable( + Features::set(&PalletFeatures::from_disabled( PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, )); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 79520a0db9c84..d731d7b89d348 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -209,12 +209,18 @@ pub enum CollectionSetting { pub struct CollectionSettings(pub BitFlags); impl CollectionSettings { - pub fn all_settings_enabled() -> Self { + pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn values(&self) -> BitFlags { + 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); @@ -247,7 +253,7 @@ impl Default for MintSettings { } impl CollectionConfig { - pub fn all_settings_enabled() -> Self { - Self { - settings: CollectionSettings::all_settings_enabled(), - max_supply: None, - mint_settings: MintSettings::default(), - } - } - pub fn get_disabled_settings(&self) -> BitFlags { - self.settings.values() - } pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { - !self.get_disabled_settings().contains(setting) + !self.settings.is_disabled(setting) } pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { - self.get_disabled_settings().contains(setting) - } - pub fn disable_settings(settings: BitFlags) -> Self { - Self { settings: CollectionSettings(settings), ..Self::all_settings_enabled() } + self.settings.is_disabled(setting) } pub fn enable_setting(&mut self, setting: CollectionSetting) { self.settings.0.remove(setting); @@ -316,13 +309,16 @@ pub enum ItemSetting { pub struct ItemSettings(pub BitFlags); impl ItemSettings { - pub fn all_settings_enabled() -> Self { + pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn get_disabled_settings(&self) -> BitFlags { + pub fn get_disabled(&self) -> BitFlags { self.0 } - pub fn disable_settings(settings: BitFlags) -> Self { + pub fn is_disabled(&self, setting: ItemSetting) -> bool { + self.0.contains(setting) + } + pub fn from_disabled(settings: BitFlags) -> Self { Self(settings) } } @@ -338,23 +334,14 @@ pub struct ItemConfig { } impl ItemConfig { - pub fn all_settings_enabled() -> Self { - Self { ..Default::default() } - } - pub fn get_disabled_settings(&self) -> BitFlags { - self.settings.get_disabled_settings() - } pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { - !self.get_disabled_settings().contains(setting) + !self.settings.is_disabled(setting) } pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { - self.get_disabled_settings().contains(setting) + self.settings.is_disabled(setting) } pub fn has_disabled_settings(&self) -> bool { - !self.get_disabled_settings().is_empty() - } - pub fn disable_settings(settings: BitFlags) -> Self { - Self { settings: ItemSettings(settings), ..Default::default() } + !self.settings.get_disabled().is_empty() } pub fn enable_setting(&mut self, setting: ItemSetting) { self.settings.0.remove(setting); @@ -387,7 +374,7 @@ impl PalletFeatures { pub fn all_enabled() -> Self { Self(BitFlags::EMPTY) } - pub fn disable(features: BitFlags) -> Self { + pub fn from_disabled(features: BitFlags) -> Self { Self(features) } pub fn is_enabled(&self, feature: PalletFeature) -> bool { From 1ab5043dbdf3b8ed21b2f31ce3b631a80f5f4009 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 1 Nov 2022 16:55:47 +0100 Subject: [PATCH 64/94] Address comments --- frame/nfts/src/features/lock.rs | 22 +++++++++++----------- frame/nfts/src/tests.rs | 2 +- frame/nfts/src/types.rs | 3 +++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index e388eb47aba1b..54aa5a0f0c85c 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -31,17 +31,17 @@ impl, I: 'static> Pallet { CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - if lock_settings.is_disabled(CollectionSetting::TransferableItems) { - config.disable_setting(CollectionSetting::TransferableItems); - } - if lock_settings.is_disabled(CollectionSetting::UnlockedMetadata) { - config.disable_setting(CollectionSetting::UnlockedMetadata); - } - if lock_settings.is_disabled(CollectionSetting::UnlockedAttributes) { - config.disable_setting(CollectionSetting::UnlockedAttributes); - } - if lock_settings.is_disabled(CollectionSetting::UnlockedMaxSupply) { - config.disable_setting(CollectionSetting::UnlockedMaxSupply); + let allowed_to_disabled = vec![ + CollectionSetting::TransferableItems, + CollectionSetting::UnlockedMetadata, + CollectionSetting::UnlockedAttributes, + CollectionSetting::UnlockedMaxSupply, + ]; + + for setting in allowed_to_disabled { + if lock_settings.is_disabled(setting) { + config.disable_setting(setting); + } } Self::deposit_event(Event::::CollectionLocked { collection }); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 66da53c8db316..c8f7b4d859771 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -417,7 +417,7 @@ fn transfer_owner_should_work() { assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 1); - // 2's acceptence from before is reset when it became owner, so it cannot be transfered + // 2's acceptance from before is reset when it became an owner, so it cannot be transferred // without a fresh acceptance. assert_noop!( Nfts::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index d731d7b89d348..3ef7ac811e5e8 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -227,8 +227,11 @@ impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum MintType { + /// Only an `Issuer` could mint items. Private, + /// Anyone could mint items. Public, + /// Only holders of items in specified collection could mint new items. HolderOf(CollectionId), } From 387bf2c4d0a40c21f6d61eafac8b6328f91c9df1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 1 Nov 2022 17:07:06 +0100 Subject: [PATCH 65/94] Refactor lock_collection() --- frame/nfts/src/features/lock.rs | 17 ++++++----------- frame/nfts/src/tests.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index 54aa5a0f0c85c..e96a30dfd2c7c 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -28,20 +28,15 @@ impl, I: 'static> Pallet { Self::has_role(&collection, &origin, CollectionRole::Freezer), Error::::NoPermission ); + ensure!( + !lock_settings.is_disabled(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); CollectionConfigOf::::try_mutate(collection, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - let allowed_to_disabled = vec![ - CollectionSetting::TransferableItems, - CollectionSetting::UnlockedMetadata, - CollectionSetting::UnlockedAttributes, - CollectionSetting::UnlockedMaxSupply, - ]; - - for setting in allowed_to_disabled { - if lock_settings.is_disabled(setting) { - config.disable_setting(setting); - } + for setting in lock_settings.get_disabled() { + config.disable_setting(setting); } Self::deposit_event(Event::::CollectionLocked { collection }); diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index c8f7b4d859771..949f8b7560e35 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1844,6 +1844,17 @@ fn collection_locking_should_work() { collection_config_with_all_settings_enabled() )); + let lock_config = + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()); + assert_noop!( + Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config.settings, + ), + Error::::WrongSetting + ); + // validate partial lock let lock_config = collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, From 1c51efd925d04e34eaaada7717a4251cb802f76d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:51:19 +0200 Subject: [PATCH 66/94] Update frame/nfts/src/types.rs Co-authored-by: Squirrel --- frame/nfts/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 3ef7ac811e5e8..da9c286222541 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -237,7 +237,7 @@ pub enum MintType { #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct MintSettings { - /// Mint type. + /// Whether anyone can mint or if minters are restricted to some subset. pub(super) mint_type: MintType, /// An optional price per mint. pub(super) price: Option, From 9e9b96912150edf0d07be30baff8fb10bc64fa76 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:52:17 +0200 Subject: [PATCH 67/94] Update frame/nfts/src/types.rs Co-authored-by: Squirrel --- frame/nfts/src/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index da9c286222541..8df701f7f7b61 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -225,6 +225,9 @@ impl CollectionSettings { impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); +/// Mint type. Can the NFT be create by anyone, or only the creator of the collection, +/// or only by wallets that already hold an NFT from a certain collection? +/// The ownership of a privately minted NFT is still publicly visible. #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum MintType { /// Only an `Issuer` could mint items. From b82fbe9e908aff7fff1015f922c1eabf420d2f0c Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:54:24 +0200 Subject: [PATCH 68/94] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 618614d103f91..44af2a522cf03 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1493,7 +1493,7 @@ pub mod pallet { }, Some(item) => { // NOTE: if the item was previously burned, the ItemConfigOf record might - // not exists. In that case, we allow to clear the attribute. + // not exist. In that case, we allow to clear the attribute. let maybe_is_locked = Self::get_item_config(&collection, &item) .map_or(false, |c| { c.has_disabled_setting(ItemSetting::UnlockedAttributes) From e495da6f573ce66bcf7dd5040f54a0012790c7a6 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:54:43 +0200 Subject: [PATCH 69/94] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 44af2a522cf03..7236a0497b97f 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1614,7 +1614,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - // NOTE: if the item was previously burned, the ItemConfigOf record might not exists + // NOTE: if the item was previously burned, the ItemConfigOf record might not exist let is_locked = Self::get_item_config(&collection, &item) .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); From 670bb28c97a2ee4a45a8baa14403c67ae066a6c3 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 8 Nov 2022 12:28:29 +0200 Subject: [PATCH 70/94] Private => Issuer --- frame/nfts/src/lib.rs | 2 +- frame/nfts/src/types.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 618614d103f91..44dc8c46f2877 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -734,7 +734,7 @@ pub mod pallet { } match mint_settings.mint_type { - MintType::Private => { + MintType::Issuer => { ensure!( Self::has_role(&collection, &caller, CollectionRole::Issuer), Error::::NoPermission diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 8df701f7f7b61..d57f62be97f39 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -231,7 +231,7 @@ impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum MintType { /// Only an `Issuer` could mint items. - Private, + Issuer, /// Anyone could mint items. Public, /// Only holders of items in specified collection could mint new items. @@ -255,7 +255,7 @@ pub struct MintSettings { impl Default for MintSettings { fn default() -> Self { Self { - mint_type: MintType::Private, + mint_type: MintType::Issuer, price: None, start_block: None, end_block: None, From 5093b89aa44b9776db55757d9f399d501177d6cb Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 8 Nov 2022 13:13:52 +0200 Subject: [PATCH 71/94] Add more tests --- frame/nfts/src/tests.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 949f8b7560e35..36bec726f4ee3 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -149,6 +149,7 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); assert_ok!(Nfts::create( RuntimeOrigin::signed(1), 1, @@ -164,10 +165,15 @@ fn lifecycle_should_work() { assert_eq!(Balances::reserved_balance(&1), 6); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); assert_eq!(Balances::reserved_balance(&1), 7); - assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); - assert_eq!(Collection::::get(0).unwrap().items, 2); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, None)); + assert_eq!(items(), vec![(1, 0, 70), (10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 70, 2)); + assert_eq!(Balances::reserved_balance(&2), 1); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); assert_eq!(Balances::reserved_balance(&1), 10); assert!(ItemMetadataOf::::contains_key(0, 42)); @@ -176,7 +182,7 @@ fn lifecycle_should_work() { assert!(ItemMetadataOf::::contains_key(0, 69)); let w = Nfts::get_destroy_witness(&0).unwrap(); - assert_eq!(w.items, 2); + assert_eq!(w.items, 3); assert_eq!(w.item_metadatas, 2); assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); From c376b439cfa5fd324b8abfe2a81d29bc60b08cb2 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 8 Nov 2022 13:25:41 +0200 Subject: [PATCH 72/94] Fix benchmarks --- frame/nfts/src/benchmarking.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 45371ae7fb610..0174df8ad9400 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -20,6 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; +use enumflags2::{BitFlag, BitFlags}; use frame_benchmarking::{ account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; @@ -38,10 +39,7 @@ use crate::Pallet as Nfts; const SEED: u32 = 0; fn create_collection, I: 'static>( -) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) -where - >::CollectionId: sp_std::default::Default, -{ +) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); @@ -130,14 +128,20 @@ fn assert_last_event, I: 'static>(generic_event: >:: assert_eq!(event, &system_event); } -fn default_collection_config, I: 'static>() -> CollectionConfigFor { +fn make_collection_config, I: 'static>( + disable_settings: BitFlags, +) -> CollectionConfigFor { CollectionConfig { - settings: CollectionSettings::all_enabled(), + settings: CollectionSettings::from_disabled(disable_settings), max_supply: None, mint_settings: MintSettings::default(), } } +fn default_collection_config, I: 'static>() -> CollectionConfigFor { + make_collection_config::(CollectionSetting::empty()) +} + benchmarks_instance_pallet! { create { let collection = T::Helper::collection(0); @@ -198,6 +202,7 @@ benchmarks_instance_pallet! { let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) verify { assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); @@ -214,7 +219,7 @@ benchmarks_instance_pallet! { caller_lookup.clone(), caller_lookup.clone(), caller_lookup, - CollectionConfig { settings: CollectionSetting::DepositRequired.into(), ..Default::default() }, + make_collection_config::(CollectionSetting::DepositRequired.into()), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { @@ -290,7 +295,7 @@ benchmarks_instance_pallet! { issuer: caller_lookup.clone(), admin: caller_lookup.clone(), freezer: caller_lookup, - config: CollectionConfig { settings: CollectionSetting::DepositRequired.into(), ..Default::default() }, + config: make_collection_config::(CollectionSetting::DepositRequired.into()), }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -538,6 +543,7 @@ benchmarks_instance_pallet! { let duration = T::MaxDeadlineDuration::get(); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(caller.clone()); frame_system::Pallet::::set_block_number(One::one()); Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; From 7fe301a4e4431d206b4553956f80cec50dfd1e5e Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 8 Nov 2022 13:52:51 +0200 Subject: [PATCH 73/94] Add benchmarks for new methods --- frame/nfts/src/benchmarking.rs | 29 +++++++++++++++++++++++++- frame/nfts/src/lib.rs | 4 ++-- frame/nfts/src/weights.rs | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 0174df8ad9400..5dcbe899e58db 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -142,6 +142,10 @@ fn default_collection_config, I: 'static>() -> CollectionConfigFor< make_collection_config::(CollectionSetting::empty()) } +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::all_enabled() } +} + benchmarks_instance_pallet! { create { let collection = T::Helper::collection(0); @@ -188,6 +192,14 @@ benchmarks_instance_pallet! { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } + force_mint { + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config()) + verify { + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + } + burn { let (collection, caller, caller_lookup) = create_collection::(); let (item, ..) = mint_item::(0); @@ -252,7 +264,8 @@ benchmarks_instance_pallet! { let lock_settings = CollectionSettings::from_disabled( CollectionSetting::TransferableItems | CollectionSetting::UnlockedMetadata | - CollectionSetting::UnlockedAttributes, + CollectionSetting::UnlockedAttributes | + CollectionSetting::UnlockedMaxSupply, ); }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) verify { @@ -429,6 +442,20 @@ benchmarks_instance_pallet! { }.into()); } + update_mint_settings { + let (collection, caller, _) = create_collection::(); + let mint_settings = MintSettings { + mint_type: MintType::HolderOf(T::Helper::collection(0)), + start_block: Some(One::one()), + end_block: Some(One::one()), + price: Some(ItemPrice::::from(1u32)), + default_item_settings: ItemSettings::all_enabled(), + }; + }: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings) + verify { + assert_last_event::(Event::CollectionMintSettingsUpdated { collection }.into()); + } + set_price { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 44dc8c46f2877..57b2ae75b8b83 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -782,7 +782,7 @@ pub mod pallet { /// Emits `Issued` event when successful. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::mint())] + #[pallet::weight(T::WeightInfo::force_mint())] pub fn force_mint( origin: OriginFor, collection: T::CollectionId, @@ -1826,7 +1826,7 @@ pub mod pallet { /// - `mint_settings`: The new mint settings. /// /// Emits `CollectionMintSettingsUpdated` event when successful. - #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + #[pallet::weight(T::WeightInfo::update_mint_settings())] pub fn update_mint_settings( origin: OriginFor, collection: T::CollectionId, diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 5f6ee43a09ffe..52a848bfff7d4 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -50,6 +50,7 @@ pub trait WeightInfo { fn force_create() -> Weight; fn destroy(n: u32, m: u32, a: u32, ) -> Weight; fn mint() -> Weight; + fn force_mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; fn redeposit(i: u32, ) -> Weight; @@ -71,6 +72,7 @@ pub trait WeightInfo { fn clear_all_transfer_approvals() -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; + fn update_mint_settings() -> Weight; fn set_price() -> Weight; fn buy_item() -> Weight; fn pay_tips(n: u32, ) -> Weight; @@ -137,6 +139,17 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemConfigOf (r:0 w:1) @@ -309,6 +322,13 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + fn update_mint_settings() -> Weight { + Weight::from_ref_time(26_358_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) @@ -419,6 +439,17 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } + // Storage: Nfts Asset (r:1 w:1) + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionMaxSupply (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:0 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + Weight::from_ref_time(47_947_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } // Storage: Nfts Class (r:1 w:1) // Storage: Nfts Asset (r:1 w:1) // Storage: Nfts ItemConfigOf (r:0 w:1) @@ -591,6 +622,13 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: Nfts CollectionMaxSupply (r:1 w:1) + // Storage: Nfts Class (r:1 w:0) + fn update_mint_settings() -> Weight { + Weight::from_ref_time(26_358_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } // Storage: Nfts Asset (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) From d667fb7b6e7d2b45680cea3d20a35fee9a84f44b Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Mon, 14 Nov 2022 09:36:19 +0200 Subject: [PATCH 74/94] [Uniques v2] Refactoring (#12570) * Move do_set_price() and do_buy_item() to buy_sell.rs * Move approvals to feature file * Move metadata to feature files * Move the rest of methods to feature files * Remove artifacts --- frame/nfts/src/common_functions.rs | 42 ++ frame/nfts/src/features/approvals.rs | 132 +++++ frame/nfts/src/features/attributes.rs | 123 +++++ frame/nfts/src/features/buy_sell.rs | 90 +++- .../src/features/create_delete_collection.rs | 109 ++++ frame/nfts/src/features/create_delete_item.rs | 123 +++++ frame/nfts/src/features/metadata.rs | 173 +++++++ frame/nfts/src/features/mod.rs | 6 + frame/nfts/src/features/roles.rs | 28 + frame/nfts/src/features/settings.rs | 89 +++- frame/nfts/src/features/transfer.rs | 138 +++++ frame/nfts/src/functions.rs | 377 -------------- frame/nfts/src/lib.rs | 481 +----------------- 13 files changed, 1074 insertions(+), 837 deletions(-) create mode 100644 frame/nfts/src/common_functions.rs create mode 100644 frame/nfts/src/features/approvals.rs create mode 100644 frame/nfts/src/features/attributes.rs create mode 100644 frame/nfts/src/features/create_delete_collection.rs create mode 100644 frame/nfts/src/features/create_delete_item.rs create mode 100644 frame/nfts/src/features/metadata.rs create mode 100644 frame/nfts/src/features/transfer.rs delete mode 100644 frame/nfts/src/functions.rs diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs new file mode 100644 index 0000000000000..b3cac7f69ec0e --- /dev/null +++ b/frame/nfts/src/common_functions.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Various pieces of common functionality. + +use super::*; + +impl, I: 'static> Pallet { + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) + } +} diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs new file mode 100644 index 0000000000000..0cbceb9113d0c --- /dev/null +++ b/frame/nfts/src/features/approvals.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_approve_transfer( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + maybe_deadline: Option<::BlockNumber>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + details + .approvals + .try_insert(delegate.clone(), deadline) + .map_err(|_| Error::::ReachedApprovalLimit)?; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovedTransfer { + collection, + item, + owner: details.owner, + delegate, + deadline, + }); + + Ok(()) + } + + pub(crate) fn do_cancel_approval( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; + + let is_past_deadline = if let Some(deadline) = maybe_deadline { + let now = frame_system::Pallet::::block_number(); + now > *deadline + } else { + false + }; + + if !is_past_deadline { + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + pub(crate) fn do_clear_all_transfer_approvals( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + details.approvals.clear(); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::AllApprovalsCancelled { + collection, + item, + owner: details.owner, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs new file mode 100644 index 0000000000000..85c1e0b302d12 --- /dev/null +++ b/frame/nfts/src/features/attributes.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + + let attribute = Attribute::::get((collection, maybe_item, &key)); + if attribute.is_none() { + collection_details.attributes.saturating_inc(); + } + let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + collection_details.total_deposit.saturating_accrue(deposit); + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + if maybe_check_owner.is_some() { + match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemConfigOf record might + // not exist. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedAttributes)); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + } + + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + } + Ok(()) + } +} diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs index c1e29057af9c9..8ba5171f8d822 100644 --- a/frame/nfts/src/features/buy_sell.rs +++ b/frame/nfts/src/features/buy_sell.rs @@ -18,7 +18,7 @@ use crate::*; use frame_support::{ pallet_prelude::*, - traits::{Currency, ExistenceRequirement::KeepAlive}, + traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, }; impl, I: 'static> Pallet { @@ -39,4 +39,92 @@ impl, I: 'static> Pallet { } Ok(()) } + + pub(crate) fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub(crate) fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::NoPermission); + } + + T::Currency::transfer( + &buyer, + &details.owner, + price_info.0, + ExistenceRequirement::KeepAlive, + )?; + + let old_owner = details.owner.clone(); + + Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + + Self::deposit_event(Event::ItemBought { + collection, + item, + price: price_info.0, + seller: old_owner, + buyer, + }); + + Ok(()) + } } diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs new file mode 100644 index 0000000000000..b9530e88b18cd --- /dev/null +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -0,0 +1,109 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + config: CollectionConfigFor, + deposit: DepositBalanceOf, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + total_deposit: deposit, + items: 0, + item_metadatas: 0, + attributes: 0, + }, + ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); + + let next_id = collection.increment(); + + CollectionConfigOf::::insert(&collection, config); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(Some(next_id)); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + Self::deposit_event(event); + Ok(()) + } + + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + PendingSwapOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; + #[allow(deprecated)] + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } +} diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs new file mode 100644 index 0000000000000..10670f4b10c1c --- /dev/null +++ b/frame/nfts/src/features/create_delete_item.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + item_config: ItemConfig, + deposit_collection_owner: bool, + with_details_and_config: impl FnOnce( + &CollectionDetailsFor, + &CollectionConfigFor, + ) -> DispatchResult, + ) -> DispatchResult { + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + + let collection_config = Self::get_collection_config(&collection)?; + with_details_and_config(collection_details, &collection_config)?; + + if let Some(max_supply) = collection_config.max_supply { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; + + let collection_config = Self::get_collection_config(&collection)?; + let deposit_amount = match collection_config + .is_setting_enabled(CollectionSetting::DepositRequired) + { + true => T::ItemDeposit::get(), + false => Zero::zero(), + }; + let deposit_account = match deposit_collection_owner { + true => collection_details.owner.clone(), + false => owner.clone(), + }; + + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, item_config); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = + ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner }); + Ok(()) + } + + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(&details)?; + + // Return the deposit. + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + collection_details.items.saturating_dec(); + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_settings() { + ItemConfigOf::::remove(&collection, &item); + } + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } +} diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs new file mode 100644 index 0000000000000..0b0a337197d9b --- /dev/null +++ b/frame/nfts/src/features/metadata.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + maybe_check_owner.is_none() || + item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_none() { + collection_details.item_metadatas.saturating_inc(); + } + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + collection_details.total_deposit.saturating_accrue(deposit); + + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + // NOTE: if the item was previously burned, the ItemConfigOf record might not exist + let is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); + + ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_some() { + collection_details.item_metadatas.saturating_dec(); + } + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if maybe_check_owner.is_some() && + collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&details.owner, old_deposit - deposit); + } + details.total_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&details.owner, deposit); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index f814d696d774b..b77ee9bf2491b 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -15,8 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod approvals; pub mod atomic_swap; +pub mod attributes; pub mod buy_sell; +pub mod create_delete_collection; +pub mod create_delete_item; pub mod lock; +pub mod metadata; pub mod roles; pub mod settings; +pub mod transfer; diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs index e961779725b6e..7f6f42363fe33 100644 --- a/frame/nfts/src/features/roles.rs +++ b/frame/nfts/src/features/roles.rs @@ -20,6 +20,34 @@ use frame_support::pallet_prelude::*; use sp_std::collections::btree_map::BTreeMap; impl, I: 'static> Pallet { + pub(crate) fn do_set_team( + origin: T::AccountId, + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); + Ok(()) + }) + } + /// Clears all the roles in a specified collection. /// /// - `collection_id`: A collection to clear the roles in. diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 3d80a38ab99eb..59444f728140c 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -18,11 +18,92 @@ use crate::*; use frame_support::pallet_prelude::*; -/// The helper methods bellow allow to read and validate different -/// collection/item/pallet settings. -/// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular -/// collection, or for a specific item. impl, I: 'static> Pallet { + pub(crate) fn do_force_collection_status( + collection: T::CollectionId, + new_owner: T::AccountId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + config: CollectionConfigFor, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_collection| { + let mut collection_info = + maybe_collection.take().ok_or(Error::::UnknownCollection)?; + let old_owner = collection_info.owner; + collection_info.owner = new_owner.clone(); + *maybe_collection = Some(collection_info); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + CollectionConfigOf::::insert(&collection, config); + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer, CollectionRole::Issuer), + (admin, CollectionRole::Admin), + (freezer, CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::CollectionStatusChanged { collection }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_max_supply( + maybe_check_owner: Option, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), + Error::::MaxSupplyLocked + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) + } + + pub(crate) fn do_update_mint_settings( + maybe_check_owner: Option, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.mint_settings = mint_settings; + Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); + Ok(()) + }) + } + pub(crate) fn get_collection_config( collection_id: &T::CollectionId, ) -> Result, DispatchError> { diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs new file mode 100644 index 0000000000000..f2040566d5f7a --- /dev/null +++ b/frame/nfts/src/features/transfer.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + with_details(&collection_details, &mut details)?; + + if details.deposit.account == details.owner { + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &dest, + details.deposit.amount, + Reserved, + )?; + } + + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); + let origin = details.owner; + details.owner = dest; + + // The approved accounts have to be reset to None, because otherwise pre-approve attack + // would be possible, where the owner can approve his second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + pub(crate) fn do_transfer_ownership( + origin: T::AccountId, + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } + + pub(crate) fn do_set_accept_ownership( + who: T::AccountId, + maybe_collection: Option, + ) -> DispatchResult { + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } +} diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs deleted file mode 100644 index d556bdd4b628f..0000000000000 --- a/frame/nfts/src/functions.rs +++ /dev/null @@ -1,377 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Various pieces of common functionality. - -use super::*; -use frame_support::{ - ensure, - traits::{ExistenceRequirement, Get}, -}; -use sp_runtime::{DispatchError, DispatchResult}; - -impl, I: 'static> Pallet { - pub fn do_transfer( - collection: T::CollectionId, - item: T::ItemId, - dest: T::AccountId, - with_details: impl FnOnce( - &CollectionDetailsFor, - &mut ItemDetailsFor, - ) -> DispatchResult, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - with_details(&collection_details, &mut details)?; - - if details.deposit.account == details.owner { - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &dest, - details.deposit.amount, - Reserved, - )?; - } - - Account::::remove((&details.owner, &collection, &item)); - Account::::insert((&dest, &collection, &item), ()); - let origin = details.owner; - details.owner = dest; - - // The approved accounts have to be reset to None, because otherwise pre-approve attack - // would be possible, where the owner can approve his second account before making the - // transaction and then claiming the item back. - details.approvals.clear(); - - Item::::insert(&collection, &item, &details); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - Self::deposit_event(Event::Transferred { - collection, - item, - from: origin, - to: details.owner, - }); - Ok(()) - } - - pub fn do_create_collection( - collection: T::CollectionId, - owner: T::AccountId, - admin: T::AccountId, - config: CollectionConfigFor, - deposit: DepositBalanceOf, - event: Event, - ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); - - T::Currency::reserve(&owner, deposit)?; - - Collection::::insert( - collection, - CollectionDetails { - owner: owner.clone(), - total_deposit: deposit, - items: 0, - item_metadatas: 0, - attributes: 0, - }, - ); - CollectionRoleOf::::insert( - collection, - admin, - CollectionRoles( - CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, - ), - ); - - let next_id = collection.increment(); - - CollectionConfigOf::::insert(&collection, config); - CollectionAccount::::insert(&owner, &collection, ()); - NextCollectionId::::set(Some(next_id)); - - Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); - Self::deposit_event(event); - Ok(()) - } - - pub fn do_destroy_collection( - collection: T::CollectionId, - witness: DestroyWitness, - maybe_check_owner: Option, - ) -> Result { - Collection::::try_mutate_exists(collection, |maybe_details| { - let collection_details = - maybe_details.take().ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(collection_details.owner == check_owner, Error::::NoPermission); - } - ensure!(collection_details.items == witness.items, Error::::BadWitness); - ensure!( - collection_details.item_metadatas == witness.item_metadatas, - Error::::BadWitness - ); - ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); - - for (item, details) in Item::::drain_prefix(&collection) { - Account::::remove((&details.owner, &collection, &item)); - T::Currency::unreserve(&details.deposit.account, details.deposit.amount); - } - #[allow(deprecated)] - ItemMetadataOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - ItemPriceOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - PendingSwapOf::::remove_prefix(&collection, None); - CollectionMetadataOf::::remove(&collection); - Self::clear_roles(&collection)?; - #[allow(deprecated)] - Attribute::::remove_prefix((&collection,), None); - CollectionAccount::::remove(&collection_details.owner, &collection); - T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); - CollectionConfigOf::::remove(&collection); - let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); - - Self::deposit_event(Event::Destroyed { collection }); - - Ok(DestroyWitness { - items: collection_details.items, - item_metadatas: collection_details.item_metadatas, - attributes: collection_details.attributes, - }) - }) - } - - pub fn do_mint( - collection: T::CollectionId, - item: T::ItemId, - owner: T::AccountId, - item_config: ItemConfig, - deposit_collection_owner: bool, - with_details_and_config: impl FnOnce( - &CollectionDetailsFor, - &CollectionConfigFor, - ) -> DispatchResult, - ) -> DispatchResult { - ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); - - Collection::::try_mutate( - &collection, - |maybe_collection_details| -> DispatchResult { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - - let collection_config = Self::get_collection_config(&collection)?; - with_details_and_config(collection_details, &collection_config)?; - - if let Some(max_supply) = collection_config.max_supply { - ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); - } - - let items = - collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; - collection_details.items = items; - - let collection_config = Self::get_collection_config(&collection)?; - let deposit_amount = match collection_config - .is_setting_enabled(CollectionSetting::DepositRequired) - { - true => T::ItemDeposit::get(), - false => Zero::zero(), - }; - let deposit_account = match deposit_collection_owner { - true => collection_details.owner.clone(), - false => owner.clone(), - }; - - let owner = owner.clone(); - Account::::insert((&owner, &collection, &item), ()); - - if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { - ensure!(existing_config == item_config, Error::::InconsistentItemConfig); - } else { - ItemConfigOf::::insert(&collection, &item, item_config); - } - - T::Currency::reserve(&deposit_account, deposit_amount)?; - - let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; - let details = - ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; - Item::::insert(&collection, &item, details); - Ok(()) - }, - )?; - - Self::deposit_event(Event::Issued { collection, item, owner }); - Ok(()) - } - - pub fn do_burn( - collection: T::CollectionId, - item: T::ItemId, - with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, - ) -> DispatchResult { - let owner = Collection::::try_mutate( - &collection, - |maybe_collection_details| -> Result { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - let details = Item::::get(&collection, &item) - .ok_or(Error::::UnknownCollection)?; - with_details(&details)?; - - // Return the deposit. - T::Currency::unreserve(&details.deposit.account, details.deposit.amount); - collection_details.items.saturating_dec(); - Ok(details.owner) - }, - )?; - - Item::::remove(&collection, &item); - Account::::remove((&owner, &collection, &item)); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - // NOTE: if item's settings are not empty (e.g. item's metadata is locked) - // then we keep the record and don't remove it - let config = Self::get_item_config(&collection, &item)?; - if !config.has_disabled_settings() { - ItemConfigOf::::remove(&collection, &item); - } - - Self::deposit_event(Event::Burned { collection, item, owner }); - Ok(()) - } - - pub fn do_set_price( - collection: T::CollectionId, - item: T::ItemId, - sender: T::AccountId, - price: Option>, - whitelisted_buyer: Option, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner == sender, Error::::NoPermission); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - if let Some(ref price) = price { - ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); - Self::deposit_event(Event::ItemPriceSet { - collection, - item, - price: *price, - whitelisted_buyer, - }); - } else { - ItemPriceOf::::remove(&collection, &item); - Self::deposit_event(Event::ItemPriceRemoved { collection, item }); - } - - Ok(()) - } - - pub fn do_buy_item( - collection: T::CollectionId, - item: T::ItemId, - buyer: T::AccountId, - bid_price: ItemPrice, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner != buyer, Error::::NoPermission); - - let price_info = - ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; - - ensure!(bid_price >= price_info.0, Error::::BidTooLow); - - if let Some(only_buyer) = price_info.1 { - ensure!(only_buyer == buyer, Error::::NoPermission); - } - - T::Currency::transfer( - &buyer, - &details.owner, - price_info.0, - ExistenceRequirement::KeepAlive, - )?; - - let old_owner = details.owner.clone(); - - Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; - - Self::deposit_event(Event::ItemBought { - collection, - item, - price: price_info.0, - seller: old_owner, - buyer, - }); - - Ok(()) - } - - #[cfg(any(test, feature = "runtime-benchmarks"))] - pub fn set_next_id(id: T::CollectionId) { - NextCollectionId::::set(Some(id)); - } - - #[cfg(test)] - pub fn get_next_id() -> T::CollectionId { - NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) - } -} diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 7496ff1cf11a8..c5138d927bb39 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -35,8 +35,8 @@ pub mod mock; #[cfg(test)] mod tests; +mod common_functions; mod features; -mod functions; mod impl_nonfungibles; mod types; @@ -557,18 +557,6 @@ pub mod pallet { MintEnded, } - impl, I: 'static> Pallet { - /// Get the owner of the item, if the item exists. - pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { - Item::::get(collection, item).map(|i| i.owner) - } - - /// Get the owner of the item, if the item exists. - pub fn collection_owner(collection: T::CollectionId) -> Option { - Collection::::get(collection).map(|i| i.owner) - } - } - #[pallet::call] impl, I: 'static> Pallet { /// Issue a new collection of non-fungible items from a public origin. @@ -1025,32 +1013,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - - let acceptable_collection = OwnershipAcceptance::::get(&owner); - ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - if details.owner == owner { - return Ok(()) - } - - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &owner, - details.total_deposit, - Reserved, - )?; - CollectionAccount::::remove(&details.owner, &collection); - CollectionAccount::::insert(&owner, &collection, ()); - details.owner = owner.clone(); - OwnershipAcceptance::::remove(&owner); - - Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); - Ok(()) - }) + Self::do_transfer_ownership(origin, collection, owner) } /// Change the Issuer, Admin and Freezer of a collection. @@ -1077,26 +1040,7 @@ pub mod pallet { let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer.clone(), CollectionRole::Issuer), - (admin.clone(), CollectionRole::Admin), - (freezer.clone(), CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); - Ok(()) - }) + Self::do_set_team(origin, collection, issuer, admin, freezer) } /// Approve an item to be transferred by a delegated third-party account. @@ -1120,55 +1064,17 @@ pub mod pallet { delegate: AccountIdLookupOf, maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Approvals), - Error::::MethodDisabled - ); - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - let now = frame_system::Pallet::::block_number(); - let deadline = maybe_deadline.map(|d| d.saturating_add(now)); - - details - .approvals - .try_insert(delegate.clone(), deadline) - .map_err(|_| Error::::ReachedApprovalLimit)?; - Item::::insert(&collection, &item, &details); - - Self::deposit_event(Event::ApprovedTransfer { + Self::do_approve_transfer( + maybe_check_origin, collection, item, - owner: details.owner, delegate, - deadline, - }); - - Ok(()) + maybe_deadline, + ) } /// Cancel one of the transfer approvals for a specific item. @@ -1193,43 +1099,11 @@ pub mod pallet { item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let maybe_deadline = - details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; - - let is_past_deadline = if let Some(deadline) = maybe_deadline { - let now = frame_system::Pallet::::block_number(); - now > *deadline - } else { - false - }; - - if !is_past_deadline { - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - } - - details.approvals.remove(&delegate); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::ApprovalCancelled { - collection, - item, - owner: details.owner, - delegate, - }); - - Ok(()) + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) } /// Cancel all the approvals of a specific item. @@ -1252,28 +1126,10 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - details.approvals.clear(); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::AllApprovalsCancelled { - collection, - item, - owner: details.owner, - }); - - Ok(()) + Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } /// Alter the attributes of a given collection. @@ -1301,37 +1157,11 @@ pub mod pallet { config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - - Collection::::try_mutate(collection, |maybe_collection| { - let mut collection_info = - maybe_collection.take().ok_or(Error::::UnknownCollection)?; - let old_owner = collection_info.owner; - let new_owner = T::Lookup::lookup(owner)?; - collection_info.owner = new_owner.clone(); - *maybe_collection = Some(collection_info); - CollectionAccount::::remove(&old_owner, &collection); - CollectionAccount::::insert(&new_owner, &collection, ()); - CollectionConfigOf::::insert(&collection, config); - - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer, CollectionRole::Issuer), - (admin, CollectionRole::Admin), - (freezer, CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::CollectionStatusChanged { collection }); - Ok(()) - }) + let new_owner = T::Lookup::lookup(owner)?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + Self::do_force_collection_status(collection, new_owner, issuer, admin, freezer, config) } /// Disallows changing the metadata of attributes of the item. @@ -1393,61 +1223,10 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Attributes), - Error::::MethodDisabled - ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - match maybe_item { - None => { - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - - let attribute = Attribute::::get((collection, maybe_item, &key)); - if attribute.is_none() { - collection_details.attributes.saturating_inc(); - } - let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((key.len() + value.len()) as u32).into()) - .saturating_add(T::AttributeDepositBase::get()); - } - collection_details.total_deposit.saturating_accrue(deposit); - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - - Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); - Ok(()) + Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) } /// Clear an attribute for a collection or item. @@ -1474,43 +1253,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - if maybe_check_owner.is_some() { - match maybe_item { - None => { - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config - .is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - // NOTE: if the item was previously burned, the ItemConfigOf record might - // not exist. In that case, we allow to clear the attribute. - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| { - c.has_disabled_setting(ItemSetting::UnlockedAttributes) - }); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - } - - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { - collection_details.attributes.saturating_dec(); - collection_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&collection_details.owner, deposit); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); - } - Ok(()) + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) } /// Set the metadata for an item. @@ -1539,50 +1282,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - maybe_check_owner.is_none() || - item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), - Error::::LockedItemMetadata - ); - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_none() { - collection_details.item_metadatas.saturating_inc(); - } - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((data.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - } - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - collection_details.total_deposit.saturating_accrue(deposit); - - *metadata = Some(ItemMetadata { deposit, data: data.clone() }); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data }); - Ok(()) - }) + Self::do_set_item_metadata(maybe_check_owner, collection, item, data) } /// Clear the metadata for an item. @@ -1607,31 +1307,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - // NOTE: if the item was previously burned, the ItemConfigOf record might not exist - let is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); - - ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_some() { - collection_details.item_metadatas.saturating_dec(); - } - let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; - T::Currency::unreserve(&collection_details.owner, deposit); - collection_details.total_deposit.saturating_reduce(deposit); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataCleared { collection, item }); - Ok(()) - }) + Self::do_clear_item_metadata(maybe_check_owner, collection, item) } /// Set the metadata for a collection. @@ -1658,45 +1334,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - let mut details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && - collection_config.is_setting_enabled(CollectionSetting::DepositRequired) - { - deposit = T::DepositPerByte::get() - .saturating_mul(((data.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - } - if deposit > old_deposit { - T::Currency::reserve(&details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&details.owner, old_deposit - deposit); - } - details.total_deposit.saturating_accrue(deposit); - - Collection::::insert(&collection, details); - - *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); - - Self::deposit_event(Event::CollectionMetadataSet { collection, data }); - Ok(()) - }) + Self::do_set_collection_metadata(maybe_check_owner, collection, data) } /// Clear the metadata for a collection. @@ -1719,26 +1357,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; - T::Currency::unreserve(&details.owner, deposit); - Self::deposit_event(Event::CollectionMetadataCleared { collection }); - Ok(()) - }) + Self::do_clear_collection_metadata(maybe_check_owner, collection) } /// Set (or reset) the acceptance of ownership for a particular account. @@ -1757,23 +1376,7 @@ pub mod pallet { maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_collection.is_some()) { - (false, true) => { - frame_system::Pallet::::inc_consumers(&who)?; - }, - (true, false) => { - frame_system::Pallet::::dec_consumers(&who); - }, - _ => {}, - } - if let Some(collection) = maybe_collection.as_ref() { - OwnershipAcceptance::::insert(&who, collection); - } else { - OwnershipAcceptance::::remove(&who); - } - Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); - Ok(()) + Self::do_set_accept_ownership(who, maybe_collection) } /// Set the maximum amount of items a collection could have. @@ -1794,27 +1397,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), - Error::::MaxSupplyLocked - ); - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); - - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - config.max_supply = Some(max_supply); - Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); - Ok(()) - }) + Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) } /// Update mint settings. @@ -1839,19 +1422,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - config.mint_settings = mint_settings; - Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); - Ok(()) - }) + Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) } /// Set (or reset) the price for an item. From 5102020bb5d2d84f3b95fe95f4d7acaedbaa88ab Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Mon, 14 Nov 2022 17:17:55 +0200 Subject: [PATCH 75/94] Smart attributes --- bin/node/runtime/src/lib.rs | 2 + frame/nfts/src/features/attributes.rs | 292 ++++++++++++++---- .../src/features/create_delete_collection.rs | 17 +- frame/nfts/src/features/create_delete_item.rs | 1 + frame/nfts/src/features/metadata.rs | 10 +- frame/nfts/src/features/transfer.rs | 3 +- frame/nfts/src/impl_nonfungibles.rs | 11 +- frame/nfts/src/lib.rs | 125 +++++++- frame/nfts/src/mock.rs | 1 + frame/nfts/src/tests.rs | 176 ++++++++++- frame/nfts/src/types.rs | 33 +- frame/support/src/traits/tokens.rs | 4 +- frame/support/src/traits/tokens/misc.rs | 15 + .../src/traits/tokens/nonfungible_v2.rs | 35 ++- .../src/traits/tokens/nonfungibles_v2.rs | 9 +- 15 files changed, 624 insertions(+), 110 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 01442946cf5f9..d73b0d5b02e57 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1490,6 +1490,7 @@ parameter_types! { pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; pub const ApprovalsLimit: u32 = 20; + pub const ItemAttributesApprovalsLimit: u32 = 20; pub const MaxTips: u32 = 10; pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } @@ -1534,6 +1535,7 @@ impl pallet_nfts::Config for Runtime { type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; type ApprovalsLimit = ApprovalsLimit; + type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; type MaxTips = MaxTips; type MaxDeadlineDuration = MaxDeadlineDuration; type Features = Features; diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index 85c1e0b302d12..ae3d74cfdb863 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -20,11 +20,12 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { pub(crate) fn do_set_attribute( - maybe_check_owner: Option, + origin: T::AccountId, collection: T::CollectionId, maybe_item: Option, key: BoundedVec, value: BoundedVec, + namespace: AttributeNamespace, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Attributes), @@ -34,90 +35,273 @@ impl, I: 'static> Pallet { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } + ensure!( + Self::is_valid_namespace( + &origin, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::NoPermission + ); let collection_config = Self::get_collection_config(&collection)?; - match maybe_item { - None => { - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + // for the `CollectionOwner` namespace we need to check if the collection/item is not locked + match namespace { + AttributeNamespace::CollectionOwner => match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, }, - }; + _ => (), + } - let attribute = Attribute::::get((collection, maybe_item, &key)); + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); if attribute.is_none() { collection_details.attributes.saturating_inc(); } - let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - collection_details.total_deposit.saturating_reduce(old_deposit); + + let old_deposit = + attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); + let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); } - collection_details.total_deposit.saturating_accrue(deposit); - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + + // NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace. + // When the new owner updates the same attribute, we will update the depositor record + // and return the deposit to the previous owner. + if old_deposit.account.is_some() && old_deposit.account != Some(origin.clone()) { + T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount); + T::Currency::reserve(&origin, deposit)?; + } else if deposit > old_deposit.amount { + T::Currency::reserve(&origin, deposit - old_deposit.amount)?; + } else if deposit < old_deposit.amount { + T::Currency::unreserve(&origin, old_deposit.amount - deposit); } - Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + // NOTE: we don't track the depositor in the CollectionOwner namespace as it's always a + // collection's owner. This simplifies the collection's transfer to another owner. + let deposit_owner = match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); + collection_details.owner_deposit.saturating_accrue(deposit); + None + }, + _ => Some(origin), + }; + + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: deposit_owner, amount: deposit }), + ); Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); Ok(()) } - pub(crate) fn do_clear_attribute( - maybe_check_owner: Option, + pub(crate) fn do_force_set_attribute( + set_as: Option, collection: T::CollectionId, maybe_item: Option, key: BoundedVec, + value: BoundedVec, + namespace: AttributeNamespace, ) -> DispatchResult { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + if let Some((_, deposit)) = attribute { + if deposit.account != set_as && deposit.amount != Zero::zero() { + if let Some(deposit_account) = deposit.account { + T::Currency::unreserve(&deposit_account, deposit.amount); + } + } + } else { + collection_details.attributes.saturating_inc(); } - if maybe_check_owner.is_some() { - match maybe_item { - None => { - let collection_config = Self::get_collection_config(&collection)?; + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: set_as, amount: Zero::zero() }), + ); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + namespace: AttributeNamespace, + ) -> DispatchResult { + if let Some((_, deposit)) = + Attribute::::take((collection, maybe_item, &namespace, &key)) + { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + if deposit.account != maybe_check_owner { ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - // NOTE: if the item was previously burned, the ItemConfigOf record might - // not exist. In that case, we allow to clear the attribute. - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedAttributes)); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - } + Self::is_valid_namespace( + &check_owner, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::NoPermission + ); + } + + // can't clear `CollectionOwner` type attributes if the collection/item is locked + match namespace { + AttributeNamespace::CollectionOwner => match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config + .is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemConfigOf record + // might not exist. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| { + c.has_disabled_setting(ItemSetting::UnlockedAttributes) + }); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }, + _ => (), + }; + } - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { collection_details.attributes.saturating_dec(); - collection_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&collection_details.owner, deposit); + match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_reduce(deposit.amount); + T::Currency::unreserve(&collection_details.owner, deposit.amount); + }, + _ => (), + }; + if let Some(deposit_account) = deposit.account { + T::Currency::unreserve(&deposit_account, deposit.amount); + } Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace }); } Ok(()) } + + pub(crate) fn do_approve_item_attributes( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals + .try_insert(delegate.clone()) + .map_err(|_| Error::::ReachedApprovalLimit)?; + + Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate }); + Ok(()) + }) + } + + pub(crate) fn do_cancel_item_attributes_approval( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals.remove(&delegate); + + let mut attributes: u32 = 0; + let mut deposited: DepositBalanceOf = Zero::zero(); + for (_, (_, deposit)) in Attribute::::drain_prefix(( + &collection, + Some(item), + AttributeNamespace::Account(delegate.clone()), + )) { + attributes.saturating_inc(); + deposited = deposited.saturating_add(deposit.amount); + } + ensure!(attributes <= witness.account_attributes, Error::::BadWitness); + + if !deposited.is_zero() { + T::Currency::unreserve(&delegate, deposited); + } + + Self::deposit_event(Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate, + }); + Ok(()) + }) + } + + fn is_valid_namespace( + origin: &T::AccountId, + namespace: &AttributeNamespace, + collection: &T::CollectionId, + collection_owner: &T::AccountId, + maybe_item: &Option, + ) -> Result { + let mut result = false; + match namespace { + AttributeNamespace::CollectionOwner => result = origin == collection_owner, + AttributeNamespace::ItemOwner => + if let Some(item) = maybe_item { + let item_details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + result = origin == &item_details.owner + }, + AttributeNamespace::Account(account_id) => + if let Some(item) = maybe_item { + let approvals = ItemAttributesApprovalsOf::::get(&collection, &item); + result = account_id == origin && approvals.contains(&origin) + }, + _ => (), + }; + Ok(result) + } } diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs index b9530e88b18cd..86625bf49efb2 100644 --- a/frame/nfts/src/features/create_delete_collection.rs +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -35,7 +35,7 @@ impl, I: 'static> Pallet { collection, CollectionDetails { owner: owner.clone(), - total_deposit: deposit, + owner_deposit: deposit, items: 0, item_metadatas: 0, attributes: 0, @@ -90,12 +90,21 @@ impl, I: 'static> Pallet { PendingSwapOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); Self::clear_roles(&collection)?; - #[allow(deprecated)] - Attribute::::remove_prefix((&collection,), None); + + for (_, (_, deposit)) in Attribute::::drain_prefix((&collection,)) { + if !deposit.amount.is_zero() { + if let Some(account) = deposit.account { + T::Currency::unreserve(&account, deposit.amount); + } + } + } + CollectionAccount::::remove(&collection_details.owner, &collection); - T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + let _ = + ItemAttributesApprovalsOf::::clear_prefix(&collection, witness.items, None); Self::deposit_event(Event::Destroyed { collection }); diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs index 10670f4b10c1c..bae1d02c8ad6b 100644 --- a/frame/nfts/src/features/create_delete_item.rs +++ b/frame/nfts/src/features/create_delete_item.rs @@ -109,6 +109,7 @@ impl, I: 'static> Pallet { Account::::remove((&owner, &collection, &item)); ItemPriceOf::::remove(&collection, &item); PendingSwapOf::::remove(&collection, &item); + ItemAttributesApprovalsOf::::remove(&collection, &item); // NOTE: if item's settings are not empty (e.g. item's metadata is locked) // then we keep the record and don't remove it diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs index 0b0a337197d9b..3a12dbe64f2f4 100644 --- a/frame/nfts/src/features/metadata.rs +++ b/frame/nfts/src/features/metadata.rs @@ -46,7 +46,7 @@ impl, I: 'static> Pallet { collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - collection_details.total_deposit.saturating_reduce(old_deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && maybe_check_owner.is_some() @@ -60,7 +60,7 @@ impl, I: 'static> Pallet { } else if deposit < old_deposit { T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - collection_details.total_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_accrue(deposit); *metadata = Some(ItemMetadata { deposit, data: data.clone() }); @@ -93,7 +93,7 @@ impl, I: 'static> Pallet { } let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; T::Currency::unreserve(&collection_details.owner, deposit); - collection_details.total_deposit.saturating_reduce(deposit); + collection_details.owner_deposit.saturating_reduce(deposit); Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::MetadataCleared { collection, item }); @@ -121,7 +121,7 @@ impl, I: 'static> Pallet { CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - details.total_deposit.saturating_reduce(old_deposit); + details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); if maybe_check_owner.is_some() && collection_config.is_setting_enabled(CollectionSetting::DepositRequired) @@ -135,7 +135,7 @@ impl, I: 'static> Pallet { } else if deposit < old_deposit { T::Currency::unreserve(&details.owner, old_deposit - deposit); } - details.total_deposit.saturating_accrue(deposit); + details.owner_deposit.saturating_accrue(deposit); Collection::::insert(&collection, details); diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs index f2040566d5f7a..98a6b39e26375 100644 --- a/frame/nfts/src/features/transfer.rs +++ b/frame/nfts/src/features/transfer.rs @@ -100,11 +100,12 @@ impl, I: 'static> Pallet { T::Currency::repatriate_reserved( &details.owner, &owner, - details.total_deposit, + details.owner_deposit, Reserved, )?; CollectionAccount::::remove(&details.owner, &collection); CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); OwnershipAcceptance::::remove(&owner); diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index b42147e6687d9..3dd3ec06ceffa 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -50,13 +50,14 @@ impl, I: 'static> Inspect<::AccountId> for Palle collection: &Self::CollectionId, item: &Self::ItemId, key: &[u8], + namespace: &AttributeNamespace<::AccountId>, ) -> Option> { if key.is_empty() { // We make the empty key map to the item metadata value. ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((collection, Some(item), key)).map(|a| a.0.into()) + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) } } @@ -71,7 +72,13 @@ impl, I: 'static> Inspect<::AccountId> for Palle CollectionMetadataOf::::get(collection).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((collection, Option::::None, key)).map(|a| a.0.into()) + Attribute::::get(( + collection, + Option::::None, + AttributeNamespace::CollectionOwner, + key, + )) + .map(|a| a.0.into()) } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index c5138d927bb39..329fac63d1e7d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -44,11 +44,10 @@ pub mod macros; pub mod weights; use codec::{Decode, Encode}; -use frame_support::{ - traits::{ - tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, - }, - BoundedBTreeMap, +use frame_support::traits::{ + tokens::{AttributeNamespace, Locker}, + BalanceStatus::Reserved, + Currency, EnsureOriginWithArg, ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -156,6 +155,10 @@ pub mod pallet { #[pallet::constant] type ApprovalsLimit: Get; + /// The maximum attributes approvals an item could have. + #[pallet::constant] + type ItemAttributesApprovalsLimit: Get; + /// The max number of tips a user could send. #[pallet::constant] type MaxTips: Get; @@ -271,9 +274,10 @@ pub mod pallet { ( NMapKey, NMapKey>, + NMapKey>, NMapKey>, ), - (BoundedVec, DepositBalanceOf), + (BoundedVec, AttributeDepositOf), OptionQuery, >; @@ -289,6 +293,18 @@ pub mod pallet { OptionQuery, >; + /// Item attribute approvals. + #[pallet::storage] + pub(super) type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemAttributesApprovals, + ValueQuery, + >; + /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented by 1 whenever a new collection is created. #[pallet::storage] @@ -412,12 +428,26 @@ pub mod pallet { maybe_item: Option, key: BoundedVec, value: BoundedVec, + namespace: AttributeNamespace, }, /// Attribute metadata has been cleared for a `collection` or `item`. AttributeCleared { collection: T::CollectionId, maybe_item: Option, key: BoundedVec, + namespace: AttributeNamespace, + }, + /// A new approval to modify item attributes was added. + ItemAttributesApprovalAdded { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + }, + /// A new approval to modify item attributes was removed. + ItemAttributesApprovalRemoved { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, }, /// Ownership acceptance has changed for an account. OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, @@ -871,7 +901,7 @@ pub mod pallet { /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `collection`: The collection to be frozen. + /// - `collection`: The collection of the items to be reevaluated. /// - `items`: The items of the collection whose deposits will be reevaluated. /// /// NOTE: This exists as a best-effort function. Any items which are unknown or @@ -1222,11 +1252,40 @@ pub mod pallet { maybe_item: Option, key: BoundedVec, value: BoundedVec, + namespace: AttributeNamespace, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) - .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) + let origin = ensure_signed(origin)?; + Self::do_set_attribute(origin, collection, maybe_item, key, value, namespace) + } + + /// Force-set an attribute for a collection or item. + /// + /// Origin must be `ForceOrigin`. + /// + /// If the origin is Signed, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `key`: The key of the attribute. + /// - `value`: The value to which to set the attribute. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_attribute())] + pub fn force_set_attribute( + origin: OriginFor, + set_as: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + namespace: AttributeNamespace, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_set_attribute(set_as, collection, maybe_item, key, value, namespace) } /// Clear an attribute for a collection or item. @@ -1249,11 +1308,53 @@ pub mod pallet { collection: T::CollectionId, maybe_item: Option, key: BoundedVec, + namespace: AttributeNamespace, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key, namespace) + } + + /// Approve item's attributes to be changed by a delegated third-party account. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: A collection of the item. + /// - `item`: The item that holds attributes. + /// - `delegate`: The account to delegate permission to change attributes of the item. + /// + /// Emits `ItemAttributesApprovalAdded` on success. + #[pallet::weight(0)] + pub fn approve_item_attributes( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_approve_item_attributes(origin, collection, item, delegate) + } + + /// Cancel the previously provided approval to change item's attributes. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: A collection of the item. + /// - `item`: The item that holds attributes. + /// - `delegate`: The previously approved account to remove. + /// + /// Emits `ItemAttributesApprovalRemoved` on success. + #[pallet::weight(0)] + pub fn cancel_item_attributes_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness) } /// Set the metadata for an item. diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index bbd1625710500..f814b209d5f78 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -105,6 +105,7 @@ impl Config for Test { type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; type ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; type MaxTips = ConstU32<10>; type MaxDeadlineDuration = ConstU64<10000>; type Features = Features; diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 36bec726f4ee3..8a0c19e5933e1 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -69,7 +69,7 @@ macro_rules! bvec { fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) - .map(|(k, v)| (k.0, k.1.into(), v.0.into())) + .map(|(k, v)| (k.0, k.2.into(), v.0.into())) .collect(); s.sort(); s @@ -81,6 +81,12 @@ fn approvals(collection_id: u32, item_id: u32) -> Vec<(u64, Option)> { s } +fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec { + let approvals = ItemAttributesApprovalsOf::::get(collection_id, item_id); + let s: Vec<_> = approvals.into_iter().collect(); + s +} + fn events() -> Vec> { let result = System::events() .into_iter() @@ -598,9 +604,30 @@ fn set_attribute_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![1], + bvec![0], + AttributeNamespace::CollectionOwner, + )); assert_eq!( attributes(0), vec![ @@ -611,7 +638,14 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 10); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + bvec![0], + bvec![0; 10], + AttributeNamespace::CollectionOwner, + )); assert_eq!( attributes(0), vec![ @@ -622,7 +656,13 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 19); - assert_ok!(Nfts::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![1], + AttributeNamespace::CollectionOwner, + )); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] @@ -649,9 +689,30 @@ fn set_attribute_should_respect_lock() { assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + )); assert_eq!( attributes(0), vec![ @@ -670,16 +731,47 @@ fn set_attribute_should_respect_lock() { )); let e = Error::::LockedCollectionAttributes; - assert_noop!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), e); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1])); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + bvec![0], + bvec![0], + AttributeNamespace::CollectionOwner, + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![1], + AttributeNamespace::CollectionOwner, + )); assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); let e = Error::::LockedItemAttributes; assert_noop!( - Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![1], + AttributeNamespace::CollectionOwner, + ), e ); - assert_ok!(Nfts::set_attribute(RuntimeOrigin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + bvec![0], + bvec![1], + AttributeNamespace::CollectionOwner, + )); }); } @@ -1929,7 +2021,8 @@ fn pallet_level_feature_flags_should_work() { collection_id, None, bvec![0], - bvec![0] + bvec![0], + AttributeNamespace::CollectionOwner, ), Error::::MethodDisabled ); @@ -1965,3 +2058,58 @@ fn group_roles_by_account_should_work() { assert_eq!(account_to_role, expect); }) } + +#[test] +fn add_remove_item_attributes_approval_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let user_4 = 4; + let collection_id = 0; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, None)); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2]); + + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_3, + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2, user_3]); + + assert_noop!( + Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_4, + ), + Error::::ReachedApprovalLimit + ); + + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + CancelAttributesApprovalWitness { account_attributes: 1 }, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3]); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index d57f62be97f39..c12ae39877d46 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -24,6 +24,7 @@ use enumflags2::{bitflags, BitFlags}; use frame_support::{ pallet_prelude::{BoundedVec, MaxEncodedLen}, traits::Get, + BoundedBTreeMap, BoundedBTreeSet, }; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; @@ -36,8 +37,12 @@ pub(super) type ApprovalsOf = BoundedBTreeMap< Option<::BlockNumber>, >::ApprovalsLimit, >; +pub(super) type ItemAttributesApprovals = + BoundedBTreeSet<::AccountId, >::ItemAttributesApprovalsLimit>; pub(super) type ItemDepositOf = ItemDeposit, ::AccountId>; +pub(super) type AttributeDepositOf = + AttributeDeposit, ::AccountId>; pub(super) type ItemDetailsFor = ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; pub(super) type BalanceOf = @@ -65,9 +70,9 @@ impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); pub struct CollectionDetails { /// Collection's owner. pub(super) owner: AccountId, - /// The total balance deposited for the all storage associated with this collection. - /// Used by `destroy`. - pub(super) total_deposit: DepositBalance, + /// The total balance deposited by the owner for the all storage data associated with this + /// collection. Used by `destroy`. + pub(super) owner_deposit: DepositBalance, /// The total number of outstanding items of this collection. pub(super) items: u32, /// The total number of outstanding item metadata of this collection. @@ -100,6 +105,13 @@ impl CollectionDetails { } } +/// Witness data for items mint transactions. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owner_of_item: ItemId, +} + /// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ItemDetails { @@ -173,6 +185,15 @@ pub struct PendingSwap { pub(super) deadline: Deadline, } +/// Information about the reserved attribute deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct AttributeDeposit { + /// A depositor account. + pub(super) account: Option, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum PriceDirection { Send, @@ -265,9 +286,9 @@ impl Default for MintSettings { - /// Provide the id of the item in a required collection. - pub owner_of_item: ItemId, +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, } #[derive( diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index b3b3b4b7d90b1..03a24bd3ba9c8 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -28,6 +28,6 @@ pub mod nonfungibles; pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ - AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, - Locker, WithdrawConsequence, WithdrawReasons, + AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, + ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 294d0e89c8b9e..f9876ef477b81 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -126,6 +126,21 @@ pub enum BalanceStatus { Reserved, } +/// Attribute namespaces for non-fungible tokens. +#[derive( + Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] +pub enum AttributeNamespace { + /// An attribute was set by the pallet. + Pallet, + /// An attribute was set by collection's owner. + CollectionOwner, + /// An attribute was set by item's owner. + ItemOwner, + /// An attribute was set by pre-approved account. + Account(AccountId), +} + bitflags::bitflags! { /// Reasons for moving funds out of an account. #[derive(Encode, Decode, MaxEncodedLen)] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 4f610d9b80a05..86c7742ed54e6 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -25,7 +25,10 @@ //! use. use super::nonfungibles_v2 as nonfungibles; -use crate::{dispatch::DispatchResult, traits::Get}; +use crate::{ + dispatch::DispatchResult, + traits::{tokens::misc::AttributeNamespace, Get}, +}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -42,15 +45,23 @@ pub trait Inspect { /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + fn attribute( + _item: &Self::ItemId, + _key: &[u8], + _namespace: &AttributeNamespace, + ) -> Option> { None } /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { - key.using_encoded(|d| Self::attribute(item, d)) + fn typed_attribute( + item: &Self::ItemId, + key: &K, + namespace: &AttributeNamespace, + ) -> Option { + key.using_encoded(|d| Self::attribute(item, d, namespace)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -137,11 +148,19 @@ impl< fn owner(item: &Self::ItemId) -> Option { >::owner(&A::get(), item) } - fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { - >::attribute(&A::get(), item, key) + fn attribute( + item: &Self::ItemId, + key: &[u8], + namespace: &AttributeNamespace, + ) -> Option> { + >::attribute(&A::get(), item, key, namespace) } - fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { - >::typed_attribute(&A::get(), item, key) + fn typed_attribute( + item: &Self::ItemId, + key: &K, + namespace: &AttributeNamespace, + ) -> Option { + >::typed_attribute(&A::get(), item, key, namespace) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 0aec193f68fcb..ae87d6ccda41f 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -27,7 +27,10 @@ //! Implementations of these traits may be converted to implementations of corresponding //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. -use crate::dispatch::{DispatchError, DispatchResult}; +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::tokens::misc::AttributeNamespace, +}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -59,6 +62,7 @@ pub trait Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _key: &[u8], + _namespace: &AttributeNamespace, ) -> Option> { None } @@ -71,8 +75,9 @@ pub trait Inspect { collection: &Self::CollectionId, item: &Self::ItemId, key: &K, + namespace: &AttributeNamespace, ) -> Option { - key.using_encoded(|d| Self::attribute(collection, item, d)) + key.using_encoded(|d| Self::attribute(collection, item, d, namespace)) .and_then(|v| V::decode(&mut &v[..]).ok()) } From 820891c1cdcbea2764905c8830cc56aabc5f1580 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 15 Nov 2022 17:01:37 +0200 Subject: [PATCH 76/94] Split force_collection_status into 2 methods --- frame/nfts/src/benchmarking.rs | 18 ++-- frame/nfts/src/features/roles.rs | 6 +- frame/nfts/src/features/settings.rs | 35 ++------ frame/nfts/src/features/transfer.rs | 27 ++++++ frame/nfts/src/lib.rs | 124 +++++++++++++++------------- frame/nfts/src/tests.rs | 63 +++++--------- frame/nfts/src/weights.rs | 23 +++++- 7 files changed, 155 insertions(+), 141 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 5dcbe899e58db..a1643a99afff6 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -299,20 +299,28 @@ benchmarks_instance_pallet! { }.into()); } - force_collection_status { + force_collection_owner { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_collection_status { collection, owner: caller_lookup.clone(), - issuer: caller_lookup.clone(), - admin: caller_lookup.clone(), - freezer: caller_lookup, + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::OwnerChanged { collection }.into()); + } + + force_collection_config { + let (collection, caller, caller_lookup) = create_collection::(); + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_collection_status { + collection, config: make_collection_config::(CollectionSetting::DepositRequired.into()), }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::CollectionStatusChanged { collection }.into()); + assert_last_event::(Event::CollectionConfigChanged { collection }.into()); } lock_item_properties { diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs index 7f6f42363fe33..d6be9965a5e74 100644 --- a/frame/nfts/src/features/roles.rs +++ b/frame/nfts/src/features/roles.rs @@ -21,7 +21,7 @@ use sp_std::collections::btree_map::BTreeMap; impl, I: 'static> Pallet { pub(crate) fn do_set_team( - origin: T::AccountId, + maybe_check_owner: Option, collection: T::CollectionId, issuer: T::AccountId, admin: T::AccountId, @@ -29,7 +29,9 @@ impl, I: 'static> Pallet { ) -> DispatchResult { Collection::::try_mutate(collection, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); + if let Some(check_origin) = maybe_check_owner { + ensure!(check_origin == details.owner, Error::::NoPermission); + } // delete previous values Self::clear_roles(&collection)?; diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 59444f728140c..5f408ed183c35 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -19,39 +19,14 @@ use crate::*; use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { - pub(crate) fn do_force_collection_status( + pub(crate) fn do_force_collection_config( collection: T::CollectionId, - new_owner: T::AccountId, - issuer: T::AccountId, - admin: T::AccountId, - freezer: T::AccountId, config: CollectionConfigFor, ) -> DispatchResult { - Collection::::try_mutate(collection, |maybe_collection| { - let mut collection_info = - maybe_collection.take().ok_or(Error::::UnknownCollection)?; - let old_owner = collection_info.owner; - collection_info.owner = new_owner.clone(); - *maybe_collection = Some(collection_info); - CollectionAccount::::remove(&old_owner, &collection); - CollectionAccount::::insert(&new_owner, &collection, ()); - CollectionConfigOf::::insert(&collection, config); - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer, CollectionRole::Issuer), - (admin, CollectionRole::Admin), - (freezer, CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::CollectionStatusChanged { collection }); - Ok(()) - }) + ensure!(Collection::::contains_key(&collection), Error::::UnknownCollection); + CollectionConfigOf::::insert(&collection, config); + Self::deposit_event(Event::CollectionConfigChanged { collection }); + Ok(()) } pub(crate) fn do_set_collection_max_supply( diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs index f2040566d5f7a..7ebad853902a9 100644 --- a/frame/nfts/src/features/transfer.rs +++ b/frame/nfts/src/features/transfer.rs @@ -135,4 +135,31 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); Ok(()) } + + pub(crate) fn do_force_collection_owner( + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } } diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index c5138d927bb39..0f3d3c89c2932 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -390,8 +390,8 @@ pub mod pallet { }, /// All approvals of an item got cancelled. AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, - /// A `collection` has had its attributes changed by the `Force` origin. - CollectionStatusChanged { collection: T::CollectionId }, + /// A `collection` has had its config changed by the `Force` origin. + CollectionConfigChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, /// Metadata has been cleared for a `collection`. @@ -427,8 +427,6 @@ pub mod pallet { CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emmited when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: T::CollectionId }, - /// The config of a collection has change. - CollectionConfigChanged { id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -665,10 +663,9 @@ pub mod pallet { collection: T::CollectionId, witness: DestroyWitness, ) -> DispatchResultWithPostInfo { - let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { - Ok(_) => None, - Err(origin) => Some(ensure_signed(origin)?), - }; + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; Ok(Some(T::WeightInfo::destroy( @@ -778,10 +775,9 @@ pub mod pallet { owner: AccountIdLookupOf, item_config: ItemConfig, ) -> DispatchResult { - let maybe_check_origin = match T::ForceOrigin::try_origin(origin) { - Ok(_) => None, - Err(origin) => Some(ensure_signed(origin)?), - }; + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let owner = T::Lookup::lookup(owner)?; if let Some(check_origin) = maybe_check_origin { @@ -1018,7 +1014,8 @@ pub mod pallet { /// Change the Issuer, Admin and Freezer of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. /// /// - `collection`: The collection whose team should be changed. /// - `issuer`: The new Issuer of this collection. @@ -1036,16 +1033,60 @@ pub mod pallet { admin: AccountIdLookupOf, freezer: AccountIdLookupOf, ) -> DispatchResult { - let origin = ensure_signed(origin)?; + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - Self::do_set_team(origin, collection, issuer, admin, freezer) + Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) + } + + /// Change the Owner of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `owner`: The new Owner of this collection. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_collection_owner())] + pub fn force_collection_owner( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let new_owner = T::Lookup::lookup(owner)?; + Self::do_force_collection_owner(collection, new_owner) + } + + /// Change the config of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `config`: The new config of this collection. + /// + /// Emits `CollectionConfigChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_collection_config())] + pub fn force_collection_config( + origin: OriginFor, + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_collection_config(collection, config) } /// Approve an item to be transferred by a delegated third-party account. /// - /// Origin must be Signed and must be the owner of the `item`. + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `item`. /// /// - `collection`: The collection of the item to be approved for delegated transfer. /// - `item`: The item to be approved for delegated transfer. @@ -1132,38 +1173,6 @@ pub mod pallet { Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } - /// Alter the attributes of a given collection. - /// - /// Origin must be `ForceOrigin`. - /// - /// - `collection`: The identifier of the collection. - /// - `owner`: The new Owner of this collection. - /// - `issuer`: The new Issuer of this collection. - /// - `admin`: The new Admin of this collection. - /// - `freezer`: The new Freezer of this collection. - /// - `config`: Collection's config. - /// - /// Emits `CollectionStatusChanged` with the identity of the item. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_collection_status())] - pub fn force_collection_status( - origin: OriginFor, - collection: T::CollectionId, - owner: AccountIdLookupOf, - issuer: AccountIdLookupOf, - admin: AccountIdLookupOf, - freezer: AccountIdLookupOf, - config: CollectionConfigFor, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - let new_owner = T::Lookup::lookup(owner)?; - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - Self::do_force_collection_status(collection, new_owner, issuer, admin, freezer, config) - } - /// Disallows changing the metadata of attributes of the item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the @@ -1187,8 +1196,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; - + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_lock_item_properties( maybe_check_owner, collection, @@ -1225,7 +1233,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) } @@ -1252,7 +1260,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) } @@ -1281,7 +1289,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_item_metadata(maybe_check_owner, collection, item, data) } @@ -1306,7 +1314,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_item_metadata(maybe_check_owner, collection, item) } @@ -1333,7 +1341,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_collection_metadata(maybe_check_owner, collection, data) } @@ -1356,7 +1364,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_collection_metadata(maybe_check_owner, collection) } @@ -1396,7 +1404,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) } @@ -1421,7 +1429,7 @@ pub mod pallet { ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) - .or_else(|origin| ensure_signed(origin).map(Some))?; + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 36bec726f4ee3..b58c81b1d70f8 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -325,13 +325,9 @@ fn locking_transfer_should_work() { Error::::ItemsNonTransferable ); - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), 0, - 1, - 1, - 1, - 1, collection_config_with_all_settings_enabled(), )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); @@ -734,7 +730,7 @@ fn preserve_config_for_frozen_items() { } #[test] -fn force_collection_status_should_work() { +fn force_update_collection_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); @@ -751,43 +747,36 @@ fn force_collection_status_should_work() { assert_eq!(Balances::reserved_balance(1), 65); // force item status to be free holding - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), 0, - 1, - 1, - 1, - 1, collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, None)); assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 65); - assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); - assert_eq!(Balances::reserved_balance(1), 63); + Balances::make_free_balance_be(&5, 100); + assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, 5)); + assert_eq!(collections(), vec![(5, 0)]); + assert_eq!(Balances::reserved_balance(1), 2); + assert_eq!(Balances::reserved_balance(5), 63); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 42); + assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(5), 0, bvec![0, 42, 50, 69, 100])); + assert_eq!(Balances::reserved_balance(1), 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 21); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 42, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 42); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 0); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 21); + + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(5), 0, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 0); // validate new roles - assert_ok!(Nfts::force_collection_status( - RuntimeOrigin::root(), - 0, - 1, - 2, - 3, - 4, - collection_config_with_all_settings_enabled(), - )); + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 2, 3, 4)); assert_eq!( CollectionRoleOf::::get(0, 2).unwrap(), CollectionRoles(CollectionRole::Issuer.into()) @@ -801,15 +790,7 @@ fn force_collection_status_should_work() { CollectionRoles(CollectionRole::Freezer.into()) ); - assert_ok!(Nfts::force_collection_status( - RuntimeOrigin::root(), - 0, - 1, - 3, - 2, - 3, - collection_config_with_all_settings_enabled(), - )); + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 3, 2, 3)); assert_eq!( CollectionRoleOf::::get(0, 2).unwrap(), @@ -1400,13 +1381,9 @@ fn buy_item_should_work() { ); // unlock the collection - assert_ok!(Nfts::force_collection_status( + assert_ok!(Nfts::force_collection_config( RuntimeOrigin::root(), collection_id, - user_1, - user_1, - user_1, - user_1, collection_config_with_all_settings_enabled(), )); diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index 52a848bfff7d4..f254726ca19f2 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -59,7 +59,8 @@ pub trait WeightInfo { fn lock_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_collection_status() -> Weight; + fn force_collection_owner() -> Weight; + fn force_collection_config() -> Weight; fn lock_item_properties() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; @@ -224,7 +225,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - fn force_collection_status() -> Weight { + fn force_collection_owner() -> Weight { + Weight::from_ref_time(28_468_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { Weight::from_ref_time(28_468_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) @@ -524,7 +533,15 @@ impl WeightInfo for () { // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ClassAccount (r:0 w:1) // Storage: Nfts CollectionConfigOf (r:0 w:1) - fn force_collection_status() -> Weight { + fn force_collection_owner() -> Weight { + Weight::from_ref_time(28_468_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts ClassAccount (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { Weight::from_ref_time(28_468_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) From 51328b0b73bfc5ce8bc6283e994117c61a790666 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 15 Nov 2022 18:46:17 +0200 Subject: [PATCH 77/94] Fix benchmarks --- frame/nfts/src/benchmarking.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index a1643a99afff6..89cda7defa35f 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -224,13 +224,9 @@ benchmarks_instance_pallet! { let i in 0 .. 5_000; let (collection, caller, caller_lookup) = create_collection::(); let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); - Nfts::::force_collection_status( + Nfts::::force_collection_config( SystemOrigin::Root.into(), collection, - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup, make_collection_config::(CollectionSetting::DepositRequired.into()), )?; }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) @@ -302,19 +298,19 @@ benchmarks_instance_pallet! { force_collection_owner { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_collection_status { + let call = Call::::force_collection_owner { collection, owner: caller_lookup.clone(), }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::OwnerChanged { collection }.into()); + assert_last_event::(Event::OwnerChanged { collection, new_owner: caller }.into()); } force_collection_config { let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_collection_status { + let call = Call::::force_collection_config { collection, config: make_collection_config::(CollectionSetting::DepositRequired.into()), }; From c1e71a295fd10ebe8b736eb912710d9876312050 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Tue, 15 Nov 2022 19:35:02 +0200 Subject: [PATCH 78/94] Fix benchmarks --- frame/nfts/src/benchmarking.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 89cda7defa35f..61407abd9f985 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -296,15 +296,18 @@ benchmarks_instance_pallet! { } force_collection_owner { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, _, _) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let call = Call::::force_collection_owner { collection, - owner: caller_lookup.clone(), + owner: target_lookup, }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::OwnerChanged { collection, new_owner: caller }.into()); + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); } force_collection_config { From 67d5643f7939284c5730945024e048ab4f5c2978 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 10:33:25 +0200 Subject: [PATCH 79/94] Update deps --- frame/nfts/Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index f0b68ea702e3a..109dffdd10f50 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -20,14 +20,14 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } [features] default = ["std"] From 9bdd37d1674d527a626bd289c556b40e8e6a1ce9 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 10:42:23 +0200 Subject: [PATCH 80/94] Fix merge artifact --- frame/nfts/src/features/transfer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs index a589c885fcac5..7d6ae3553a361 100644 --- a/frame/nfts/src/features/transfer.rs +++ b/frame/nfts/src/features/transfer.rs @@ -151,7 +151,7 @@ impl, I: 'static> Pallet { T::Currency::repatriate_reserved( &details.owner, &owner, - details.total_deposit, + details.owner_deposit, Reserved, )?; From 83e9221c67344fa66a4cc56bdf9b2db3499b3277 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 13:23:53 +0200 Subject: [PATCH 81/94] Weights + benchmarks + docs --- frame/nfts/src/benchmarking.rs | 106 +++++++++++++++++++++++++++++++-- frame/nfts/src/lib.rs | 35 +++++++---- frame/nfts/src/weights.rs | 57 ++++++++++++++++++ 3 files changed, 180 insertions(+), 18 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 61407abd9f985..57f6c7ac97de1 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -116,6 +116,7 @@ fn add_item_attribute, I: 'static>( Some(item), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + AttributeNamespace::CollectionOwner, )); (key, caller, caller_lookup) } @@ -338,10 +339,38 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - add_item_metadata::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone(), AttributeNamespace::CollectionOwner) + verify { + assert_last_event::( + Event::AttributeSet { + collection, + maybe_item: Some(item), + key, + value, + namespace: AttributeNamespace::CollectionOwner, + } + .into(), + ); + } + + force_set_attribute { + let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Root, Some(caller), collection, Some(item), key.clone(), value.clone(), AttributeNamespace::CollectionOwner) verify { - assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); + assert_last_event::( + Event::AttributeSet { + collection, + maybe_item: Some(item), + key, + value, + namespace: AttributeNamespace::CollectionOwner, + } + .into(), + ); } clear_attribute { @@ -349,9 +378,76 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); add_item_metadata::(item); let (key, ..) = add_item_attribute::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), AttributeNamespace::CollectionOwner) + verify { + assert_last_event::( + Event::AttributeCleared { + collection, + maybe_item: Some(item), + key, + namespace: AttributeNamespace::CollectionOwner, + }.into(), + ); + } + + approve_item_attributes { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller), collection, item, target_lookup) verify { - assert_last_event::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); + assert_last_event::( + Event::ItemAttributesApprovalAdded { + collection, + item, + delegate: target, + } + .into(), + ); + } + + cancel_item_attributes_approval { + let n in 0 .. 1_000; + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + Nfts::::approve_item_attributes( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + target_lookup.clone(), + )?; + T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + for i in 0..n { + let mut key = vec![0u8; T::KeyLimit::get() as usize]; + let mut s = Vec::from((i as u16).to_string().as_bytes()); + key.truncate(s.len()); + key.append(&mut s); + + Nfts::::set_attribute( + SystemOrigin::Signed(target.clone()).into(), + T::Helper::collection(0), + Some(item), + key.try_into().unwrap(), + value.clone(), + AttributeNamespace::Account(target.clone()), + )?; + } + let witness = CancelAttributesApprovalWitness { account_attributes: n }; + }: _(SystemOrigin::Signed(caller), collection, item, target_lookup, witness) + verify { + assert_last_event::( + Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate: target, + } + .into(), + ); } set_metadata { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index a9d93ce6c661b..7ad0431d7c53a 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1238,17 +1238,22 @@ pub mod pallet { /// Set an attribute for a collection or item. /// - /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// `collection`. - /// - /// If the origin is Signed, then funds of signer are reserved according to the formula: - /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into + /// Origin must be Signed and must conform to the namespace ruleset: + /// - `CollectionOwner` namespace could be modified by the `collection` owner only; + /// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item` + /// should be set in that case; + /// - `Account(AccountId)` namespace could be modified only when the `origin` was given a + /// permission to do so; + /// + /// The funds of `origin` are reserved according to the formula: + /// `AttributeDepositBase + DepositPerByte * (key.len + value.len)` taking into /// account any already reserved funds. /// /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. + /// - `namespace`: Attribute's namespace. /// /// Emits `AttributeSet`. /// @@ -1270,19 +1275,20 @@ pub mod pallet { /// /// Origin must be `ForceOrigin`. /// - /// If the origin is Signed, then funds of signer are reserved according to the formula: - /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into - /// account any already reserved funds. + /// If the attribute already exist and it was set by another account, the deposit + /// will be returned to the previous owner. /// + /// - `set_as`: An optional owner if the attribute. /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. + /// - `namespace`: Attribute's namespace. /// /// Emits `AttributeSet`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::set_attribute())] + #[pallet::weight(T::WeightInfo::force_set_attribute())] pub fn force_set_attribute( origin: OriginFor, set_as: Option, @@ -1306,6 +1312,7 @@ pub mod pallet { /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `maybe_item`: The identifier of the item whose metadata to clear. /// - `key`: The key of the attribute. + /// - `namespace`: Attribute's namespace. /// /// Emits `AttributeCleared`. /// @@ -1333,14 +1340,15 @@ pub mod pallet { /// - `delegate`: The account to delegate permission to change attributes of the item. /// /// Emits `ItemAttributesApprovalAdded` on success. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, - delegate: T::AccountId, + delegate: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; Self::do_approve_item_attributes(origin, collection, item, delegate) } @@ -1353,15 +1361,16 @@ pub mod pallet { /// - `delegate`: The previously approved account to remove. /// /// Emits `ItemAttributesApprovalRemoved` on success. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval())] pub fn cancel_item_attributes_approval( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, - delegate: T::AccountId, + delegate: AccountIdLookupOf, witness: CancelAttributesApprovalWitness, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness) } diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index f254726ca19f2..a7eb3773f2ae8 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -63,7 +63,10 @@ pub trait WeightInfo { fn force_collection_config() -> Weight; fn lock_item_properties() -> Weight; fn set_attribute() -> Weight; + fn force_set_attribute() -> Weight; fn clear_attribute() -> Weight; + fn approve_item_attributes() -> Weight; + fn cancel_item_attributes_approval() -> Weight; fn set_metadata() -> Weight; fn clear_metadata() -> Weight; fn set_collection_metadata() -> Weight; @@ -258,12 +261,39 @@ impl WeightInfo for SubstrateWeight { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { Weight::from_ref_time(52_530_000 as u64) .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn approve_item_attributes() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn cancel_item_attributes_approval() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) @@ -566,12 +596,39 @@ impl WeightInfo for () { // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + Weight::from_ref_time(53_019_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) fn clear_attribute() -> Weight { Weight::from_ref_time(52_530_000 as u64) .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn approve_item_attributes() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn cancel_item_attributes_approval() -> Weight { + Weight::from_ref_time(52_530_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nfts Class (r:1 w:1) // Storage: Nfts ItemConfigOf (r:1 w:0) // Storage: Nfts CollectionConfigOf (r:1 w:0) // Storage: Nfts InstanceMetadataOf (r:1 w:1) From 255b09572aa315a689ba1428a6b86cab6ee476c5 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 14:07:15 +0200 Subject: [PATCH 82/94] Change params order --- frame/nfts/src/benchmarking.rs | 16 ++++++------ frame/nfts/src/features/attributes.rs | 6 ++--- frame/nfts/src/impl_nonfungibles.rs | 2 +- frame/nfts/src/lib.rs | 12 ++++----- frame/nfts/src/tests.rs | 26 +++++++++---------- .../src/traits/tokens/nonfungible_v2.rs | 14 +++++----- .../src/traits/tokens/nonfungibles_v2.rs | 6 ++--- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 57f6c7ac97de1..fa98c36ba7d87 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -114,9 +114,9 @@ fn add_item_attribute, I: 'static>( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), Some(item), + AttributeNamespace::CollectionOwner, key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), - AttributeNamespace::CollectionOwner, )); (key, caller, caller_lookup) } @@ -339,15 +339,15 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone(), AttributeNamespace::CollectionOwner) + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) verify { assert_last_event::( Event::AttributeSet { collection, maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, key, value, - namespace: AttributeNamespace::CollectionOwner, } .into(), ); @@ -359,15 +359,15 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Root, Some(caller), collection, Some(item), key.clone(), value.clone(), AttributeNamespace::CollectionOwner) + }: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) verify { assert_last_event::( Event::AttributeSet { collection, maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, key, value, - namespace: AttributeNamespace::CollectionOwner, } .into(), ); @@ -378,14 +378,14 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); add_item_metadata::(item); let (key, ..) = add_item_attribute::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), AttributeNamespace::CollectionOwner) + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone()) verify { assert_last_event::( Event::AttributeCleared { collection, maybe_item: Some(item), - key, namespace: AttributeNamespace::CollectionOwner, + key, }.into(), ); } @@ -432,9 +432,9 @@ benchmarks_instance_pallet! { SystemOrigin::Signed(target.clone()).into(), T::Helper::collection(0), Some(item), + AttributeNamespace::Account(target.clone()), key.try_into().unwrap(), value.clone(), - AttributeNamespace::Account(target.clone()), )?; } let witness = CancelAttributesApprovalWitness { account_attributes: n }; diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index ae3d74cfdb863..ad820b01d62a6 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -23,9 +23,9 @@ impl, I: 'static> Pallet { origin: T::AccountId, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, - namespace: AttributeNamespace, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Attributes), @@ -116,9 +116,9 @@ impl, I: 'static> Pallet { set_as: Option, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, - namespace: AttributeNamespace, ) -> DispatchResult { let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -147,8 +147,8 @@ impl, I: 'static> Pallet { maybe_check_owner: Option, collection: T::CollectionId, maybe_item: Option, - key: BoundedVec, namespace: AttributeNamespace, + key: BoundedVec, ) -> DispatchResult { if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &namespace, &key)) diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 3dd3ec06ceffa..a9e05a6f41ce9 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -49,8 +49,8 @@ impl, I: 'static> Inspect<::AccountId> for Palle fn attribute( collection: &Self::CollectionId, item: &Self::ItemId, - key: &[u8], namespace: &AttributeNamespace<::AccountId>, + key: &[u8], ) -> Option> { if key.is_empty() { // We make the empty key map to the item metadata value. diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 7ad0431d7c53a..9084724480c3c 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1263,12 +1263,12 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, - namespace: AttributeNamespace, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::do_set_attribute(origin, collection, maybe_item, key, value, namespace) + Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value) } /// Force-set an attribute for a collection or item. @@ -1294,12 +1294,12 @@ pub mod pallet { set_as: Option, collection: T::CollectionId, maybe_item: Option, + namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, - namespace: AttributeNamespace, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Self::do_force_set_attribute(set_as, collection, maybe_item, key, value, namespace) + Self::do_force_set_attribute(set_as, collection, maybe_item, namespace, key, value) } /// Clear an attribute for a collection or item. @@ -1322,13 +1322,13 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, maybe_item: Option, - key: BoundedVec, namespace: AttributeNamespace, + key: BoundedVec, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key, namespace) + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, namespace, key) } /// Approve item's attributes to be changed by a delegated third-party account. diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 51bd55dc29168..e0d3798e0b7b3 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -604,25 +604,25 @@ fn set_attribute_should_work() { RuntimeOrigin::signed(1), 0, None, + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), 0, Some(0), + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), 0, Some(0), + AttributeNamespace::CollectionOwner, bvec![1], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_eq!( attributes(0), @@ -638,9 +638,9 @@ fn set_attribute_should_work() { RuntimeOrigin::signed(1), 0, None, + AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10], - AttributeNamespace::CollectionOwner, )); assert_eq!( attributes(0), @@ -656,8 +656,8 @@ fn set_attribute_should_work() { RuntimeOrigin::signed(1), 0, Some(0), - bvec![1], AttributeNamespace::CollectionOwner, + bvec![1], )); assert_eq!( attributes(0), @@ -689,25 +689,25 @@ fn set_attribute_should_respect_lock() { RuntimeOrigin::signed(1), 0, None, + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), 0, Some(0), + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), 0, Some(1), + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, )); assert_eq!( attributes(0), @@ -732,9 +732,9 @@ fn set_attribute_should_respect_lock() { RuntimeOrigin::signed(1), 0, None, + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, ), e ); @@ -742,9 +742,9 @@ fn set_attribute_should_respect_lock() { RuntimeOrigin::signed(1), 0, Some(0), + AttributeNamespace::CollectionOwner, bvec![0], bvec![1], - AttributeNamespace::CollectionOwner, )); assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); @@ -754,9 +754,9 @@ fn set_attribute_should_respect_lock() { RuntimeOrigin::signed(1), 0, Some(0), + AttributeNamespace::CollectionOwner, bvec![0], bvec![1], - AttributeNamespace::CollectionOwner, ), e ); @@ -764,9 +764,9 @@ fn set_attribute_should_respect_lock() { RuntimeOrigin::signed(1), 0, Some(1), + AttributeNamespace::CollectionOwner, bvec![0], bvec![1], - AttributeNamespace::CollectionOwner, )); }); } @@ -1997,9 +1997,9 @@ fn pallet_level_feature_flags_should_work() { RuntimeOrigin::signed(user_id), collection_id, None, + AttributeNamespace::CollectionOwner, bvec![0], bvec![0], - AttributeNamespace::CollectionOwner, ), Error::::MethodDisabled ); diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 86c7742ed54e6..cd091791821ed 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -47,8 +47,8 @@ pub trait Inspect { /// By default this is `None`; no attributes are defined. fn attribute( _item: &Self::ItemId, - _key: &[u8], _namespace: &AttributeNamespace, + _key: &[u8], ) -> Option> { None } @@ -58,10 +58,10 @@ pub trait Inspect { /// By default this just attempts to use `attribute`. fn typed_attribute( item: &Self::ItemId, - key: &K, namespace: &AttributeNamespace, + key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(item, d, namespace)) + key.using_encoded(|d| Self::attribute(item, namespace, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -150,17 +150,17 @@ impl< } fn attribute( item: &Self::ItemId, - key: &[u8], namespace: &AttributeNamespace, + key: &[u8], ) -> Option> { - >::attribute(&A::get(), item, key, namespace) + >::attribute(&A::get(), item, namespace, key) } fn typed_attribute( item: &Self::ItemId, - key: &K, namespace: &AttributeNamespace, + key: &K, ) -> Option { - >::typed_attribute(&A::get(), item, key, namespace) + >::typed_attribute(&A::get(), item, namespace, key) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index ae87d6ccda41f..5b93ca832d4f1 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -61,8 +61,8 @@ pub trait Inspect { fn attribute( _collection: &Self::CollectionId, _item: &Self::ItemId, - _key: &[u8], _namespace: &AttributeNamespace, + _key: &[u8], ) -> Option> { None } @@ -74,10 +74,10 @@ pub trait Inspect { fn typed_attribute( collection: &Self::CollectionId, item: &Self::ItemId, - key: &K, namespace: &AttributeNamespace, + key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(collection, item, d, namespace)) + key.using_encoded(|d| Self::attribute(collection, item, namespace, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } From 3b1f69d3f3fcc80f5a012d91eab66577bac7136f Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 14:30:28 +0200 Subject: [PATCH 83/94] Chore --- frame/nfts/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index fa98c36ba7d87..5e1b0237ca3ec 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -424,7 +424,7 @@ benchmarks_instance_pallet! { let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); for i in 0..n { let mut key = vec![0u8; T::KeyLimit::get() as usize]; - let mut s = Vec::from((i as u16).to_string().as_bytes()); + let mut s = Vec::from((i as u16).to_be_bytes()); key.truncate(s.len()); key.append(&mut s); From 9cad6fbd9a131d6fa5dde7f42210a57282540c9c Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:31:08 +0200 Subject: [PATCH 84/94] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 9084724480c3c..14d021aeaa863 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1275,7 +1275,7 @@ pub mod pallet { /// /// Origin must be `ForceOrigin`. /// - /// If the attribute already exist and it was set by another account, the deposit + /// If the attribute already exists and it was set by another account, the deposit /// will be returned to the previous owner. /// /// - `set_as`: An optional owner if the attribute. From be11b17b315a7273469d1d16fb68d9a8d741f7d7 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:31:19 +0200 Subject: [PATCH 85/94] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 14d021aeaa863..66deeb2e0132d 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1278,7 +1278,7 @@ pub mod pallet { /// If the attribute already exists and it was set by another account, the deposit /// will be returned to the previous owner. /// - /// - `set_as`: An optional owner if the attribute. + /// - `set_as`: An optional owner of the attribute. /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `key`: The key of the attribute. From ff3daf04fdd8d183bc1190ade48d876562021c47 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 14:33:35 +0200 Subject: [PATCH 86/94] Update docs --- frame/nfts/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 66deeb2e0132d..d09db0fb22280 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1251,9 +1251,9 @@ pub mod pallet { /// /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. - /// - `namespace`: Attribute's namespace. /// /// Emits `AttributeSet`. /// @@ -1281,9 +1281,9 @@ pub mod pallet { /// - `set_as`: An optional owner of the attribute. /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. - /// - `namespace`: Attribute's namespace. /// /// Emits `AttributeSet`. /// @@ -1311,8 +1311,8 @@ pub mod pallet { /// /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `maybe_item`: The identifier of the item whose metadata to clear. - /// - `key`: The key of the attribute. /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. /// /// Emits `AttributeCleared`. /// From 01075b356343a308abea2302b5b0597f1334fc46 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:44:07 +0200 Subject: [PATCH 87/94] Update frame/nfts/src/lib.rs Co-authored-by: Squirrel --- frame/nfts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index d09db0fb22280..165ea161fc505 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1356,7 +1356,7 @@ pub mod pallet { /// /// Origin must be Signed and must be an owner of the `item`. /// - /// - `collection`: A collection of the item. + /// - `collection`: Collection that the item is contained within. /// - `item`: The item that holds attributes. /// - `delegate`: The previously approved account to remove. /// From 3a5cabfef656b5b52cc29ee10be169145224e7e0 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 16:33:33 +0200 Subject: [PATCH 88/94] Add PalletId --- frame/support/src/lib.rs | 4 ++-- frame/support/src/traits/tokens/misc.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 84e416e50544d..a5d7c36de2c6c 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -115,7 +115,7 @@ pub use sp_runtime::{ self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::TypeId; @@ -127,7 +127,7 @@ pub const LOG_TARGET: &str = "runtime::frame-support"; pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. -#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct PalletId(pub [u8; 8]); impl TypeId for PalletId { diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index f9876ef477b81..f0b172841aa84 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -17,6 +17,7 @@ //! Miscellaneous types. +use crate::PalletId; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; @@ -132,7 +133,7 @@ pub enum BalanceStatus { )] pub enum AttributeNamespace { /// An attribute was set by the pallet. - Pallet, + Pallet(PalletId), /// An attribute was set by collection's owner. CollectionOwner, /// An attribute was set by item's owner. From fc5ab5425824be38214ff1cf339e380d36e53470 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 16:41:57 +0200 Subject: [PATCH 89/94] Chore --- frame/nfts/src/features/attributes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index ad820b01d62a6..0d65a1169323b 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -96,8 +96,8 @@ impl, I: 'static> Pallet { // collection's owner. This simplifies the collection's transfer to another owner. let deposit_owner = match namespace { AttributeNamespace::CollectionOwner => { - collection_details.owner_deposit.saturating_reduce(old_deposit.amount); collection_details.owner_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); None }, _ => Some(origin), From 6b5fe82bd11c829001ec898af80b09f8b6150eab Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 18:42:20 +0200 Subject: [PATCH 90/94] Add tests --- frame/nfts/src/tests.rs | 214 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 199 insertions(+), 15 deletions(-) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index e0d3798e0b7b3..1c1f8d9aae414 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -25,6 +25,7 @@ use frame_support::{ traits::{tokens::nonfungibles_v2::Destroy, Currency, Get}, }; use pallet_balances::Error as BalancesError; +use sp_core::bounded::BoundedVec; use sp_std::prelude::*; fn items() -> Vec<(u64, u32, u32)> { @@ -67,11 +68,12 @@ macro_rules! bvec { } } -fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { +fn attributes(collection: u32) -> Vec<(Option, AttributeNamespace, Vec, Vec)> { let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) - .map(|(k, v)| (k.0, k.2.into(), v.0.into())) + .map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into())) .collect(); - s.sort(); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.0); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.2.clone()); s } @@ -589,7 +591,7 @@ fn set_item_metadata_should_work() { } #[test] -fn set_attribute_should_work() { +fn set_collection_owner_attributes_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); @@ -627,9 +629,9 @@ fn set_attribute_should_work() { assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0]), - (Some(0), bvec![0], bvec![0]), - (Some(0), bvec![1], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 10); @@ -645,9 +647,9 @@ fn set_attribute_should_work() { assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0; 10]), - (Some(0), bvec![0], bvec![0]), - (Some(0), bvec![1], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 19); @@ -661,7 +663,10 @@ fn set_attribute_should_work() { )); assert_eq!( attributes(0), - vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + ] ); assert_eq!(Balances::reserved_balance(1), 16); @@ -673,15 +678,194 @@ fn set_attribute_should_work() { } #[test] -fn set_attribute_should_respect_lock() { +fn set_item_owner_attributes_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), 1, collection_config_with_all_settings_enabled() )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + + // can't set for the collection + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + None, + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + // can't set for the non-owned item + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 9); + + // validate an attribute can be updated + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 10], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 18); + + // validate only item's owner (or the root) can remove an attribute + assert_noop!( + Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_eq!(Balances::reserved_balance(2), 15); + + // transfer item + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 0, 3)); + + // validate the attribute are still here & the deposit belongs to the previous owner + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + let key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(2)); + assert_eq!(deposit.amount, 12); + + // on attribute update the deposit should be returned to the previous owner + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 11], + )); + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(3)); + assert_eq!(deposit.amount, 13); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::reserved_balance(3), 13); + + // validate attributes on item deletion + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 0, None)); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 11]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + )); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + )); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::reserved_balance(3), 0); + }); +} + +#[test] +fn set_attribute_should_respect_lock() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled(), + )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, None)); assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, None)); @@ -712,9 +896,9 @@ fn set_attribute_should_respect_lock() { assert_eq!( attributes(0), vec![ - (None, bvec![0], bvec![0]), - (Some(0), bvec![0], bvec![0]), - (Some(1), bvec![0], bvec![0]), + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(1), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), ] ); assert_eq!(Balances::reserved_balance(1), 11); From 99249aebf5dbb290995d8a06a65cad86321f30cd Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Wed, 16 Nov 2022 18:49:59 +0200 Subject: [PATCH 91/94] More tests --- frame/nfts/src/tests.rs | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 1c1f8d9aae414..a7cda09a21726 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -856,6 +856,80 @@ fn set_item_owner_attributes_should_work() { }); } +#[test] +fn set_external_account_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(1), 0, 0, 2)); + + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(1), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::Account(2), bvec![0], bvec![0]), + (Some(0), AttributeNamespace::Account(2), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 6); + + // remove permission to set attributes + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(1), + 0, + 0, + 2, + CancelAttributesApprovalWitness { account_attributes: 2 }, + )); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(2), 0); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + }); +} + #[test] fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { From 7d98c814bb9977fca0b236d69c3de0b468dc0169 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 17 Nov 2022 11:01:24 +0200 Subject: [PATCH 92/94] Add doc --- frame/nfts/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 165ea161fc505..8de9f3103e7c2 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1353,6 +1353,7 @@ pub mod pallet { } /// Cancel the previously provided approval to change item's attributes. + /// All the previously set attributes by the `delegate` will be removed. /// /// Origin must be Signed and must be an owner of the `item`. /// From fae8421636dbcd2495bf2eff8e0e43b2da180699 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 17 Nov 2022 17:45:17 +0200 Subject: [PATCH 93/94] Update errors snapshots --- .../pallet_ui/dev_mode_without_arg_max_encoded_len.stderr | 2 +- .../storage_ensure_span_are_ok_on_wrong_gen.stderr | 6 +++--- .../storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +++--- .../test/tests/pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../tests/pallet_ui/storage_info_unsatisfied_nmap.stderr | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index a5ec31a9bb4e7..bb49e11679028 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 42ef5a34e4c30..999d8585c221a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 461d63ebb0d9c..e2870ffb9e86f 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index cce9fa70b3da5..d5b0c3b50a5ac 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 877485dda2084..6b174d13c5778 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 80 others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From 673702427c82a6642f002c1424f64dc8d88c9335 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 18 Nov 2022 09:48:08 +0200 Subject: [PATCH 94/94] Ensure we track the owner_deposit field correctly --- frame/nfts/src/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index a7cda09a21726..1e057a8b58d6d 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -635,6 +635,7 @@ fn set_collection_owner_attributes_should_work() { ] ); assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 9); assert_ok!(Nfts::set_attribute( RuntimeOrigin::signed(1), @@ -653,6 +654,7 @@ fn set_collection_owner_attributes_should_work() { ] ); assert_eq!(Balances::reserved_balance(1), 19); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 18); assert_ok!(Nfts::clear_attribute( RuntimeOrigin::signed(1),