git.delta.rocks / unique-network / refs/commits / 396d6c0fcaf0

difftreelog

CORE-37 Metadata update permissions

str-mv2021-07-14parent: #29c61bb.patch.diff
in: master

7 files changed

modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -196,6 +196,7 @@
 					const_on_chain_schema: vec![],
 					variable_on_chain_schema: vec![],
 					limits: CollectionLimits::default(),
+					meta_update_permission: MetaUpdatePermission::ItemOwner,
 				},
 			)],
 			nft_item_id: vec![],
modifiedpallets/inflation/src/tests.rsdiffbeforeafterboth
31 type ExistentialDeposit = ExistentialDeposit;31 type ExistentialDeposit = ExistentialDeposit;
32 type WeightInfo = ();32 type WeightInfo = ();
33 type MaxLocks = MaxLocks;33 type MaxLocks = MaxLocks;
34 type MaxReserves = ();
35 type ReserveIdentifier = [u8; 8];
34}36}
3537
36frame_support::construct_runtime!(38frame_support::construct_runtime!(
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -41,7 +41,7 @@
 	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,
 	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits, CollectionId,
 	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,
-	FungibleItemType, ReFungibleItemType,
+	FungibleItemType, ReFungibleItemType, MetaUpdatePermission,
 };
 use pallet_ethereum::EthereumTransactionSender;
 
@@ -175,6 +175,10 @@
 		BadCreateRefungibleCall,
 		/// Gas limit exceeded
 		OutOfGas,
+		/// Metadata update denied by collection settings 
+		MetadataUpdateDenied,
+		/// Metadata update flag become unmutable with None option
+		MetadataFlagFrozen,
 	}
 }
 
@@ -533,6 +537,7 @@
 				variable_on_chain_schema: Vec::new(),
 				const_on_chain_schema: Vec::new(),
 				limits,
+				meta_update_permission: MetaUpdatePermission::default(),
 			};
 
 			// Add new collection to map
@@ -922,6 +927,37 @@
 			Ok(())
 		}
 
+		// TODO! transaction weight
+		/// Set meta_update_permission value for particular collection
+		///
+		/// # Permissions
+		///
+		/// * Collection Owner.
+		///
+		/// # Arguments
+		///
+		/// * collection_id: ID of the collection.
+		///
+		/// * value: New flag value.
+		#[weight = <T as Config>::WeightInfo::burn_item()]
+		#[transactional]
+		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {
+
+			let sender = ensure_signed(origin)?;
+			let mut target_collection = Self::get_collection(collection_id)?;
+
+			ensure!(
+				target_collection.meta_update_permission != MetaUpdatePermission::None,
+				Error::<T>::MetadataFlagFrozen
+			);
+			Self::check_owner_permissions(&target_collection, &sender)?;
+			
+			target_collection.meta_update_permission = value;
+			Self::save_collection(target_collection);
+
+			Ok(())
+		}
+
 		/// Destroys a concrete instance of NFT.
 		///
 		/// # Permissions
@@ -1481,11 +1517,7 @@
 		);
 
 		// Modify permissions check
-		ensure!(
-			Self::is_item_owner(sender, collection, item_id)
-				|| Self::is_owner_or_admin_permissions(collection, sender),
-			Error::<T>::NoPermission
-		);
+		Self::meta_update_check(sender, collection, item_id)?;
 
 		match collection.mode {
 			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,
@@ -1499,6 +1531,32 @@
 		Ok(())
 	}
 
