git.delta.rocks / unique-network / refs/commits / 51e66acd38a2

difftreelog

Merge branch 'develop' into feature/CORE-189

Igor Kozyrev2021-09-09parents: #2e92a20 #e873ca1.patch.diff
in: master

9 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,
 					transfers_enabled: true,
 				},
 			)],
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -43,7 +43,7 @@
 	OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,
 	MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,
 	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,
-	FungibleItemType, ReFungibleItemType,
+	MetaUpdatePermission, FungibleItemType, ReFungibleItemType,
 };
 
 #[cfg(test)]
@@ -145,6 +145,10 @@
 		BadCreateRefungibleCall,
 		/// Gas limit exceeded
 		OutOfGas,
+		/// Metadata update denied by collection settings
+		MetadataUpdateDenied,
+		/// Metadata update flag become unmutable with None option
+		MetadataFlagFrozen,
 		/// Collection settings not allowing items transferring
 		TransferNotAllowed,
 		/// Can't transfer tokens to ethereum zero address
@@ -541,6 +545,7 @@
 				variable_on_chain_schema: Vec::new(),
 				const_on_chain_schema: Vec::new(),
 				limits,
+				meta_update_permission: MetaUpdatePermission::default(),
 				transfers_enabled: true,
 			};
 
@@ -940,6 +945,36 @@
 			target_collection.save()
 		}
 
+		// 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;
+
+			target_collection.save()
+		}
+
 		/// Destroys a concrete instance of NFT.
 		///
 		/// # Permissions
@@ -1485,10 +1520,11 @@
 			Error::<T>::TokenVariableDataLimitExceeded
 		);
 
-		// Modify permissions check
 		ensure!(
-			Self::is_item_owner(sender, collection, item_id)?
-				|| Self::is_owner_or_admin_permissions(collection, sender)?,
+			(Self::is_item_owner(sender, collection, item_id)?
+				&& collection.meta_update_permission == MetaUpdatePermission::ItemOwner)
+				|| (Self::is_owner_or_admin_permissions(collection, sender)?
+					&& collection.meta_update_permission == MetaUpdatePermission::Admin),
 			Error::<T>::NoPermission
 		);
 
@@ -1504,6 +1540,26 @@
 		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 get_variable_metadata(
 		collection: &CollectionHandle<T>,
 		item_id: TokenId,
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -2005,56 +2005,285 @@
 }
 
 #[test]