+	pub fn meta_update_check(		
+		sender: &T::CrossAccountId,
+		collection: &CollectionHandle<T>,
+		item_id: TokenId) -> DispatchResult {
+
+		match collection.meta_update_permission {
+			MetaUpdatePermission::ItemOwner => {
+				ensure!(
+					Self::is_item_owner(sender, collection, item_id), 
+					Error::<T>::NoPermission
+				);
+			},
+			MetaUpdatePermission::Admin => {
+				ensure!(
+					Self::is_owner_or_admin_permissions(collection, sender), 
+					Error::<T>::NoPermission
+				);
+			},
+			MetaUpdatePermission::None => {
+				fail!(Error::<T>::MetadataUpdateDenied);
+			},
+		}
+
+		Ok(())
+	}
+
 	pub fn create_multiple_items_internal(
 		sender: &T::CrossAccountId,
 		collection: &CollectionHandle<T>,
modifiedpallets/nft/src/mock.rsdiffbeforeafterboth
--- a/pallets/nft/src/mock.rs
+++ b/pallets/nft/src/mock.rs
@@ -74,6 +74,8 @@
 	type ExistentialDeposit = ExistentialDeposit;
 	type WeightInfo = ();
 	type MaxLocks = MaxLocks;
+	type MaxReserves = ();
+	type ReserveIdentifier = [u8; 8];
 }
 
 parameter_types! {
@@ -99,41 +101,6 @@
 
 type Timestamp = pallet_timestamp::Pallet<Test>;
 type Randomness = pallet_randomness_collective_flip::Pallet<Test>;
-
-parameter_types! {
-	pub const TombstoneDeposit: u64 = 1;
-	pub const DepositPerContract: u64 = 1;
-	pub const DepositPerStorageByte: u64 = 1;
-	pub const DepositPerStorageItem: u64 = 1;
-	pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * 24 * 60 * 10);
-	pub const SurchargeReward: u64 = 1;
-	pub const SignedClaimHandicap: u32 = 2;
-	pub DeletionWeightLimit: u64 = u64::MAX;//Perbill::from_percent(10);
-	pub DeletionQueueDepth: u32 = 10;
-	pub Schedule: pallet_contracts::Schedule<Test> = Default::default();
-}
-
-impl pallet_contracts::Config for Test {
-	type Time = Timestamp;
-	type Randomness = Randomness;
-	type Currency = pallet_balances::Pallet<Test>;
-	type Event = ();
-	type RentPayment = ();
-	type SignedClaimHandicap = SignedClaimHandicap;
-	type TombstoneDeposit = TombstoneDeposit;
-	type DepositPerContract = DepositPerContract;
-	type DepositPerStorageByte = DepositPerStorageByte;
-	type DepositPerStorageItem = DepositPerStorageItem;
-	type RentFraction = RentFraction;
-	type SurchargeReward = SurchargeReward;
-	type DeletionWeightLimit = DeletionWeightLimit;
-	type DeletionQueueDepth = DeletionQueueDepth;
-	type ChainExtension = ();
-	type WeightPrice = ();
-	type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
-	type Schedule = Schedule;
-	type CallStack = [pallet_contracts::Frame<Self>; 31];
-}
 
 parameter_types! {
 	pub const CollectionCreationPrice: u32 = 0;
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -2351,3 +2351,200 @@
 		);
 	});
 }