-fn collection_transfer_flag_works() {
+fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() {
 	new_test_ext().execute_with(|| {
-		let origin1 = Origin::signed(1);
+		//default_limits();
 
 		let collection_id = create_test_collection(&CollectionMode::NFT, 1);
-		assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
 
+		let origin1 = Origin::signed(1);
+
 		let data = default_nft_data();
-		create_test_item(collection_id, &data.into());
-		assert_eq!(TemplateModule::balance_count(1, 1), 1);
-		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+		create_test_item(1, &data.into());
 
-		let origin1 = Origin::signed(1);
+		TemplateModule::set_meta_update_permission_flag(
+			origin1.clone(),
+			collection_id,
+			MetaUpdatePermission::ItemOwner,
+		);
 
-		// default scenario
-		assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
-		assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
-		assert_eq!(TemplateModule::balance_count(1, 1), 0);
-		assert_eq!(TemplateModule::balance_count(1, 2), 1);
+		let variable_data = b"ten chars.".to_vec();
+		assert_ok!(TemplateModule::set_variable_meta_data(
+			origin1,
+			collection_id,
+			1,
+			variable_data.clone()
+		));
 
-		assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
+		assert_eq!(
+			TemplateModule::nft_item_id(collection_id, 1)
+				.unwrap()
+				.variable_data,
+			variable_data
+		);
 	});
 }
 
 #[test]
-fn collection_transfer_flag_works_neg() {
+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);
 
-		let collection_id = create_test_collection(&CollectionMode::NFT, 1);
-		assert_ok!(TemplateModule::set_transfers_enabled_flag(
-			origin1, 1, false
+		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(collection_id, &data.into());
-		assert_eq!(TemplateModule::balance_count(1, 1), 1);
-		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+		create_test_item(1, &data.into());
 
-		let origin1 = Origin::signed(1);
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
+			origin2.clone(),
+			collection_id,
+			MetaUpdatePermission::ItemOwner,
+		));
 
-		// default scenario
+		let variable_data = b"ten chars.++".to_vec();
 		assert_noop!(
-			TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
-			Error::<Test>::TransferNotAllowed
+			TemplateModule::set_variable_meta_data(
+				origin2,
+				collection_id,
+				1,
+				variable_data.clone()
+			),
+			Error::<Test>::TokenVariableDataLimitExceeded
 		);
-		assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
-		assert_eq!(TemplateModule::balance_count(1, 1), 1);
-		assert_eq!(TemplateModule::balance_count(1, 2), 0);
 
-		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+		#[test]
+		fn collection_transfer_flag_works() {
+			new_test_ext().execute_with(|| {
+				let origin1 = Origin::signed(1);
+
+				let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+				assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
+
+				let data = default_nft_data();
+				create_test_item(collection_id, &data.into());
+				assert_eq!(TemplateModule::balance_count(1, 1), 1);
+				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+
+				let origin1 = Origin::signed(1);
+
+				// default scenario
+				assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
+				assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
+				assert_eq!(TemplateModule::balance_count(1, 1), 0);
+				assert_eq!(TemplateModule::balance_count(1, 2), 1);
+
+				assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
+			});
+		}
+
+		#[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());
+
+				assert_ok!(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());
+
+				assert_ok!(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);
+
+				assert_ok!(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());
+
+				assert_ok!(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
+				);
+			});
+		}
+
+		#[test]
+		fn collection_transfer_flag_works_neg() {
+			new_test_ext().execute_with(|| {
+				let origin1 = Origin::signed(1);
+
+				let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+				assert_ok!(TemplateModule::set_transfers_enabled_flag(
+					origin1, 1, false
+				));
+
+				let data = default_nft_data();
+				create_test_item(collection_id, &data.into());
+				assert_eq!(TemplateModule::balance_count(1, 1), 1);
+				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+
+				let origin1 = Origin::signed(1);
+
+				// default scenario
+				assert_noop!(
+					TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
+					Error::<Test>::TransferNotAllowed
+				);
+				assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
+				assert_eq!(TemplateModule::balance_count(1, 1), 1);
+				assert_eq!(TemplateModule::balance_count(1, 2), 0);
+
+				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+			});
+		}
 	});
 }
modifiedprimitives/nft/src/lib.rsdiffbeforeafterboth
before · primitives/nft/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "serde")]4pub use serde::{Serialize, Deserialize};56use sp_runtime::sp_std::prelude::Vec;7use codec::{Decode, Encode, MaxEncodedLen};8pub use frame_support::{9	BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,10	dispatch::DispatchResult,11	ensure, fail, parameter_types,12	traits::{13		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,14		Randomness, IsSubType, WithdrawReasons,15	},16	weights::{17		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},18		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,19		WeightToFeePolynomial, DispatchClass,20	},21	StorageValue, transactional,22};23use derivative::Derivative;2425pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;26pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;27pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;28pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;2930pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {31	10000032} else {33	1034};35pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {36	204837} else {38	1039};40pub const COLLECTION_ADMINS_LIMIT: u64 = 5;41pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {42	100000043} else {44	1045};4647// Timeouts for item types in passed blocks48pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;49pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;50pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5152// Schema limits53pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;54pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;55pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;5657pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;58pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;59pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6061/// How much items can be created per single62/// create_many call63pub const MAX_ITEMS_PER_BATCH: u32 = 200;6465parameter_types! {66	pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;67}6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]75pub enum CollectionMode {76	Invalid,77	NFT,78	// decimal points79	Fungible(DecimalPoints),80	ReFungible,81}8283impl Default for CollectionMode {84	fn default() -> Self {85		Self::Invalid86	}87}8889impl CollectionMode {90	pub fn id(&self) -> u8 {91		match self {92			CollectionMode::Invalid => 0,93			CollectionMode::NFT => 1,94			CollectionMode::Fungible(_) => 2,95			CollectionMode::ReFungible => 3,96		}97	}98}99100pub trait SponsoringResolve<AccountId, Call> {101	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;102}103104#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]105#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]106pub enum AccessMode {107	Normal,108	WhiteList,109}110impl Default for AccessMode {111	fn default() -> Self {112		Self::Normal113	}114}115116#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]118pub enum SchemaVersion {119	ImageURL,120	Unique,121}122impl Default for SchemaVersion {123	fn default() -> Self {124		Self::ImageURL125	}126}127128#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]130pub struct Ownership<AccountId> {131	pub owner: AccountId,132	pub fraction: u128,133}134135#[derive(Encode, Decode, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]137pub enum SponsorshipState<AccountId> {138	/// The fees are applied to the transaction sender139	Disabled,140	Unconfirmed(AccountId),141	/// Transactions are sponsored by specified account142	Confirmed(AccountId),143}144145impl<AccountId> SponsorshipState<AccountId> {146	pub fn sponsor(&self) -> Option<&AccountId> {147		match self {148			Self::Confirmed(sponsor) => Some(sponsor),149			_ => None,150		}151	}152153	pub fn pending_sponsor(&self) -> Option<&AccountId> {154		match self {155			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),156			_ => None,157		}158	}159160	pub fn confirmed(&self) -> bool {161		matches!(self, Self::Confirmed(_))162	}163}164165impl<T> Default for SponsorshipState<T> {166	fn default() -> Self {167		Self::Disabled168	}169}170171#[derive(Encode, Decode, Clone, PartialEq)]172#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]173pub struct Collection<T: frame_system::Config> {174	pub owner: T::AccountId,175	pub mode: CollectionMode,176	pub access: AccessMode,177	pub decimal_points: DecimalPoints,178	pub name: Vec<u16>,        // 64 include null escape char179	pub description: Vec<u16>, // 256 include null escape char180	pub token_prefix: Vec<u8>, // 16 include null escape char181	pub mint_mode: bool,182	pub offchain_schema: Vec<u8>,183	pub schema_version: SchemaVersion,184	pub sponsorship: SponsorshipState<T::AccountId>,185	pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions186	pub variable_on_chain_schema: Vec<u8>,        //187	pub const_on_chain_schema: Vec<u8>,           //188	pub transfers_enabled: bool,189}190191#[derive(Encode, Decode, Debug, Clone, PartialEq)]192#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]193pub struct NftItemType<AccountId> {194	pub owner: AccountId,195	pub const_data: Vec<u8>,196	pub variable_data: Vec<u8>,197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]201pub struct FungibleItemType {202	pub value: u128,203}204205#[derive(Encode, Decode, Debug, Clone, PartialEq)]206#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]207pub struct ReFungibleItemType<AccountId> {208	pub owner: Vec<Ownership<AccountId>>,209	pub const_data: Vec<u8>,210	pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]215pub struct CollectionLimits<BlockNumber: Encode + Decode> {216	pub account_token_ownership_limit: u32,217	pub sponsored_data_size: u32,218	/// None - setVariableMetadata is not sponsored219	/// Some(v) - setVariableMetadata is sponsored220	///           if there is v block between txs221	pub sponsored_data_rate_limit: Option<BlockNumber>,222	pub token_limit: u32,223224	// Timeouts for item types in passed blocks225	pub sponsor_transfer_timeout: u32,226	pub owner_can_transfer: bool,227	pub owner_can_destroy: bool,228}229230impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {231	fn default() -> Self {232		Self {233			account_token_ownership_limit: 10_000_000,234			token_limit: u32::max_value(),235			sponsored_data_size: u32::MAX,236			sponsored_data_rate_limit: None,237			sponsor_transfer_timeout: 14400,238			owner_can_transfer: true,239			owner_can_destroy: true,240		}241	}242}243244/// BoundedVec doesn't supports serde245#[cfg(feature = "serde1")]246mod bounded_serde {247	use core::convert::TryFrom;248	use frame_support::{BoundedVec, traits::Get};249	use serde::{250		ser::{self, Serialize},251		de::{self, Deserialize, Error},252	};253	use sp_std::vec::Vec;254255	pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>256	where257		D: ser::Serializer,258		V: Serialize,259	{260		let vec: &Vec<_> = &value;261		vec.serialize(serializer)262	}263264	pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>265	where266		D: de::Deserializer<'de>,267		V: de::Deserialize<'de>,268		S: Get<u32>,269	{270		// TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?271		let vec = <Vec<V>>::deserialize(deserializer)?;272		let len = vec.len();273		TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))274	}275}276277#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]278#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]279#[derivative(Debug)]280pub struct CreateNftData {281	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]282	#[derivative(Debug = "ignore")]283	pub const_data: BoundedVec<u8, CustomDataLimit>,284	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]285	#[derivative(Debug = "ignore")]286	pub variable_data: BoundedVec<u8, CustomDataLimit>,287}288289#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq)]290#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]291pub struct CreateFungibleData {292	pub value: u128,293}294295#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]296#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]297#[derivative(Debug)]298pub struct CreateReFungibleData {299	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]300	#[derivative(Debug = "ignore")]301	pub const_data: BoundedVec<u8, CustomDataLimit>,302	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]303	#[derivative(Debug = "ignore")]304	pub variable_data: BoundedVec<u8, CustomDataLimit>,305	pub pieces: u128,306}307308#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug)]309#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]310pub enum CreateItemData {311	NFT(CreateNftData),312	Fungible(CreateFungibleData),313	ReFungible(CreateReFungibleData),314}315316impl CreateItemData {317	pub fn data_size(&self) -> usize {318		match self {319			CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),320			CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),321			_ => 0,322		}323	}324}325326impl From<CreateNftData> for CreateItemData {327	fn from(item: CreateNftData) -> Self {328		CreateItemData::NFT(item)329	}330}331332impl From<CreateReFungibleData> for CreateItemData {333	fn from(item: CreateReFungibleData) -> Self {334		CreateItemData::ReFungible(item)335	}336}337338impl From<CreateFungibleData> for CreateItemData {339	fn from(item: CreateFungibleData) -> Self {340		CreateItemData::Fungible(item)341	}342}
after · primitives/nft/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "serde")]4pub use serde::{Serialize, Deserialize};56use sp_runtime::sp_std::prelude::Vec;7use codec::{Decode, Encode, MaxEncodedLen};8pub use frame_support::{9	BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,10	dispatch::DispatchResult,11	ensure, fail, parameter_types,12	traits::{13		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,14		Randomness, IsSubType, WithdrawReasons,15	},16	weights::{17		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},18		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,19		WeightToFeePolynomial, DispatchClass,20	},21	StorageValue, transactional,22};23use derivative::Derivative;2425pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;26pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;27pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;28pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;2930pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {31	10000032} else {33	1034};35pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {36	204837} else {38	1039};40pub const COLLECTION_ADMINS_LIMIT: u64 = 5;41pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {42	100000043} else {44	1045};4647// Timeouts for item types in passed blocks48pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;49pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;50pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5152// Schema limits53pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;54pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;55pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;5657pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;58pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;59pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6061/// How much items can be created per single62/// create_many call63pub const MAX_ITEMS_PER_BATCH: u32 = 200;6465parameter_types! {66	pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;67}6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]75pub enum CollectionMode {76	Invalid,77	NFT,78	// decimal points79	Fungible(DecimalPoints),80	ReFungible,81}8283impl Default for CollectionMode {84	fn default() -> Self {85		Self::Invalid86	}87}8889impl CollectionMode {90	pub fn id(&self) -> u8 {91		match self {92			CollectionMode::Invalid => 0,93			CollectionMode::NFT => 1,94			CollectionMode::Fungible(_) => 2,95			CollectionMode::ReFungible => 3,96		}97	}98}99100pub trait SponsoringResolve<AccountId, Call> {101	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;102}103104#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]105#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]106pub enum AccessMode {107	Normal,108	WhiteList,109}110impl Default for AccessMode {111	fn default() -> Self {112		Self::Normal113	}114}115116#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]118pub enum SchemaVersion {119	ImageURL,120	Unique,121}122impl Default for SchemaVersion {123	fn default() -> Self {124		Self::ImageURL125	}126}127128#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]130pub struct Ownership<AccountId> {131	pub owner: AccountId,132	pub fraction: u128,133}134135#[derive(Encode, Decode, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]137pub enum SponsorshipState<AccountId> {138	/// The fees are applied to the transaction sender139	Disabled,140	Unconfirmed(AccountId),141	/// Transactions are sponsored by specified account142	Confirmed(AccountId),143}144145impl<AccountId> SponsorshipState<AccountId> {146	pub fn sponsor(&self) -> Option<&AccountId> {147		match self {148			Self::Confirmed(sponsor) => Some(sponsor),149			_ => None,150		}151	}152153	pub fn pending_sponsor(&self) -> Option<&AccountId> {154		match self {155			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),156			_ => None,157		}158	}159160	pub fn confirmed(&self) -> bool {161		matches!(self, Self::Confirmed(_))162	}163}164165impl<T> Default for SponsorshipState<T> {166	fn default() -> Self {167		Self::Disabled168	}169}170171#[derive(Encode, Decode, Clone, PartialEq)]172#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]173pub struct Collection<T: frame_system::Config> {174	pub owner: T::AccountId,175	pub mode: CollectionMode,176	pub access: AccessMode,177	pub decimal_points: DecimalPoints,178	pub name: Vec<u16>,        // 64 include null escape char179	pub description: Vec<u16>, // 256 include null escape char180	pub token_prefix: Vec<u8>, // 16 include null escape char181	pub mint_mode: bool,182	pub offchain_schema: Vec<u8>,183	pub schema_version: SchemaVersion,184	pub sponsorship: SponsorshipState<T::AccountId>,185	pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions186	pub variable_on_chain_schema: Vec<u8>,        //187	pub const_on_chain_schema: Vec<u8>,           //188	pub meta_update_permission: MetaUpdatePermission,189	pub transfers_enabled: bool,190}191192#[derive(Encode, Decode, Debug, Clone, PartialEq)]193#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]194pub struct NftItemType<AccountId> {195	pub owner: AccountId,196	pub const_data: Vec<u8>,197	pub variable_data: Vec<u8>,198}199200#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]201#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]202pub struct FungibleItemType {203	pub value: u128,204}205206#[derive(Encode, Decode, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]208pub struct ReFungibleItemType<AccountId> {209	pub owner: Vec<Ownership<AccountId>>,210	pub const_data: Vec<u8>,211	pub variable_data: Vec<u8>,212}213214#[derive(Encode, Decode, Debug, Clone, PartialEq)]215#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]216pub struct CollectionLimits<BlockNumber: Encode + Decode> {217	pub account_token_ownership_limit: u32,218	pub sponsored_data_size: u32,219	/// None - setVariableMetadata is not sponsored220	/// Some(v) - setVariableMetadata is sponsored221	///           if there is v block between txs222	pub sponsored_data_rate_limit: Option<BlockNumber>,223	pub token_limit: u32,224225	// Timeouts for item types in passed blocks226	pub sponsor_transfer_timeout: u32,227	pub owner_can_transfer: bool,228	pub owner_can_destroy: bool,229}230231impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {232	fn default() -> Self {233		Self {234			account_token_ownership_limit: 10_000_000,235			token_limit: u32::max_value(),236			sponsored_data_size: u32::MAX,237			sponsored_data_rate_limit: None,238			sponsor_transfer_timeout: 14400,239			owner_can_transfer: true,240			owner_can_destroy: true,241		}242	}243}244245/// BoundedVec doesn't supports serde246#[cfg(feature = "serde1")]247mod bounded_serde {248	use core::convert::TryFrom;249	use frame_support::{BoundedVec, traits::Get};250	use serde::{251		ser::{self, Serialize},252		de::{self, Deserialize, Error},253	};254	use sp_std::vec::Vec;255256	pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>257	where258		D: ser::Serializer,259		V: Serialize,260	{261		let vec: &Vec<_> = &value;262		vec.serialize(serializer)263	}264265	pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>266	where267		D: de::Deserializer<'de>,268		V: de::Deserialize<'de>,269		S: Get<u32>,270	{271		// TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?272		let vec = <Vec<V>>::deserialize(deserializer)?;273		let len = vec.len();274		TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))275	}276}277278#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]279#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]280#[derivative(Debug)]281pub struct CreateNftData {282	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]283	#[derivative(Debug = "ignore")]284	pub const_data: BoundedVec<u8, CustomDataLimit>,285	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]286	#[derivative(Debug = "ignore")]287	pub variable_data: BoundedVec<u8, CustomDataLimit>,288}289290#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq)]291#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]292pub struct CreateFungibleData {293	pub value: u128,294}295296#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]297#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]298#[derivative(Debug)]299pub struct CreateReFungibleData {300	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]301	#[derivative(Debug = "ignore")]302	pub const_data: BoundedVec<u8, CustomDataLimit>,303	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]304	#[derivative(Debug = "ignore")]305	pub variable_data: BoundedVec<u8, CustomDataLimit>,306	pub pieces: u128,307}308309#[derive(Encode, Decode, Debug, Clone, PartialEq)]310#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]311pub enum MetaUpdatePermission {312	ItemOwner,313	Admin,314	None,315}316317impl Default for MetaUpdatePermission {318	fn default() -> Self {319		Self::ItemOwner320	}321}322323#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug)]324#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]325pub enum CreateItemData {326	NFT(CreateNftData),327	Fungible(CreateFungibleData),328	ReFungible(CreateReFungibleData),329}330331impl CreateItemData {332	pub fn data_size(&self) -> usize {333		match self {334			CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),335			CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),336			_ => 0,337		}338	}339}340341impl From<CreateNftData> for CreateItemData {342	fn from(item: CreateNftData) -> Self {343		CreateItemData::NFT(item)344	}345}346347impl From<CreateReFungibleData> for CreateItemData {348	fn from(item: CreateReFungibleData) -> Self {349		CreateItemData::ReFungible(item)350	}351}352353impl From<CreateFungibleData> for CreateItemData {354	fn from(item: CreateFungibleData) -> Self {355		CreateItemData::Fungible(item)356	}357}
modifiedruntime_types.jsondiffbeforeafterboth
--- a/runtime_types.json
+++ b/runtime_types.json
@@ -63,6 +63,7 @@
       "Limits": "CollectionLimits",
       "VariableOnChainSchema": "Vec<u8>",
       "ConstOnChainSchema": "Vec<u8>",