+
+#[test]
+fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+
+		let origin1 = Origin::signed(1);
+
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
+
+		TemplateModule::set_meta_update_permission_flag(origin1.clone(), collection_id, MetaUpdatePermission::ItemOwner);
+
+		let variable_data = b"test set_variable_meta_data method.".to_vec();
+		assert_ok!(TemplateModule::set_variable_meta_data(
+			origin1,
+			collection_id,
+			1,
+			variable_data.clone()
+		));
+
+		assert_eq!(
+			TemplateModule::nft_item_id(collection_id, 1)
+				.unwrap()
+				.variable_data,
+			variable_data
+		);
+	});
+}
+
+#[test]
+fn set_variable_meta_data_on_nft_with_item_owner_permission_flag_neg() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+		let origin1 = Origin::signed(1);
+		let origin2 = Origin::signed(2);
+
+		assert_ok!(TemplateModule::set_mint_permission(
+			origin2.clone(),
+			collection_id,
+			true
+		));
+		assert_ok!(TemplateModule::add_to_white_list(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
+
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
+
+		TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::ItemOwner);
+
+		let variable_data = b"test set_variable_meta_data method.".to_vec();
+		assert_noop!(TemplateModule::set_variable_meta_data(
+			origin2,
+			collection_id,
+			1,
+			variable_data.clone()),
+		Error::<Test>::NoPermission
+		);
+	});
+}
+
+#[test]
+fn set_variable_meta_data_on_nft_with_admin_flag() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+		let origin1 = Origin::signed(1);
+		let origin2 = Origin::signed(2);
+
+		assert_ok!(TemplateModule::set_mint_permission(
+			origin2.clone(),
+			collection_id,
+			true
+		));
+		assert_ok!(TemplateModule::add_to_white_list(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
+
+		assert_ok!(TemplateModule::add_collection_admin(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
+
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
+
+		TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::Admin);
+
+		let variable_data = b"test set_variable_meta_data method.".to_vec();
+		assert_ok!(TemplateModule::set_variable_meta_data(
+			origin1,
+			collection_id,
+			1,
+			variable_data.clone()
+		));
+
+		assert_eq!(
+			TemplateModule::nft_item_id(collection_id, 1)
+				.unwrap()
+				.variable_data,
+			variable_data
+		);
+	});
+}
+
+#[test]
+fn set_variable_meta_data_on_nft_with_admin_flag_neg() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+		let origin1 = Origin::signed(1);
+		let origin2 = Origin::signed(2);
+
+		assert_ok!(TemplateModule::set_mint_permission(
+			origin2.clone(),
+			collection_id,
+			true
+		));
+		assert_ok!(TemplateModule::add_to_white_list(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
+
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
+
+		TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::Admin);
+
+		let variable_data = b"test set_variable_meta_data method.".to_vec();
+		assert_noop!(TemplateModule::set_variable_meta_data(
+			origin1,
+			collection_id,
+			1,
+			variable_data.clone()),
+		Error::<Test>::NoPermission
+		);
+	});
+}
+
+#[test]
+fn set_variable_meta_flag_after_freeze() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+		let origin2 = Origin::signed(2);
+
+		TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::None);
+		assert_noop!(TemplateModule::set_meta_update_permission_flag(
+			origin2.clone(),
+			collection_id,
+			MetaUpdatePermission::Admin),
+		Error::<Test>::MetadataFlagFrozen
+		);
+	});
+}
+
+#[test]
+fn set_variable_meta_data_on_nft_with_none_flag_neg() {
+	new_test_ext().execute_with(|| {
+		default_limits();
+
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);
+		let origin1 = Origin::signed(1);
+
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
+
+		TemplateModule::set_meta_update_permission_flag(origin1.clone(), collection_id, MetaUpdatePermission::None);
+
+		let variable_data = b"test set_variable_meta_data method.".to_vec();
+		assert_noop!(TemplateModule::set_variable_meta_data(
+			origin1.clone(),
+			collection_id,
+			1,
+			variable_data.clone()),
+		Error::<Test>::MetadataUpdateDenied
+		);
+	});
+}
\ No newline at end of file
modifiedpallets/scheduler/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -947,900 +947,4 @@
 	fn root() -> OriginCaller {
 		system::RawOrigin::Root.into()
 	}
-
-	#[test]
-	fn basic_scheduling_works() {
-		new_test_ext().execute_with(|| {
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				call
-			));
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn schedule_after_works() {
-		new_test_ext().execute_with(|| {
-			run_to_block(2);
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			// This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::After(3),
-				None,
-				127,
-				root(),
-				call
-			));
-			run_to_block(5);
-			assert!(logger::log().is_empty());
-			run_to_block(6);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn schedule_after_zero_works() {
-		new_test_ext().execute_with(|| {
-			run_to_block(2);
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::After(0),
-				None,
-				127,
-				root(),
-				call
-			));
-			// Will trigger on the next block.
-			run_to_block(3);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn periodic_scheduling_works() {
-		new_test_ext().execute_with(|| {
-			// at #4, every 3 blocks, 3 times.
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				Some((3, 3)),
-				127,
-				root(),
-				Call::Logger(logger::Call::log(42, 1000))
-			));
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(6);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(7);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
-			run_to_block(9);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
-			run_to_block(10);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]
-			);
-			run_to_block(100);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]
-			);
-		});
-	}
-
-	#[test]
-	fn reschedule_works() {
-		new_test_ext().execute_with(|| {
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			assert_eq!(
-				Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(),
-				(4, 0)
-			);
-
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-
-			assert_eq!(
-				Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(),
-				(6, 0)
-			);
-
-			assert_noop!(
-				Scheduler::do_reschedule((6, 0), DispatchTime::At(6)),
-				Error::<Test>::RescheduleNoChange
-			);
-
-			run_to_block(4);
-			assert!(logger::log().is_empty());
-
-			run_to_block(6);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn reschedule_named_works() {
-		new_test_ext().execute_with(|| {
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			assert_eq!(
-				Scheduler::do_schedule_named(
-					1u32.encode(),
-					DispatchTime::At(4),
-					None,
-					127,
-					root(),
-					call
-				)
-				.unwrap(),
-				(4, 0)
-			);
-
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-
-			assert_eq!(
-				Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(),
-				(6, 0)
-			);
-
-			assert_noop!(
-				Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)),
-				Error::<Test>::RescheduleNoChange
-			);
-
-			run_to_block(4);
-			assert!(logger::log().is_empty());
-
-			run_to_block(6);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn reschedule_named_perodic_works() {
-		new_test_ext().execute_with(|| {
-			let call = Call::Logger(logger::Call::log(42, 1000));
-			assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(
-				&call
-			));
-			assert_eq!(
-				Scheduler::do_schedule_named(
-					1u32.encode(),
-					DispatchTime::At(4),
-					Some((3, 3)),
-					127,
-					root(),
-					call
-				)
-				.unwrap(),
-				(4, 0)
-			);
-
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-
-			assert_eq!(
-				Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(),
-				(5, 0)
-			);
-			assert_eq!(
-				Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(),
-				(6, 0)
-			);
-
-			run_to_block(5);
-			assert!(logger::log().is_empty());
-
-			run_to_block(6);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-
-			assert_eq!(
-				Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(),
-				(10, 0)
-			);
-
-			run_to_block(9);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-
-			run_to_block(10);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
-
-			run_to_block(13);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]
-			);
-
-			run_to_block(100);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]
-			);
-		});
-	}
-
-	#[test]
-	fn cancel_named_scheduling_works_with_normal_cancel() {
-		new_test_ext().execute_with(|| {
-			// at #4.
-			Scheduler::do_schedule_named(
-				1u32.encode(),
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, 1000)),
-			)
-			.unwrap();
-			let i = Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(42, 1000)),
-			)
-			.unwrap();
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-			assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));
-			assert_ok!(Scheduler::do_cancel(None, i));
-			run_to_block(100);
-			assert!(logger::log().is_empty());
-		});
-	}
-
-	#[test]
-	fn cancel_named_periodic_scheduling_works() {
-		new_test_ext().execute_with(|| {
-			// at #4, every 3 blocks, 3 times.
-			Scheduler::do_schedule_named(
-				1u32.encode(),
-				DispatchTime::At(4),
-				Some((3, 3)),
-				127,
-				root(),
-				Call::Logger(logger::Call::log(42, 1000)),
-			)
-			.unwrap();
-			// same id results in error.
-			assert!(Scheduler::do_schedule_named(
-				1u32.encode(),
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, 1000))
-			)
-			.is_err());
-			// different id is ok.
-			Scheduler::do_schedule_named(
-				2u32.encode(),
-				DispatchTime::At(8),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, 1000)),
-			)
-			.unwrap();
-			run_to_block(3);
-			assert!(logger::log().is_empty());
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(6);
-			assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));
-			run_to_block(100);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
-		});
-	}
-
-	#[test]
-	fn scheduler_respects_weight_limits() {
-		new_test_ext().execute_with(|| {
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
-			));
-			// 69 and 42 do not fit together
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 42u32)]);
-			run_to_block(5);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
-		});
-	}
-
-	#[test]
-	fn scheduler_respects_hard_deadlines_more() {
-		new_test_ext().execute_with(|| {
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				0,
-				root(),
-				Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				0,
-				root(),
-				Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
-			));
-			// With base weights, 69 and 42 should not fit together, but do because of hard deadlines
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
-		});
-	}
-
-	#[test]
-	fn scheduler_respects_priority_ordering() {
-		new_test_ext().execute_with(|| {
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				1,
-				root(),
-				Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				0,
-				root(),
-				Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
-			));
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]);
-		});
-	}
-
-	#[test]
-	fn scheduler_respects_priority_ordering_with_soft_deadlines() {
-		new_test_ext().execute_with(|| {
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				255,
-				root(),
-				Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
-			));
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(4),
-				None,
-				126,
-				root(),
-				Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))
-			));
-
-			// 2600 does not fit with 69 or 42, but has higher priority, so will go through
-			run_to_block(4);
-			assert_eq!(logger::log(), vec![(root(), 2600u32)]);
-			// 69 and 42 fit together
-			run_to_block(5);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]
-			);
-		});
-	}
-
-	#[test]
-	fn on_initialize_weight_is_correct() {
-		new_test_ext().execute_with(|| {
-			let base_weight: Weight =
-				<Test as frame_system::Config>::DbWeight::get().reads_writes(1, 2);
-			let base_multiplier = 0;
-			let named_multiplier = <Test as frame_system::Config>::DbWeight::get().writes(1);
-			let periodic_multiplier =
-				<Test as frame_system::Config>::DbWeight::get().reads_writes(1, 1);
-
-			// Named
-			assert_ok!(Scheduler::do_schedule_named(
-				1u32.encode(),
-				DispatchTime::At(1),
-				None,
-				255,
-				root(),
-				Call::Logger(logger::Call::log(3, MaximumSchedulerWeight::get() / 3))
-			));
-			// Anon Periodic
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(1),
-				Some((1000, 3)),
-				128,
-				root(),
-				Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))
-			));
-			// Anon
-			assert_ok!(Scheduler::do_schedule(
-				DispatchTime::At(1),
-				None,
-				127,
-				root(),
-				Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
-			));
-			// Named Periodic
-			assert_ok!(Scheduler::do_schedule_named(
-				2u32.encode(),
-				DispatchTime::At(1),
-				Some((1000, 3)),
-				126,
-				root(),
-				Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))
-			));
-
-			// Will include the named periodic only
-			let actual_weight = Scheduler::on_initialize(1);
-			let call_weight = MaximumSchedulerWeight::get() / 2;
-			assert_eq!(
-				actual_weight,
-				call_weight
-					+ base_weight + base_multiplier
-					+ named_multiplier + periodic_multiplier
-			);
-			assert_eq!(logger::log(), vec![(root(), 2600u32)]);
-
-			// Will include anon and anon periodic
-			let actual_weight = Scheduler::on_initialize(2);
-			let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3;
-			assert_eq!(
-				actual_weight,
-				call_weight + base_weight + base_multiplier * 2 + periodic_multiplier
-			);
-			assert_eq!(
-				logger::log(),
-				vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]
-			);
-
-			// Will include named only
-			let actual_weight = Scheduler::on_initialize(3);
-			let call_weight = MaximumSchedulerWeight::get() / 3;
-			assert_eq!(
-				actual_weight,
-				call_weight + base_weight + base_multiplier + named_multiplier
-			);
-			assert_eq!(
-				logger::log(),
-				vec![
-					(root(), 2600u32),
-					(root(), 69u32),
-					(root(), 42u32),
-					(root(), 3u32)
-				]
-			);
-
-			// Will contain none
-			let actual_weight = Scheduler::on_initialize(4);
-			assert_eq!(actual_weight, 0);
-		});
-	}
-
-	#[test]
-	fn root_calls_works() {
-		new_test_ext().execute_with(|| {
-			let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
-			let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
-			assert_ok!(Scheduler::schedule_named(
-				Origin::root(),
-				1u32.encode(),
-				4,
-				None,
-				127,
-				call
-			));
-			assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2));
-			run_to_block(3);
-			// Scheduled calls are in the agenda.
-			assert_eq!(Agenda::<Test>::get(4).len(), 2);
-			assert!(logger::log().is_empty());
-			assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode()));
-			assert_ok!(Scheduler::cancel(Origin::root(), 4, 1));
-			// Scheduled calls are made NONE, so should not effect state
-			run_to_block(100);
-			assert!(logger::log().is_empty());
-		});
-	}
-
-	#[test]
-	fn fails_to_schedule_task_in_the_past() {
-		new_test_ext().execute_with(|| {
-			run_to_block(3);
-
-			let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
-			let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
-
-			assert_err!(
-				Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call),
-				Error::<Test>::TargetBlockNumberInPast,
-			);
-
-			assert_err!(
-				Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()),
-				Error::<Test>::TargetBlockNumberInPast,
-			);
-
-			assert_err!(
-				Scheduler::schedule(Origin::root(), 3, None, 127, call2),
-				Error::<Test>::TargetBlockNumberInPast,
-			);
-		});
-	}
-
-	#[test]
-	fn should_use_orign() {
-		new_test_ext().execute_with(|| {
-			let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
-			let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
-			assert_ok!(Scheduler::schedule_named(
-				system::RawOrigin::Signed(1).into(),
-				1u32.encode(),
-				4,
-				None,
-				127,
-				call
-			));
-			assert_ok!(Scheduler::schedule(
-				system::RawOrigin::Signed(1).into(),
-				4,
-				None,
-				127,
-				call2
-			));
-			run_to_block(3);
-			// Scheduled calls are in the agenda.
-			assert_eq!(Agenda::<Test>::get(4).len(), 2);
-			assert!(logger::log().is_empty());
-			assert_ok!(Scheduler::cancel_named(
-				system::RawOrigin::Signed(1).into(),
-				1u32.encode()
-			));
-			assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1));
-			// Scheduled calls are made NONE, so should not effect state
-			run_to_block(100);
-			assert!(logger::log().is_empty());
-		});
-	}
-
-	#[test]
-	fn should_check_orign() {
-		new_test_ext().execute_with(|| {
-			let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
-			let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
-			assert_noop!(
-				Scheduler::schedule_named(
-					system::RawOrigin::Signed(2).into(),
-					1u32.encode(),
-					4,
-					None,
-					127,
-					call
-				),
-				BadOrigin
-			);
-			assert_noop!(
-				Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2),
-				BadOrigin
-			);
-		});
-	}
-
-	#[test]
-	fn should_check_orign_for_cancel() {
-		new_test_ext().execute_with(|| {
-			let call = Box::new(Call::Logger(logger::Call::log_without_filter(69, 1000)));
-			let call2 = Box::new(Call::Logger(logger::Call::log_without_filter(42, 1000)));
-			assert_ok!(Scheduler::schedule_named(
-				system::RawOrigin::Signed(1).into(),
-				1u32.encode(),
-				4,
-				None,
-				127,
-				call
-			));
-			assert_ok!(Scheduler::schedule(
-				system::RawOrigin::Signed(1).into(),
-				4,
-				None,
-				127,
-				call2
-			));
-			run_to_block(3);
-			// Scheduled calls are in the agenda.
-			assert_eq!(Agenda::<Test>::get(4).len(), 2);
-			assert!(logger::log().is_empty());
-			assert_noop!(
-				Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()),
-				BadOrigin
-			);
-			assert_noop!(
-				Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1),
-				BadOrigin
-			);
-			assert_noop!(
-				Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()),
-				BadOrigin
-			);
-			assert_noop!(
-				Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1),
-				BadOrigin
-			);
-			run_to_block(5);
-			assert_eq!(
-				logger::log(),
-				vec![
-					(system::RawOrigin::Signed(1).into(), 69u32),
-					(system::RawOrigin::Signed(1).into(), 42u32)
-				]
-			);
-		});
-	}
-
-	#[test]
-	fn migration_to_v2_works() {
-		new_test_ext().execute_with(|| {
-			for i in 0..3u64 {
-				let k = i.twox_64_concat();
-				let old = vec![
-					Some(ScheduledV1 {
-						maybe_id: None,
-						priority: i as u8 + 10,
-						call: Call::Logger(logger::Call::log(96, 100)),
-						maybe_periodic: None,
-					}),
-					None,
-					Some(ScheduledV1 {
-						maybe_id: Some(b"test".to_vec()),
-						priority: 123,
-						call: Call::Logger(logger::Call::log(69, 1000)),
-						maybe_periodic: Some((456u64, 10)),
-					}),
-				];
-				frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
-			}
-
-			assert_eq!(StorageVersion::get(), Releases::V1);
-
-			assert!(Scheduler::migrate_v1_to_t2());
-
-			assert_eq_uvec!(
-				Agenda::<Test>::iter().collect::<Vec<_>>(),
-				vec![
-					(
-						0,
-						vec![
-							Some(ScheduledV2 {
-								maybe_id: None,
-								priority: 10,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					),
-					(
-						1,
-						vec![
-							Some(ScheduledV2 {
-								maybe_id: None,
-								priority: 11,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					),
-					(
-						2,
-						vec![
-							Some(ScheduledV2 {
-								maybe_id: None,
-								priority: 12,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: root(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					)
-				]
-			);
-
-			assert_eq!(StorageVersion::get(), Releases::V2);
-		});
-	}
-
-	#[test]
-	fn test_migrate_origin() {
-		new_test_ext().execute_with(|| {
-			for i in 0..3u64 {
-				let k = i.twox_64_concat();
-				let old: Vec<Option<Scheduled<_, _, u32, u64>>> = vec![
-					Some(Scheduled {
-						maybe_id: None,
-						priority: i as u8 + 10,
-						call: Call::Logger(logger::Call::log(96, 100)),
-						origin: 3u32,
-						maybe_periodic: None,
-						_phantom: Default::default(),
-					}),
-					None,
-					Some(Scheduled {
-						maybe_id: Some(b"test".to_vec()),
-						priority: 123,
-						origin: 2u32,
-						call: Call::Logger(logger::Call::log(69, 1000)),
-						maybe_periodic: Some((456u64, 10)),
-						_phantom: Default::default(),
-					}),
-				];
-				frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
-			}
-
-			impl From<u32> for OriginCaller {
-				fn from(value: u32) -> Self {
-					match value {
-						3 => system::RawOrigin::Root.into(),
-						2 => system::RawOrigin::None.into(),
-						_ => unimplemented!(),
-					}
-				}
-			}
-
-			Scheduler::migrate_origin::<u32>();
-
-			assert_eq_uvec!(
-				Agenda::<Test>::iter().collect::<Vec<_>>(),
-				vec![
-					(
-						0,
-						vec![
-							Some(ScheduledV2::<_, _, OriginCaller, u64> {
-								maybe_id: None,
-								priority: 10,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: system::RawOrigin::Root.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: system::RawOrigin::None.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					),
-					(
-						1,
-						vec![
-							Some(ScheduledV2 {
-								maybe_id: None,
-								priority: 11,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: system::RawOrigin::Root.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: system::RawOrigin::None.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					),
-					(
-						2,
-						vec![
-							Some(ScheduledV2 {
-								maybe_id: None,
-								priority: 12,
-								call: Call::Logger(logger::Call::log(96, 100)),
-								maybe_periodic: None,
-								origin: system::RawOrigin::Root.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-							None,
-							Some(ScheduledV2 {
-								maybe_id: Some(b"test".to_vec()),
-								priority: 123,
-								call: Call::Logger(logger::Call::log(69, 1000)),
-								maybe_periodic: Some((456u64, 10)),
-								origin: system::RawOrigin::None.into(),
-								_phantom: PhantomData::<u64>::default(),
-							}),
-						]
-					)
-				]
-			);
-		});
-	}
 }
modifiedprimitives/nft/src/lib.rsdiffbeforeafterboth
--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -144,6 +144,7 @@
 	pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions
 	pub variable_on_chain_schema: Vec<u8>,        //
 	pub const_on_chain_schema: Vec<u8>,           //
+	pub meta_update_permission: MetaUpdatePermission,
 }
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq)]
@@ -241,6 +242,20 @@
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum MetaUpdatePermission  {
+	ItemOwner,
+	Admin,
+	None,
+}
+
+impl Default for MetaUpdatePermission {
+	fn default() -> Self {
+		Self::ItemOwner
+	}
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 pub enum CreateItemData {
 	NFT(CreateNftData),
 	Fungible(CreateFungibleData),