+      "MetaUpdatePermission": "MetaUpdatePermission",
       "TransfersEnabled": "bool"
     },
     "RawData": "Vec<u8>",
@@ -94,6 +95,13 @@
         "Unique"
       ]
     },
+    "MetaUpdatePermission": {
+      "_enum": [
+        "ItemOwner",
+        "Admin",
+        "None"      
+      ]
+    },
     "CollectionId": "u32",
     "TokenId": "u32",
     "ChainLimits": {
modifiedtests/src/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -4,7 +4,6 @@
 //
 
 import { ApiPromise } from '@polkadot/api';
-import BN from 'bn.js';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
modifiedtests/src/collision-tests/adminLimitsOff.test.tsdiffbeforeafterboth
--- a/tests/src/collision-tests/adminLimitsOff.test.ts
+++ b/tests/src/collision-tests/adminLimitsOff.test.ts
@@ -1,5 +1,4 @@
 import { IKeyringPair } from '@polkadot/types/types';
-import BN from 'bn.js';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from '../substrate/privateKey';
addedtests/src/metadataUpdate.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/metadataUpdate.test.ts
@@ -0,0 +1,204 @@
+
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+  createItemExpectSuccess,
+  createCollectionExpectSuccess,
+  enablePublicMintingExpectSuccess,
+  enableWhiteListExpectSuccess,
+  setMetadataUpdatePermissionFlagExpectSuccess,
+  setVariableMetaDataExpectSuccess,
+  setMintPermissionExpectSuccess,
+  addToWhiteListExpectSuccess,
+  addCollectionAdminExpectSuccess,
+  setVariableMetaDataExpectFailure,
+  setMetadataUpdatePermissionFlagExpectFailure,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+
+describe('Metadata update permissions with ItemOwner flag', () => {
+  it('ItemOwner can set variable metadata with ItemOwner permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+
+      const data = [1, 2, 254, 255];
+
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+      await setVariableMetaDataExpectSuccess(Alice, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('Admin can\'n set variable metadata with ItemOwner permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+      await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+      await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+      await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+  
+      await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('User can\'n set variable metadata with ItemOwner permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+      await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);  
+      await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+});
+
+describe('Metadata update permissions with Admin flag', () => {
+  it('Admin can set variable metadata with Admin permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+
+      await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+      await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+      await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+  
+      await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('User can\'n can set variable metadata with Admin permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+
+      await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+      await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+      await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+  
+      await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('ItemOwner can\'n can set variable metadata with Admin permission flag', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      await enablePublicMintingExpectSuccess(Alice, nftCollectionId);
+      await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+      await enableWhiteListExpectSuccess(Alice, nftCollectionId);
+      const newNftTokenId = await createItemExpectSuccess(Bob, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+  
+      await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+});
+
+describe('Metadata update permissions with None flag', () => {
+  it('Nobody can set variable metadata with None flag (Regular)', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+  
+      await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('Nobody can set variable metadata with None flag (Admin)', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+      const Bob = privateKey('//Bob');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+  
+      await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+      await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+      await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+  
+      await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('Nobody can set variable metadata with None flag (ItemOwner)', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+  
+      const data = [1, 2, 254, 255];
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+  
+      await setVariableMetaDataExpectFailure(Alice, nftCollectionId, newNftTokenId, data);
+    });
+  });
+
+  it('Nobody can set variable metadata flag after freeze', async () => {
+    await usingApi(async () => {
+      const Alice = privateKey('//Alice');
+  
+      // nft
+      const nftCollectionId = await createCollectionExpectSuccess();
+      await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+      await setMetadataUpdatePermissionFlagExpectFailure(Alice, nftCollectionId, 'Admin');       
+    });
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -525,6 +525,28 @@
   });
 }
 
+export async function setMetadataUpdatePermissionFlagExpectSuccess(sender: IKeyringPair, collectionId: number, flag: string) {
+
+  await usingApi(async (api) => {
+    const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag); 
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    expect(result.success).to.be.true;
+  }); 
+}
+
+export async function setMetadataUpdatePermissionFlagExpectFailure(sender: IKeyringPair, collectionId: number, flag: string) {
+
+  await usingApi(async (api) => {
+    const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag); 
+    const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+    const result = getGenericResult(events);
+
+    expect(result.success).to.be.false;
+  }); 
+}
+
 export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {
   await usingApi(async (api) => {
     const tx = api.tx.nft.enableContractSponsoring(contractAddress, enable);