git.delta.rocks / unique-network / refs/commits / f3ce0b5ee1c5

difftreelog

refactor Remove variable data from tokens

Daniel Shiposha2022-05-14parent: #c4410a4.patch.diff
in: master

23 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -71,13 +71,6 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<u8>>;
-	#[rpc(name = "unique_variableMetadata")]
-	fn variable_metadata(
-		&self,
-		collection: CollectionId,
-		token: TokenId,
-		at: Option<BlockHash>,
-	) -> Result<Vec<u8>>;
 
 	#[rpc(name = "unique_collectionProperties")]
 	fn collection_properties(
@@ -279,7 +272,6 @@
 	);
 	pass_method!(topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>);
 	pass_method!(const_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
-	pass_method!(variable_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
 
 	pass_method!(collection_properties(
 		collection: CollectionId,
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -33,7 +33,7 @@
 	MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,
 	CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,
 	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,
-	CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,
+	CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState,
 	CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,
 	PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,
 	PropertiesError, PropertyKeyPermission, TokenData, TrySet,
@@ -312,8 +312,6 @@
 		CollectionTokenPrefixLimitExceeded,
 		/// Total collections bound exceeded.
 		TotalCollectionsLimitExceeded,
-		/// variable_data exceeded data limit.
-		TokenVariableDataLimitExceeded,
 		/// Exceeded max admin count
 		CollectionAdminCountExceeded,
 		/// Collection limit bounds per collection exceeded
@@ -1073,7 +1071,6 @@
 	fn approve() -> Weight;
 	fn transfer_from() -> Weight;
 	fn burn_from() -> Weight;
-	fn set_variable_metadata(bytes: u32) -> Weight;
 }
 
 pub trait CommonCollectionOperations<T: Config> {
@@ -1163,13 +1160,6 @@
 		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo;
 
-	fn set_variable_metadata(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		data: BoundedVec<u8, CustomDataLimit>,
-	) -> DispatchResultWithPostInfo;
-
 	fn check_nesting(
 		&self,
 		sender: T::CrossAccountId,
@@ -1185,7 +1175,6 @@
 
 	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;
 	fn const_metadata(&self, token: TokenId) -> Vec<u8>;
-	fn variable_metadata(&self, token: TokenId) -> Vec<u8>;
 	fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;
 	/// Amount of unique collection tokens
 	fn total_supply(&self) -> u32;
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -16,12 +16,12 @@
 
 use core::marker::PhantomData;
 
-use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
 use sp_runtime::ArithmeticError;
 use sp_std::{vec::Vec, vec};
-use up_data_structs::{CustomDataLimit, Property, PropertyKey, PropertyKeyPermission};
+use up_data_structs::{Property, PropertyKey, PropertyKeyPermission};
 
 use crate::{
 	Allowance, Balance, Config, Error, FungibleHandle, Pallet, SelfWeightOf, weights::WeightInfo,
@@ -85,11 +85,6 @@
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
 	}
-
-	fn set_variable_metadata(_bytes: u32) -> Weight {
-		// Error
-		0
-	}
 }
 
 impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
@@ -287,15 +282,6 @@
 		fail!(<Error<T>>::SettingPropertiesNotAllowed)
 	}
 
-	fn set_variable_metadata(
-		&self,
-		_sender: T::CrossAccountId,
-		_token: TokenId,
-		_data: BoundedVec<u8, CustomDataLimit>,
-	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::FungibleItemsDontHaveData)
-	}
-
 	fn check_nesting(
 		&self,
 		_sender: <T>::CrossAccountId,
@@ -330,9 +316,6 @@
 		None
 	}
 	fn const_metadata(&self, _token: TokenId) -> Vec<u8> {
-		Vec::new()
-	}
-	fn variable_metadata(&self, _token: TokenId) -> Vec<u8> {
 		Vec::new()
 	}
 
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -27,6 +27,7 @@
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
+struct-versioning = { path = "../../crates/struct-versioning" }
 
 [features]
 default = ["std"]
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -28,10 +28,8 @@
 
 fn create_max_item_data<T: Config>(owner: T::CrossAccountId) -> CreateItemData<T> {
 	let const_data = create_data::<CUSTOM_DATA_LIMIT>();
-	let variable_data = create_data::<CUSTOM_DATA_LIMIT>();
 	CreateItemData::<T> {
 		const_data,
-		variable_data,
 		owner,
 	}
 }
@@ -125,14 +123,4 @@
 		let item = create_max_item(&collection, &owner, sender.clone())?;
 		<Pallet<T>>::set_allowance(&collection, &sender, item, Some(&burner))?;
 	}: {<Pallet<T>>::burn_from(&collection, &burner, &sender, item, &Unlimited)?}
-
-	set_variable_metadata {
-		let b in 0..CUSTOM_DATA_LIMIT;
-		bench_init!{
-			owner: sub; collection: collection(owner);
-			owner: cross_from_sub; sender: cross_sub;
-		};
-		let item = create_max_item(&collection, &owner, sender.clone())?;
-		let data = create_var_data(b).try_into().unwrap();
-	}: {<Pallet<T>>::set_variable_metadata(&collection, &sender, item, data)?}
 }
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -16,9 +16,9 @@
 
 use core::marker::PhantomData;
 
-use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use up_data_structs::{
-	TokenId, CustomDataLimit, CreateItemExData, CollectionId, budget::Budget, Property,
+	TokenId, CreateItemExData, CollectionId, budget::Budget, Property,
 	PropertyKey, PropertyKeyPermission,
 };
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
@@ -85,10 +85,6 @@
 
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
-	}
-
-	fn set_variable_metadata(bytes: u32) -> Weight {
-		<SelfWeightOf<T>>::set_variable_metadata(bytes)
 	}
 }
 
@@ -99,7 +95,6 @@
 	match data {
 		up_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData::<T> {
 			const_data: data.const_data,
-			variable_data: data.variable_data,
 			properties: data.properties,
 			owner: to.clone(),
 		}),
@@ -325,19 +320,6 @@
 		} else {
 			Ok(().into())
 		}
-	}
-
-	fn set_variable_metadata(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		data: BoundedVec<u8, CustomDataLimit>,
-	) -> DispatchResultWithPostInfo {
-		let len = data.len();
-		with_weight(
-			<Pallet<T>>::set_variable_metadata(self, &sender, token, data),
-			<CommonWeights<T>>::set_variable_metadata(len as u32),
-		)
 	}
 
 	fn check_nesting(
@@ -376,12 +358,6 @@
 	fn const_metadata(&self, token: TokenId) -> Vec<u8> {
 		<TokenData<T>>::get((self.id, token))
 			.map(|t| t.const_data)
-			.unwrap_or_default()
-			.into_inner()
-	}
-	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
-		<TokenData<T>>::get((self.id, token))
-			.map(|t| t.variable_data)
 			.unwrap_or_default()
 			.into_inner()
 	}
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -24,7 +24,7 @@
 use up_data_structs::{TokenId, SchemaVersion};
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_core::{H160, U256};
-use sp_std::{vec::Vec, vec};
+use sp_std::vec::Vec;
 use pallet_common::{
 	erc::{CommonEvmHandler, PrecompileResult, CollectionPropertiesCall},
 	CollectionHandle,
@@ -274,7 +274,6 @@
 			&caller,
 			CreateItemData::<T> {
 				const_data: BoundedVec::default(),
-				variable_data: BoundedVec::default(),
 				properties: BoundedVec::default(),
 				owner: to,
 			},
@@ -322,7 +321,6 @@
 				const_data: Vec::<u8>::from(token_uri)
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
-				variable_data: BoundedVec::default(),
 				properties: BoundedVec::default(),
 				owner: to,
 			},
@@ -387,37 +385,6 @@
 			.into())
 	}
 
-	#[weight(<SelfWeightOf<T>>::set_variable_metadata(data.len() as u32))]
-	fn set_variable_metadata(
-		&mut self,
-		caller: caller,
-		token_id: uint256,
-		data: bytes,
-	) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let token = token_id.try_into()?;
-
-		<Pallet<T>>::set_variable_metadata(
-			self,
-			&caller,
-			token,
-			data.try_into()
-				.map_err(|_| "metadata size exceeded limit")?,
-		)
-		.map_err(dispatch_to_evm::<T>)?;
-		Ok(())
-	}
-
-	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
-		self.consume_store_reads(1)?;
-		let token: TokenId = token_id.try_into()?;
-
-		Ok(<TokenData<T>>::get((self.id, token))
-			.ok_or("token not found")?
-			.variable_data
-			.into_inner())
-	}
-
 	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]
 	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
@@ -440,7 +407,6 @@
 		let data = (0..total_tokens)
 			.map(|_| CreateItemData::<T> {
 				const_data: BoundedVec::default(),
-				variable_data: BoundedVec::default(),
 				properties: BoundedVec::default(),
 				owner: to.clone(),
 			})
@@ -484,7 +450,6 @@
 				const_data: Vec::<u8>::from(token_uri)
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
-				variable_data: vec![].try_into().unwrap(),
 				properties: BoundedVec::default(),
 				owner: to.clone(),
 			});
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{BoundedVec, ensure, fail};22use up_data_structs::{23	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,24	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,25	PropertyKey, PropertyKeyPermission, Properties, TrySet,26};27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};28use pallet_common::{29	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,30	dispatch::CollectionDispatch, eth::collection_id_to_address,31};32use pallet_structure::Pallet as PalletStructure;33use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};34use sp_core::H160;35use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};36use sp_std::{vec::Vec, vec};37use core::ops::Deref;38use sp_std::collections::btree_map::BTreeMap;39use codec::{Encode, Decode, MaxEncodedLen};40use scale_info::TypeInfo;4142pub use pallet::*;43#[cfg(feature = "runtime-benchmarks")]44pub mod benchmarking;45pub mod common;46pub mod erc;47pub mod weights;4849pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;50pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5152#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]53pub struct ItemData<CrossAccountId> {54	pub const_data: BoundedVec<u8, CustomDataLimit>,55	pub variable_data: BoundedVec<u8, CustomDataLimit>,56	pub owner: CrossAccountId,57}5859#[frame_support::pallet]60pub mod pallet {61	use super::*;62	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};63	use up_data_structs::{CollectionId, TokenId};64	use super::weights::WeightInfo;6566	#[pallet::error]67	pub enum Error<T> {68		/// Not Nonfungible item data used to mint in Nonfungible collection.69		NotNonfungibleDataUsedToMintFungibleCollectionToken,70		/// Used amount > 1 with NFT71		NonfungibleItemsHaveNoAmount,72	}7374	#[pallet::config]75	pub trait Config:76		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config77	{78		type WeightInfo: WeightInfo;79	}8081	#[pallet::pallet]82	#[pallet::generate_store(pub(super) trait Store)]83	pub struct Pallet<T>(_);8485	#[pallet::storage]86	pub type TokensMinted<T: Config> =87		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;88	#[pallet::storage]89	pub type TokensBurnt<T: Config> =90		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9192	#[pallet::storage]93	pub type TokenData<T: Config> = StorageNMap<94		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),95		Value = ItemData<T::CrossAccountId>,96		QueryKind = OptionQuery,97	>;9899	#[pallet::storage]100	#[pallet::getter(fn token_properties)]101	pub type TokenProperties<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = Properties,104		QueryKind = ValueQuery,105		OnEmpty = up_data_structs::TokenProperties,106	>;107108	/// Used to enumerate tokens owned by account109	#[pallet::storage]110	pub type Owned<T: Config> = StorageNMap<111		Key = (112			Key<Twox64Concat, CollectionId>,113			Key<Blake2_128Concat, T::CrossAccountId>,114			Key<Twox64Concat, TokenId>,115		),116		Value = bool,117		QueryKind = ValueQuery,118	>;119120	#[pallet::storage]121	pub type AccountBalance<T: Config> = StorageNMap<122		Key = (123			Key<Twox64Concat, CollectionId>,124			Key<Blake2_128Concat, T::CrossAccountId>,125		),126		Value = u32,127		QueryKind = ValueQuery,128	>;129130	#[pallet::storage]131	pub type Allowance<T: Config> = StorageNMap<132		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),133		Value = T::CrossAccountId,134		QueryKind = OptionQuery,135	>;136}137138pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);139impl<T: Config> NonfungibleHandle<T> {140	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {141		Self(inner)142	}143	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {144		self.0145	}146	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {147		&mut self.0148	}149}150impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {151	fn recorder(&self) -> &SubstrateRecorder<T> {152		self.0.recorder()153	}154	fn into_recorder(self) -> SubstrateRecorder<T> {155		self.0.into_recorder()156	}157}158impl<T: Config> Deref for NonfungibleHandle<T> {159	type Target = pallet_common::CollectionHandle<T>;160161	fn deref(&self) -> &Self::Target {162		&self.0163	}164}165166impl<T: Config> Pallet<T> {167	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {168		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)169	}170	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {171		<TokenData<T>>::contains_key((collection.id, token))172	}173}174175// unchecked calls skips any permission checks176impl<T: Config> Pallet<T> {177	pub fn init_collection(178		owner: T::AccountId,179		data: CreateCollectionData<T::AccountId>,180	) -> Result<CollectionId, DispatchError> {181		<PalletCommon<T>>::init_collection(owner, data)182	}183	pub fn destroy_collection(184		collection: NonfungibleHandle<T>,185		sender: &T::CrossAccountId,186	) -> DispatchResult {187		let id = collection.id;188189		// =========190191		PalletCommon::destroy_collection(collection.0, sender)?;192193		<TokenData<T>>::remove_prefix((id,), None);194		<Owned<T>>::remove_prefix((id,), None);195		<TokensMinted<T>>::remove(id);196		<TokensBurnt<T>>::remove(id);197		<Allowance<T>>::remove_prefix((id,), None);198		<AccountBalance<T>>::remove_prefix((id,), None);199		Ok(())200	}201202	pub fn burn(203		collection: &NonfungibleHandle<T>,204		sender: &T::CrossAccountId,205		token: TokenId,206	) -> DispatchResult {207		let token_data =208			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;209		ensure!(210			&token_data.owner == sender211				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),212			<CommonError<T>>::NoPermission213		);214215		if collection.access == AccessMode::AllowList {216			collection.check_allowlist(sender)?;217		}218219		let burnt = <TokensBurnt<T>>::get(collection.id)220			.checked_add(1)221			.ok_or(ArithmeticError::Overflow)?;222223		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))224			.checked_sub(1)225			.ok_or(ArithmeticError::Overflow)?;226227		if balance == 0 {228			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));229		} else {230			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);231		}232		// =========233234		<Owned<T>>::remove((collection.id, &token_data.owner, token));235		<TokensBurnt<T>>::insert(collection.id, burnt);236		<TokenData<T>>::remove((collection.id, token));237		let old_spender = <Allowance<T>>::take((collection.id, token));238239		if let Some(old_spender) = old_spender {240			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(241				collection.id,242				token,243				sender.clone(),244				old_spender,245				0,246			));247		}248249		<PalletEvm<T>>::deposit_log(250			ERC721Events::Transfer {251				from: *token_data.owner.as_eth(),252				to: H160::default(),253				token_id: token.into(),254			}255			.to_log(collection_id_to_address(collection.id)),256		);257		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(258			collection.id,259			token,260			token_data.owner,261			1,262		));263		Ok(())264	}265266	pub fn set_token_property(267		collection: &NonfungibleHandle<T>,268		sender: &T::CrossAccountId,269		token_id: TokenId,270		property: Property,271	) -> DispatchResult {272		Self::check_token_change_permission(collection, sender, token_id, &property.key)?;273274		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {275			let property = property.clone();276			properties.try_set(property.key, property.value)277		})278		.map_err(<CommonError<T>>::from)?;279280		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(281			collection.id,282			token_id,283			property.key,284		));285286		Ok(())287	}288289	pub fn set_token_properties(290		collection: &NonfungibleHandle<T>,291		sender: &T::CrossAccountId,292		token_id: TokenId,293		properties: Vec<Property>,294	) -> DispatchResult {295		for property in properties {296			Self::set_token_property(collection, sender, token_id, property)?;297		}298299		Ok(())300	}301302	pub fn delete_token_property(303		collection: &NonfungibleHandle<T>,304		sender: &T::CrossAccountId,305		token_id: TokenId,306		property_key: PropertyKey,307	) -> DispatchResult {308		Self::check_token_change_permission(collection, sender, token_id, &property_key)?;309310		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {311			properties.remove(&property_key)312		})313		.map_err(<CommonError<T>>::from)?;314315		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(316			collection.id,317			token_id,318			property_key,319		));320321		Ok(())322	}323324	fn check_token_change_permission(325		collection: &NonfungibleHandle<T>,326		sender: &T::CrossAccountId,327		token_id: TokenId,328		property_key: &PropertyKey,329	) -> DispatchResult {330		let permission = <PalletCommon<T>>::property_permissions(collection.id)331			.get(property_key)332			.map(|p| p.clone())333			.unwrap_or(PropertyPermission::none());334335		let token_data = <TokenData<T>>::get((collection.id, token_id))336			.ok_or(<CommonError<T>>::TokenNotFound)?;337338		let check_token_owner = || -> DispatchResult {339			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);340			Ok(())341		};342343		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))344			.get(property_key)345			.is_some();346347		match permission {348			PropertyPermission { mutable: false, .. } if is_property_exists => {349				Err(<CommonError<T>>::NoPermission.into())350			}351352			PropertyPermission {353				collection_admin,354				token_owner,355				..356			} => {357				let mut check_result = Err(<CommonError<T>>::NoPermission.into());358359				if collection_admin {360					check_result = collection.check_is_owner_or_admin(sender);361				}362363				if token_owner {364					check_result.or_else(|_| check_token_owner())365				} else {366					check_result367				}368			}369		}370	}371372	pub fn delete_token_properties(373		collection: &NonfungibleHandle<T>,374		sender: &T::CrossAccountId,375		token_id: TokenId,376		property_keys: Vec<PropertyKey>,377	) -> DispatchResult {378		for key in property_keys {379			Self::delete_token_property(collection, sender, token_id, key)?;380		}381382		Ok(())383	}384385	pub fn set_collection_properties(386		collection: &NonfungibleHandle<T>,387		sender: &T::CrossAccountId,388		properties: Vec<Property>,389	) -> DispatchResult {390		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)391	}392393	pub fn delete_collection_properties(394		collection: &CollectionHandle<T>,395		sender: &T::CrossAccountId,396		property_keys: Vec<PropertyKey>,397	) -> DispatchResult {398		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)399	}400401	pub fn set_property_permissions(402		collection: &CollectionHandle<T>,403		sender: &T::CrossAccountId,404		property_permissions: Vec<PropertyKeyPermission>,405	) -> DispatchResult {406		<PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)407	}408409	pub fn transfer(410		collection: &NonfungibleHandle<T>,411		from: &T::CrossAccountId,412		to: &T::CrossAccountId,413		token: TokenId,414		nesting_budget: &dyn Budget,415	) -> DispatchResult {416		ensure!(417			collection.limits.transfers_enabled(),418			<CommonError<T>>::TransferNotAllowed419		);420421		let token_data =422			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;423		// TODO: require sender to be token, owner, require admins to go through transfer_from424		ensure!(425			&token_data.owner == from426				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),427			<CommonError<T>>::NoPermission428		);429430		if collection.access == AccessMode::AllowList {431			collection.check_allowlist(from)?;432			collection.check_allowlist(to)?;433		}434		<PalletCommon<T>>::ensure_correct_receiver(to)?;435436		let balance_from = <AccountBalance<T>>::get((collection.id, from))437			.checked_sub(1)438			.ok_or(<CommonError<T>>::TokenValueTooLow)?;439		let balance_to = if from != to {440			let balance_to = <AccountBalance<T>>::get((collection.id, to))441				.checked_add(1)442				.ok_or(ArithmeticError::Overflow)?;443444			ensure!(445				balance_to < collection.limits.account_token_ownership_limit(),446				<CommonError<T>>::AccountTokenLimitExceeded,447			);448449			Some(balance_to)450		} else {451			None452		};453454		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {455			let handle = <CollectionHandle<T>>::try_get(target.0)?;456			let dispatch = T::CollectionDispatch::dispatch(handle);457			let dispatch = dispatch.as_dyn();458459			dispatch.check_nesting(460				from.clone(),461				(collection.id, token),462				target.1,463				nesting_budget,464			)?;465		}466467		// =========468469		<TokenData<T>>::insert(470			(collection.id, token),471			ItemData {472				owner: to.clone(),473				..token_data474			},475		);476477		if let Some(balance_to) = balance_to {478			// from != to479			if balance_from == 0 {480				<AccountBalance<T>>::remove((collection.id, from));481			} else {482				<AccountBalance<T>>::insert((collection.id, from), balance_from);483			}484			<AccountBalance<T>>::insert((collection.id, to), balance_to);485			<Owned<T>>::remove((collection.id, from, token));486			<Owned<T>>::insert((collection.id, to, token), true);487		}488		Self::set_allowance_unchecked(collection, from, token, None, true);489490		<PalletEvm<T>>::deposit_log(491			ERC721Events::Transfer {492				from: *from.as_eth(),493				to: *to.as_eth(),494				token_id: token.into(),495			}496			.to_log(collection_id_to_address(collection.id)),497		);498		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(499			collection.id,500			token,501			from.clone(),502			to.clone(),503			1,504		));505		Ok(())506	}507508	pub fn create_multiple_items(509		collection: &NonfungibleHandle<T>,510		sender: &T::CrossAccountId,511		data: Vec<CreateItemData<T>>,512		nesting_budget: &dyn Budget,513	) -> DispatchResult {514		if !collection.is_owner_or_admin(sender) {515			ensure!(516				collection.mint_mode,517				<CommonError<T>>::PublicMintingNotAllowed518			);519			collection.check_allowlist(sender)?;520521			for item in data.iter() {522				collection.check_allowlist(&item.owner)?;523			}524		}525526		for data in data.iter() {527			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;528		}529530		let first_token = <TokensMinted<T>>::get(collection.id);531		let tokens_minted = first_token532			.checked_add(data.len() as u32)533			.ok_or(ArithmeticError::Overflow)?;534		ensure!(535			tokens_minted <= collection.limits.token_limit(),536			<CommonError<T>>::CollectionTokenLimitExceeded537		);538539		let mut balances = BTreeMap::new();540		for data in &data {541			let balance = balances542				.entry(&data.owner)543				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));544			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;545546			ensure!(547				*balance <= collection.limits.account_token_ownership_limit(),548				<CommonError<T>>::AccountTokenLimitExceeded,549			);550		}551552		for (i, data) in data.iter().enumerate() {553			let token = TokenId(first_token + i as u32 + 1);554			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {555				let handle = <CollectionHandle<T>>::try_get(target.0)?;556				let dispatch = T::CollectionDispatch::dispatch(handle);557				let dispatch = dispatch.as_dyn();558				dispatch.check_nesting(559					sender.clone(),560					(collection.id, token),561					target.1,562					nesting_budget,563				)?;564			}565		}566567		// =========568569		<TokensMinted<T>>::insert(collection.id, tokens_minted);570		for (account, balance) in balances {571			<AccountBalance<T>>::insert((collection.id, account), balance);572		}573		for (i, data) in data.into_iter().enumerate() {574			let token = first_token + i as u32 + 1;575576			<TokenData<T>>::insert(577				(collection.id, token),578				ItemData {579					const_data: data.const_data,580					variable_data: data.variable_data,581					owner: data.owner.clone(),582				},583			);584			<Owned<T>>::insert((collection.id, &data.owner, token), true);585586			Self::set_token_properties(587				collection,588				sender,589				TokenId(token),590				data.properties.into_inner(),591			)?;592593			<PalletEvm<T>>::deposit_log(594				ERC721Events::Transfer {595					from: H160::default(),596					to: *data.owner.as_eth(),597					token_id: token.into(),598				}599				.to_log(collection_id_to_address(collection.id)),600			);601			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(602				collection.id,603				TokenId(token),604				data.owner.clone(),605				1,606			));607		}608		Ok(())609	}610611	pub fn set_allowance_unchecked(612		collection: &NonfungibleHandle<T>,613		sender: &T::CrossAccountId,614		token: TokenId,615		spender: Option<&T::CrossAccountId>,616		assume_implicit_eth: bool,617	) {618		if let Some(spender) = spender {619			let old_spender = <Allowance<T>>::get((collection.id, token));620			<Allowance<T>>::insert((collection.id, token), spender);621			// In ERC721 there is only one possible approved user of token, so we set622			// approved user to spender623			<PalletEvm<T>>::deposit_log(624				ERC721Events::Approval {625					owner: *sender.as_eth(),626					approved: *spender.as_eth(),627					token_id: token.into(),628				}629				.to_log(collection_id_to_address(collection.id)),630			);631			// In Unique chain, any token can have any amount of approved users, so we need to632			// set allowance of old owner to 0, and allowance of new owner to 1633			if old_spender.as_ref() != Some(spender) {634				if let Some(old_owner) = old_spender {635					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(636						collection.id,637						token,638						sender.clone(),639						old_owner,640						0,641					));642				}643				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(644					collection.id,645					token,646					sender.clone(),647					spender.clone(),648					1,649				));650			}651		} else {652			let old_spender = <Allowance<T>>::take((collection.id, token));653			if !assume_implicit_eth {654				// In ERC721 there is only one possible approved user of token, so we set655				// approved user to zero address656				<PalletEvm<T>>::deposit_log(657					ERC721Events::Approval {658						owner: *sender.as_eth(),659						approved: H160::default(),660						token_id: token.into(),661					}662					.to_log(collection_id_to_address(collection.id)),663				);664			}665			// In Unique chain, any token can have any amount of approved users, so we need to666			// set allowance of old owner to 0667			if let Some(old_spender) = old_spender {668				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(669					collection.id,670					token,671					sender.clone(),672					old_spender,673					0,674				));675			}676		}677	}678679	pub fn set_allowance(680		collection: &NonfungibleHandle<T>,681		sender: &T::CrossAccountId,682		token: TokenId,683		spender: Option<&T::CrossAccountId>,684	) -> DispatchResult {685		if collection.access == AccessMode::AllowList {686			collection.check_allowlist(sender)?;687			if let Some(spender) = spender {688				collection.check_allowlist(spender)?;689			}690		}691692		if let Some(spender) = spender {693			<PalletCommon<T>>::ensure_correct_receiver(spender)?;694		}695		let token_data =696			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;697		if &token_data.owner != sender {698			ensure!(699				collection.ignores_owned_amount(sender),700				<CommonError<T>>::CantApproveMoreThanOwned701			);702		}703704		// =========705706		Self::set_allowance_unchecked(collection, sender, token, spender, false);707		Ok(())708	}709710	fn check_allowed(711		collection: &NonfungibleHandle<T>,712		spender: &T::CrossAccountId,713		from: &T::CrossAccountId,714		token: TokenId,715		nesting_budget: &dyn Budget,716	) -> DispatchResult {717		if spender.conv_eq(from) {718			return Ok(());719		}720		if collection.access == AccessMode::AllowList {721			// `from`, `to` checked in [`transfer`]722			collection.check_allowlist(spender)?;723		}724		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {725			// TODO: should collection owner be allowed to perform this transfer?726			ensure!(727				<PalletStructure<T>>::check_indirectly_owned(728					spender.clone(),729					source.0,730					source.1,731					None,732					nesting_budget733				)?,734				<CommonError<T>>::ApprovedValueTooLow,735			);736			return Ok(());737		}738		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {739			return Ok(());740		}741		ensure!(742			collection.ignores_allowance(spender),743			<CommonError<T>>::ApprovedValueTooLow744		);745		Ok(())746	}747748	pub fn transfer_from(749		collection: &NonfungibleHandle<T>,750		spender: &T::CrossAccountId,751		from: &T::CrossAccountId,752		to: &T::CrossAccountId,753		token: TokenId,754		nesting_budget: &dyn Budget,755	) -> DispatchResult {756		Self::check_allowed(collection, spender, from, token, nesting_budget)?;757758		// =========759760		// Allowance is reset in [`transfer`]761		Self::transfer(collection, from, to, token, nesting_budget)762	}763764	pub fn burn_from(765		collection: &NonfungibleHandle<T>,766		spender: &T::CrossAccountId,767		from: &T::CrossAccountId,768		token: TokenId,769		nesting_budget: &dyn Budget,770	) -> DispatchResult {771		Self::check_allowed(collection, spender, from, token, nesting_budget)?;772773		// =========774775		Self::burn(collection, from, token)776	}777778	pub fn set_variable_metadata(779		collection: &NonfungibleHandle<T>,780		sender: &T::CrossAccountId,781		token: TokenId,782		data: BoundedVec<u8, CustomDataLimit>,783	) -> DispatchResult {784		let token_data =785			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;786		collection.check_can_update_meta(sender, &token_data.owner)?;787788		// =========789790		<TokenData<T>>::insert(791			(collection.id, token),792			ItemData {793				variable_data: data,794				..token_data795			},796		);797		Ok(())798	}799800	pub fn check_nesting(801		handle: &NonfungibleHandle<T>,802		sender: T::CrossAccountId,803		from: (CollectionId, TokenId),804		under: TokenId,805		nesting_budget: &dyn Budget,806	) -> DispatchResult {807		fn ensure_sender_allowed<T: Config>(808			collection: CollectionId,809			token: TokenId,810			for_nest: (CollectionId, TokenId),811			sender: T::CrossAccountId,812			budget: &dyn Budget,813		) -> DispatchResult {814			ensure!(815				<PalletStructure<T>>::check_indirectly_owned(816					sender,817					collection,818					token,819					Some(for_nest),820					budget821				)?,822				<CommonError<T>>::OnlyOwnerAllowedToNest,823			);824			Ok(())825		}826		match handle.limits.nesting_rule() {827			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),828			NestingRule::Owner => {829				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?830			}831			NestingRule::OwnerRestricted(whitelist) => {832				ensure!(833					whitelist.contains(&from.0),834					<CommonError<T>>::SourceCollectionIsNotAllowedToNest835				);836				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?837			}838		}839		Ok(())840	}841842	/// Delegated to `create_multiple_items`843	pub fn create_item(844		collection: &NonfungibleHandle<T>,845		sender: &T::CrossAccountId,846		data: CreateItemData<T>,847		nesting_budget: &dyn Budget,848	) -> DispatchResult {849		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)850	}851}
after · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{BoundedVec, ensure, fail};22use up_data_structs::{23	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,24	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,25	PropertyKey, PropertyKeyPermission, Properties, TrySet,26};27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};28use pallet_common::{29	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,30	dispatch::CollectionDispatch, eth::collection_id_to_address,31};32use pallet_structure::Pallet as PalletStructure;33use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};34use sp_core::H160;35use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};36use sp_std::{vec::Vec, vec};37use core::ops::Deref;38use sp_std::collections::btree_map::BTreeMap;39use codec::{Encode, Decode, MaxEncodedLen};40use scale_info::TypeInfo;4142pub use pallet::*;43#[cfg(feature = "runtime-benchmarks")]44pub mod benchmarking;45pub mod common;46pub mod erc;47pub mod weights;4849pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;50pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5152#[struct_versioning::versioned(version = 2, upper)]53#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]54pub struct ItemData<CrossAccountId> {55	pub const_data: BoundedVec<u8, CustomDataLimit>,5657	#[version(..2)]58	pub variable_data: BoundedVec<u8, CustomDataLimit>,5960	pub owner: CrossAccountId,61}6263#[frame_support::pallet]64pub mod pallet {65	use super::*;66	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};67	use frame_system::pallet_prelude::*;68	use up_data_structs::{CollectionId, TokenId};69	use super::weights::WeightInfo;7071	#[pallet::error]72	pub enum Error<T> {73		/// Not Nonfungible item data used to mint in Nonfungible collection.74		NotNonfungibleDataUsedToMintFungibleCollectionToken,75		/// Used amount > 1 with NFT76		NonfungibleItemsHaveNoAmount,77	}7879	#[pallet::config]80	pub trait Config:81		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config82	{83		type WeightInfo: WeightInfo;84	}8586	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8788	#[pallet::pallet]89	#[pallet::storage_version(STORAGE_VERSION)]90	#[pallet::generate_store(pub(super) trait Store)]91	pub struct Pallet<T>(_);9293	#[pallet::storage]94	pub type TokensMinted<T: Config> =95		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;96	#[pallet::storage]97	pub type TokensBurnt<T: Config> =98		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;99100	#[pallet::storage]101	pub type TokenData<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = ItemData<T::CrossAccountId>,104		QueryKind = OptionQuery,105	>;106107	#[pallet::storage]108	#[pallet::getter(fn token_properties)]109	pub type TokenProperties<T: Config> = StorageNMap<110		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),111		Value = Properties,112		QueryKind = ValueQuery,113		OnEmpty = up_data_structs::TokenProperties,114	>;115116	/// Used to enumerate tokens owned by account117	#[pallet::storage]118	pub type Owned<T: Config> = StorageNMap<119		Key = (120			Key<Twox64Concat, CollectionId>,121			Key<Blake2_128Concat, T::CrossAccountId>,122			Key<Twox64Concat, TokenId>,123		),124		Value = bool,125		QueryKind = ValueQuery,126	>;127128	#[pallet::storage]129	pub type AccountBalance<T: Config> = StorageNMap<130		Key = (131			Key<Twox64Concat, CollectionId>,132			Key<Blake2_128Concat, T::CrossAccountId>,133		),134		Value = u32,135		QueryKind = ValueQuery,136	>;137138	#[pallet::storage]139	pub type Allowance<T: Config> = StorageNMap<140		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),141		Value = T::CrossAccountId,142		QueryKind = OptionQuery,143	>;144145	#[pallet::hooks]146	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {147		fn on_runtime_upgrade() -> Weight {148			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {149				<TokenData<T>>::translate_values::<ItemDataVersion1<T::CrossAccountId>, _>(|v| {150					Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))151				})152			}153154			0155		}156	}157}158159pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);160impl<T: Config> NonfungibleHandle<T> {161	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {162		Self(inner)163	}164	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {165		self.0166	}167	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {168		&mut self.0169	}170}171impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {172	fn recorder(&self) -> &SubstrateRecorder<T> {173		self.0.recorder()174	}175	fn into_recorder(self) -> SubstrateRecorder<T> {176		self.0.into_recorder()177	}178}179impl<T: Config> Deref for NonfungibleHandle<T> {180	type Target = pallet_common::CollectionHandle<T>;181182	fn deref(&self) -> &Self::Target {183		&self.0184	}185}186187impl<T: Config> Pallet<T> {188	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {189		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)190	}191	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {192		<TokenData<T>>::contains_key((collection.id, token))193	}194}195196// unchecked calls skips any permission checks197impl<T: Config> Pallet<T> {198	pub fn init_collection(199		owner: T::AccountId,200		data: CreateCollectionData<T::AccountId>,201	) -> Result<CollectionId, DispatchError> {202		<PalletCommon<T>>::init_collection(owner, data)203	}204	pub fn destroy_collection(205		collection: NonfungibleHandle<T>,206		sender: &T::CrossAccountId,207	) -> DispatchResult {208		let id = collection.id;209210		// =========211212		PalletCommon::destroy_collection(collection.0, sender)?;213214		<TokenData<T>>::remove_prefix((id,), None);215		<Owned<T>>::remove_prefix((id,), None);216		<TokensMinted<T>>::remove(id);217		<TokensBurnt<T>>::remove(id);218		<Allowance<T>>::remove_prefix((id,), None);219		<AccountBalance<T>>::remove_prefix((id,), None);220		Ok(())221	}222223	pub fn burn(224		collection: &NonfungibleHandle<T>,225		sender: &T::CrossAccountId,226		token: TokenId,227	) -> DispatchResult {228		let token_data =229			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;230		ensure!(231			&token_data.owner == sender232				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),233			<CommonError<T>>::NoPermission234		);235236		if collection.access == AccessMode::AllowList {237			collection.check_allowlist(sender)?;238		}239240		let burnt = <TokensBurnt<T>>::get(collection.id)241			.checked_add(1)242			.ok_or(ArithmeticError::Overflow)?;243244		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))245			.checked_sub(1)246			.ok_or(ArithmeticError::Overflow)?;247248		if balance == 0 {249			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));250		} else {251			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);252		}253		// =========254255		<Owned<T>>::remove((collection.id, &token_data.owner, token));256		<TokensBurnt<T>>::insert(collection.id, burnt);257		<TokenData<T>>::remove((collection.id, token));258		let old_spender = <Allowance<T>>::take((collection.id, token));259260		if let Some(old_spender) = old_spender {261			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(262				collection.id,263				token,264				sender.clone(),265				old_spender,266				0,267			));268		}269270		<PalletEvm<T>>::deposit_log(271			ERC721Events::Transfer {272				from: *token_data.owner.as_eth(),273				to: H160::default(),274				token_id: token.into(),275			}276			.to_log(collection_id_to_address(collection.id)),277		);278		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(279			collection.id,280			token,281			token_data.owner,282			1,283		));284		Ok(())285	}286287	pub fn set_token_property(288		collection: &NonfungibleHandle<T>,289		sender: &T::CrossAccountId,290		token_id: TokenId,291		property: Property,292	) -> DispatchResult {293		Self::check_token_change_permission(collection, sender, token_id, &property.key)?;294295		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {296			let property = property.clone();297			properties.try_set(property.key, property.value)298		})299		.map_err(<CommonError<T>>::from)?;300301		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(302			collection.id,303			token_id,304			property.key,305		));306307		Ok(())308	}309310	pub fn set_token_properties(311		collection: &NonfungibleHandle<T>,312		sender: &T::CrossAccountId,313		token_id: TokenId,314		properties: Vec<Property>,315	) -> DispatchResult {316		for property in properties {317			Self::set_token_property(collection, sender, token_id, property)?;318		}319320		Ok(())321	}322323	pub fn delete_token_property(324		collection: &NonfungibleHandle<T>,325		sender: &T::CrossAccountId,326		token_id: TokenId,327		property_key: PropertyKey,328	) -> DispatchResult {329		Self::check_token_change_permission(collection, sender, token_id, &property_key)?;330331		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {332			properties.remove(&property_key)333		})334		.map_err(<CommonError<T>>::from)?;335336		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(337			collection.id,338			token_id,339			property_key,340		));341342		Ok(())343	}344345	fn check_token_change_permission(346		collection: &NonfungibleHandle<T>,347		sender: &T::CrossAccountId,348		token_id: TokenId,349		property_key: &PropertyKey,350	) -> DispatchResult {351		let permission = <PalletCommon<T>>::property_permissions(collection.id)352			.get(property_key)353			.map(|p| p.clone())354			.unwrap_or(PropertyPermission::none());355356		let token_data = <TokenData<T>>::get((collection.id, token_id))357			.ok_or(<CommonError<T>>::TokenNotFound)?;358359		let check_token_owner = || -> DispatchResult {360			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);361			Ok(())362		};363364		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))365			.get(property_key)366			.is_some();367368		match permission {369			PropertyPermission { mutable: false, .. } if is_property_exists => {370				Err(<CommonError<T>>::NoPermission.into())371			}372373			PropertyPermission {374				collection_admin,375				token_owner,376				..377			} => {378				let mut check_result = Err(<CommonError<T>>::NoPermission.into());379380				if collection_admin {381					check_result = collection.check_is_owner_or_admin(sender);382				}383384				if token_owner {385					check_result.or_else(|_| check_token_owner())386				} else {387					check_result388				}389			}390		}391	}392393	pub fn delete_token_properties(394		collection: &NonfungibleHandle<T>,395		sender: &T::CrossAccountId,396		token_id: TokenId,397		property_keys: Vec<PropertyKey>,398	) -> DispatchResult {399		for key in property_keys {400			Self::delete_token_property(collection, sender, token_id, key)?;401		}402403		Ok(())404	}405406	pub fn set_collection_properties(407		collection: &NonfungibleHandle<T>,408		sender: &T::CrossAccountId,409		properties: Vec<Property>,410	) -> DispatchResult {411		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)412	}413414	pub fn delete_collection_properties(415		collection: &CollectionHandle<T>,416		sender: &T::CrossAccountId,417		property_keys: Vec<PropertyKey>,418	) -> DispatchResult {419		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)420	}421422	pub fn set_property_permissions(423		collection: &CollectionHandle<T>,424		sender: &T::CrossAccountId,425		property_permissions: Vec<PropertyKeyPermission>,426	) -> DispatchResult {427		<PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)428	}429430	pub fn transfer(431		collection: &NonfungibleHandle<T>,432		from: &T::CrossAccountId,433		to: &T::CrossAccountId,434		token: TokenId,435		nesting_budget: &dyn Budget,436	) -> DispatchResult {437		ensure!(438			collection.limits.transfers_enabled(),439			<CommonError<T>>::TransferNotAllowed440		);441442		let token_data =443			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;444		// TODO: require sender to be token, owner, require admins to go through transfer_from445		ensure!(446			&token_data.owner == from447				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),448			<CommonError<T>>::NoPermission449		);450451		if collection.access == AccessMode::AllowList {452			collection.check_allowlist(from)?;453			collection.check_allowlist(to)?;454		}455		<PalletCommon<T>>::ensure_correct_receiver(to)?;456457		let balance_from = <AccountBalance<T>>::get((collection.id, from))458			.checked_sub(1)459			.ok_or(<CommonError<T>>::TokenValueTooLow)?;460		let balance_to = if from != to {461			let balance_to = <AccountBalance<T>>::get((collection.id, to))462				.checked_add(1)463				.ok_or(ArithmeticError::Overflow)?;464465			ensure!(466				balance_to < collection.limits.account_token_ownership_limit(),467				<CommonError<T>>::AccountTokenLimitExceeded,468			);469470			Some(balance_to)471		} else {472			None473		};474475		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {476			let handle = <CollectionHandle<T>>::try_get(target.0)?;477			let dispatch = T::CollectionDispatch::dispatch(handle);478			let dispatch = dispatch.as_dyn();479480			dispatch.check_nesting(481				from.clone(),482				(collection.id, token),483				target.1,484				nesting_budget,485			)?;486		}487488		// =========489490		<TokenData<T>>::insert(491			(collection.id, token),492			ItemData {493				owner: to.clone(),494				..token_data495			},496		);497498		if let Some(balance_to) = balance_to {499			// from != to500			if balance_from == 0 {501				<AccountBalance<T>>::remove((collection.id, from));502			} else {503				<AccountBalance<T>>::insert((collection.id, from), balance_from);504			}505			<AccountBalance<T>>::insert((collection.id, to), balance_to);506			<Owned<T>>::remove((collection.id, from, token));507			<Owned<T>>::insert((collection.id, to, token), true);508		}509		Self::set_allowance_unchecked(collection, from, token, None, true);510511		<PalletEvm<T>>::deposit_log(512			ERC721Events::Transfer {513				from: *from.as_eth(),514				to: *to.as_eth(),515				token_id: token.into(),516			}517			.to_log(collection_id_to_address(collection.id)),518		);519		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(520			collection.id,521			token,522			from.clone(),523			to.clone(),524			1,525		));526		Ok(())527	}528529	pub fn create_multiple_items(530		collection: &NonfungibleHandle<T>,531		sender: &T::CrossAccountId,532		data: Vec<CreateItemData<T>>,533		nesting_budget: &dyn Budget,534	) -> DispatchResult {535		if !collection.is_owner_or_admin(sender) {536			ensure!(537				collection.mint_mode,538				<CommonError<T>>::PublicMintingNotAllowed539			);540			collection.check_allowlist(sender)?;541542			for item in data.iter() {543				collection.check_allowlist(&item.owner)?;544			}545		}546547		for data in data.iter() {548			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;549		}550551		let first_token = <TokensMinted<T>>::get(collection.id);552		let tokens_minted = first_token553			.checked_add(data.len() as u32)554			.ok_or(ArithmeticError::Overflow)?;555		ensure!(556			tokens_minted <= collection.limits.token_limit(),557			<CommonError<T>>::CollectionTokenLimitExceeded558		);559560		let mut balances = BTreeMap::new();561		for data in &data {562			let balance = balances563				.entry(&data.owner)564				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));565			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;566567			ensure!(568				*balance <= collection.limits.account_token_ownership_limit(),569				<CommonError<T>>::AccountTokenLimitExceeded,570			);571		}572573		for (i, data) in data.iter().enumerate() {574			let token = TokenId(first_token + i as u32 + 1);575			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {576				let handle = <CollectionHandle<T>>::try_get(target.0)?;577				let dispatch = T::CollectionDispatch::dispatch(handle);578				let dispatch = dispatch.as_dyn();579				dispatch.check_nesting(580					sender.clone(),581					(collection.id, token),582					target.1,583					nesting_budget,584				)?;585			}586		}587588		// =========589590		<TokensMinted<T>>::insert(collection.id, tokens_minted);591		for (account, balance) in balances {592			<AccountBalance<T>>::insert((collection.id, account), balance);593		}594		for (i, data) in data.into_iter().enumerate() {595			let token = first_token + i as u32 + 1;596597			<TokenData<T>>::insert(598				(collection.id, token),599				ItemData {600					const_data: data.const_data,601					owner: data.owner.clone(),602				},603			);604			<Owned<T>>::insert((collection.id, &data.owner, token), true);605606			Self::set_token_properties(607				collection,608				sender,609				TokenId(token),610				data.properties.into_inner(),611			)?;612613			<PalletEvm<T>>::deposit_log(614				ERC721Events::Transfer {615					from: H160::default(),616					to: *data.owner.as_eth(),617					token_id: token.into(),618				}619				.to_log(collection_id_to_address(collection.id)),620			);621			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(622				collection.id,623				TokenId(token),624				data.owner.clone(),625				1,626			));627		}628		Ok(())629	}630631	pub fn set_allowance_unchecked(632		collection: &NonfungibleHandle<T>,633		sender: &T::CrossAccountId,634		token: TokenId,635		spender: Option<&T::CrossAccountId>,636		assume_implicit_eth: bool,637	) {638		if let Some(spender) = spender {639			let old_spender = <Allowance<T>>::get((collection.id, token));640			<Allowance<T>>::insert((collection.id, token), spender);641			// In ERC721 there is only one possible approved user of token, so we set642			// approved user to spender643			<PalletEvm<T>>::deposit_log(644				ERC721Events::Approval {645					owner: *sender.as_eth(),646					approved: *spender.as_eth(),647					token_id: token.into(),648				}649				.to_log(collection_id_to_address(collection.id)),650			);651			// In Unique chain, any token can have any amount of approved users, so we need to652			// set allowance of old owner to 0, and allowance of new owner to 1653			if old_spender.as_ref() != Some(spender) {654				if let Some(old_owner) = old_spender {655					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(656						collection.id,657						token,658						sender.clone(),659						old_owner,660						0,661					));662				}663				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(664					collection.id,665					token,666					sender.clone(),667					spender.clone(),668					1,669				));670			}671		} else {672			let old_spender = <Allowance<T>>::take((collection.id, token));673			if !assume_implicit_eth {674				// In ERC721 there is only one possible approved user of token, so we set675				// approved user to zero address676				<PalletEvm<T>>::deposit_log(677					ERC721Events::Approval {678						owner: *sender.as_eth(),679						approved: H160::default(),680						token_id: token.into(),681					}682					.to_log(collection_id_to_address(collection.id)),683				);684			}685			// In Unique chain, any token can have any amount of approved users, so we need to686			// set allowance of old owner to 0687			if let Some(old_spender) = old_spender {688				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(689					collection.id,690					token,691					sender.clone(),692					old_spender,693					0,694				));695			}696		}697	}698699	pub fn set_allowance(700		collection: &NonfungibleHandle<T>,701		sender: &T::CrossAccountId,702		token: TokenId,703		spender: Option<&T::CrossAccountId>,704	) -> DispatchResult {705		if collection.access == AccessMode::AllowList {706			collection.check_allowlist(sender)?;707			if let Some(spender) = spender {708				collection.check_allowlist(spender)?;709			}710		}711712		if let Some(spender) = spender {713			<PalletCommon<T>>::ensure_correct_receiver(spender)?;714		}715		let token_data =716			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;717		if &token_data.owner != sender {718			ensure!(719				collection.ignores_owned_amount(sender),720				<CommonError<T>>::CantApproveMoreThanOwned721			);722		}723724		// =========725726		Self::set_allowance_unchecked(collection, sender, token, spender, false);727		Ok(())728	}729730	fn check_allowed(731		collection: &NonfungibleHandle<T>,732		spender: &T::CrossAccountId,733		from: &T::CrossAccountId,734		token: TokenId,735		nesting_budget: &dyn Budget,736	) -> DispatchResult {737		if spender.conv_eq(from) {738			return Ok(());739		}740		if collection.access == AccessMode::AllowList {741			// `from`, `to` checked in [`transfer`]742			collection.check_allowlist(spender)?;743		}744		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {745			// TODO: should collection owner be allowed to perform this transfer?746			ensure!(747				<PalletStructure<T>>::check_indirectly_owned(748					spender.clone(),749					source.0,750					source.1,751					None,752					nesting_budget753				)?,754				<CommonError<T>>::ApprovedValueTooLow,755			);756			return Ok(());757		}758		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {759			return Ok(());760		}761		ensure!(762			collection.ignores_allowance(spender),763			<CommonError<T>>::ApprovedValueTooLow764		);765		Ok(())766	}767768	pub fn transfer_from(769		collection: &NonfungibleHandle<T>,770		spender: &T::CrossAccountId,771		from: &T::CrossAccountId,772		to: &T::CrossAccountId,773		token: TokenId,774		nesting_budget: &dyn Budget,775	) -> DispatchResult {776		Self::check_allowed(collection, spender, from, token, nesting_budget)?;777778		// =========779780		// Allowance is reset in [`transfer`]781		Self::transfer(collection, from, to, token, nesting_budget)782	}783784	pub fn burn_from(785		collection: &NonfungibleHandle<T>,786		spender: &T::CrossAccountId,787		from: &T::CrossAccountId,788		token: TokenId,789		nesting_budget: &dyn Budget,790	) -> DispatchResult {791		Self::check_allowed(collection, spender, from, token, nesting_budget)?;792793		// =========794795		Self::burn(collection, from, token)796	}797798	pub fn check_nesting(799		handle: &NonfungibleHandle<T>,800		sender: T::CrossAccountId,801		from: (CollectionId, TokenId),802		under: TokenId,803		nesting_budget: &dyn Budget,804	) -> DispatchResult {805		fn ensure_sender_allowed<T: Config>(806			collection: CollectionId,807			token: TokenId,808			for_nest: (CollectionId, TokenId),809			sender: T::CrossAccountId,810			budget: &dyn Budget,811		) -> DispatchResult {812			ensure!(813				<PalletStructure<T>>::check_indirectly_owned(814					sender,815					collection,816					token,817					Some(for_nest),818					budget819				)?,820				<CommonError<T>>::OnlyOwnerAllowedToNest,821			);822			Ok(())823		}824		match handle.limits.nesting_rule() {825			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),826			NestingRule::Owner => {827				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?828			}829			NestingRule::OwnerRestricted(whitelist) => {830				ensure!(831					whitelist.contains(&from.0),832					<CommonError<T>>::SourceCollectionIsNotAllowedToNest833				);834				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?835			}836		}837		Ok(())838	}839840	/// Delegated to `create_multiple_items`841	pub fn create_item(842		collection: &NonfungibleHandle<T>,843		sender: &T::CrossAccountId,844		data: CreateItemData<T>,845		nesting_budget: &dyn Budget,846	) -> DispatchResult {847		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)848	}849}
modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -61,6 +61,24 @@
 	}
 }
 
+// Selector: 56fd500b
+contract CollectionProperties is Dummy, ERC165 {
+	// Selector: setProperty(string,string) 62d9491f
+	function setProperty(string memory key, string memory value) public {
+		require(false, stub_error);
+		key;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: deleteProperty(string) 34241914
+	function deleteProperty(string memory key) public {
+		require(false, stub_error);
+		key;
+		dummy = 0;
+	}
+}
+
 // Selector: 58800161
 contract ERC721 is Dummy, ERC165, ERC721Events {
 	// Selector: balanceOf(address) 70a08231
@@ -276,7 +294,7 @@
 	}
 }
 
-// Selector: e562194d
+// Selector: d74d154f
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	// Selector: transfer(address,uint256) a9059cbb
 	function transfer(address to, uint256 tokenId) public {
@@ -301,26 +319,6 @@
 		return 0;
 	}
 
-	// Selector: setVariableMetadata(uint256,bytes) d4eac26d
-	function setVariableMetadata(uint256 tokenId, bytes memory data) public {
-		require(false, stub_error);
-		tokenId;
-		data;
-		dummy = 0;
-	}
-
-	// Selector: getVariableMetadata(uint256) e6c5ce6f
-	function getVariableMetadata(uint256 tokenId)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return hex"";
-	}
-
 	// Selector: mintBulk(address,uint256[]) 44a9945e
 	function mintBulk(address to, uint256[] memory tokenIds)
 		public
@@ -354,5 +352,6 @@
 	ERC721Enumerable,
 	ERC721UniqueExtensions,
 	ERC721Mintable,
-	ERC721Burnable
+	ERC721Burnable,
+	CollectionProperties
 {}
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -45,7 +45,6 @@
 	fn approve() -> Weight;
 	fn transfer_from() -> Weight;
 	fn burn_from() -> Weight;
-	fn set_variable_metadata(b: u32, ) -> Weight;
 }
 
 /// Weights for pallet_nonfungible using the Substrate node and recommended hardware.
@@ -155,12 +154,6 @@
 		(27_580_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
-	}
-	// Storage: Nonfungible TokenData (r:1 w:1)
-	fn set_variable_metadata(_b: u32, ) -> Weight {
-		(7_700_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
-			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 }
 
@@ -270,11 +263,5 @@
 		(27_580_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
-	}
-	// Storage: Nonfungible TokenData (r:1 w:1)
-	fn set_variable_metadata(_b: u32, ) -> Weight {
-		(7_700_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 }
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -24,6 +24,7 @@
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
+struct-versioning = { path = "../../crates/struct-versioning" }
 
 [features]
 default = ["std"]
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -31,10 +31,8 @@
 	users: impl IntoIterator<Item = (CrossAccountId, u128)>,
 ) -> CreateRefungibleExData<CrossAccountId> {
 	let const_data = create_data::<CUSTOM_DATA_LIMIT>();
-	let variable_data = create_data::<CUSTOM_DATA_LIMIT>();
 	CreateRefungibleExData {
 		const_data,
-		variable_data,
 		users: users
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
@@ -203,14 +201,4 @@
 		let item = create_max_item(&collection, &owner, [(sender.clone(), 200)])?;
 		<Pallet<T>>::set_allowance(&collection, &sender, &burner, item, 200)?;
 	}: {<Pallet<T>>::burn_from(&collection, &burner, &sender, item, 200, &Unlimited)?}
-
-	set_variable_metadata {
-		let b in 0..CUSTOM_DATA_LIMIT;
-		bench_init!{
-			owner: sub; collection: collection(owner);
-			sender: cross_from_sub(owner);
-		};
-		let item = create_max_item(&collection, &sender, [(sender.clone(), 200)])?;
-		let data = create_var_data(b).try_into().unwrap();
-	}: {<Pallet<T>>::set_variable_metadata(&collection, &sender, item, data)?}
 }
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -17,9 +17,9 @@
 use core::marker::PhantomData;
 
 use sp_std::collections::btree_map::BTreeMap;
-use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight, BoundedVec};
+use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};
 use up_data_structs::{
-	CollectionId, TokenId, CustomDataLimit, CreateItemExData, CreateRefungibleExData,
+	CollectionId, TokenId, CreateItemExData, CreateRefungibleExData,
 	budget::Budget, Property, PropertyKey, PropertyKeyPermission,
 };
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
@@ -110,10 +110,6 @@
 
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
-	}
-
-	fn set_variable_metadata(bytes: u32) -> Weight {
-		<SelfWeightOf<T>>::set_variable_metadata(bytes)
 	}
 }
 
@@ -124,7 +120,6 @@
 	match data {
 		up_data_structs::CreateItemData::ReFungible(data) => Ok(CreateRefungibleExData {
 			const_data: data.const_data,
-			variable_data: data.variable_data,
 			users: {
 				let mut out = BTreeMap::new();
 				out.insert(to.clone(), data.pieces);
@@ -306,19 +301,6 @@
 		fail!(<Error<T>>::SettingPropertiesNotAllowed)
 	}
 
-	fn set_variable_metadata(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		data: BoundedVec<u8, CustomDataLimit>,
-	) -> DispatchResultWithPostInfo {
-		let len = data.len();
-		with_weight(
-			<Pallet<T>>::set_variable_metadata(self, &sender, token, data),
-			<CommonWeights<T>>::set_variable_metadata(len as u32),
-		)
-	}
-
 	fn check_nesting(
 		&self,
 		_sender: <T>::CrossAccountId,
@@ -355,11 +337,6 @@
 	fn const_metadata(&self, token: TokenId) -> Vec<u8> {
 		<TokenData<T>>::get((self.id, token))
 			.const_data
-			.into_inner()
-	}
-	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
-		<TokenData<T>>::get((self.id, token))
-			.variable_data
 			.into_inner()
 	}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -41,16 +41,20 @@
 pub mod weights;
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
+#[struct_versioning::versioned(version = 2, upper)]
 #[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]
 pub struct ItemData {
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
+
+	#[version(..2)]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 }
 
 #[frame_support::pallet]
 pub mod pallet {
 	use super::*;
-	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};
+	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};
+	use frame_system::pallet_prelude::*;
 	use up_data_structs::{CollectionId, TokenId};
 	use super::weights::WeightInfo;
 
@@ -73,7 +77,10 @@
 		type WeightInfo: WeightInfo;
 	}
 
+	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+
 	#[pallet::pallet]
+	#[pallet::storage_version(STORAGE_VERSION)]
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
 
@@ -146,6 +153,19 @@
 		Value = u128,
 		QueryKind = ValueQuery,
 	>;
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		fn on_runtime_upgrade() -> Weight {
+			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {
+				<TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {
+					Some(<ItemDataVersion2>::from(v))
+				})
+			}
+
+			0
+		}
+	}
 }
 
 pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);
@@ -494,7 +514,6 @@
 				(collection.id, token_id),
 				ItemData {
 					const_data: token.const_data,
-					variable_data: token.variable_data,
 				},
 			);
 			for (user, amount) in token.users.into_iter() {
@@ -643,31 +662,6 @@
 		if let Some(allowance) = allowance {
 			Self::set_allowance_unchecked(collection, from, spender, token, allowance);
 		}
-		Ok(())
-	}
-
-	pub fn set_variable_metadata(
-		collection: &RefungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		token: TokenId,
-		data: BoundedVec<u8, CustomDataLimit>,
-	) -> DispatchResult {
-		collection.check_can_update_meta(
-			sender,
-			&T::CrossAccountId::from_sub(collection.owner.clone()),
-		)?;
-
-		let token_data = <TokenData<T>>::get((collection.id, token));
-
-		// =========
-
-		<TokenData<T>>::insert(
-			(collection.id, token),
-			ItemData {
-				variable_data: data,
-				..token_data
-			},
-		);
 		Ok(())
 	}
 
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -53,7 +53,6 @@
 	fn transfer_from_removing() -> Weight;
 	fn transfer_from_creating_removing() -> Weight;
 	fn burn_from() -> Weight;
-	fn set_variable_metadata(b: u32, ) -> Weight;
 }
 
 /// Weights for pallet_refungible using the Substrate node and recommended hardware.
@@ -242,12 +241,6 @@
 		(42_043_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(7 as Weight))
-	}
-	// Storage: Refungible TokenData (r:1 w:1)
-	fn set_variable_metadata(_b: u32, ) -> Weight {
-		(7_364_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
-			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 }
 
@@ -436,11 +429,5 @@
 		(42_043_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
-	}
-	// Storage: Refungible TokenData (r:1 w:1)
-	fn set_variable_metadata(_b: u32, ) -> Weight {
-		(7_364_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -38,7 +38,7 @@
 	CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
 	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
 	AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,
-	SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,
+	SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData,
 	CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,
 };
 use pallet_evm::account::CrossAccountId;
@@ -238,9 +238,6 @@
 		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
 		//#endregion
 
-		/// Variable metadata sponsoring
-		/// Collection id (controlled?2), token id (controlled?2)
-		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
 		/// Approval sponsoring
 		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
 		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
@@ -333,7 +330,6 @@
 			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);
 			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);
 
-			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);
 			<NftApproveBasket<T>>::remove_prefix(collection_id, None);
 			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);
 			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);
@@ -929,31 +925,6 @@
 			let budget = budget::Value::new(2);
 
 			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
-		}
-
-		/// Set off-chain data schema.
-		///
-		/// # Permissions
-		///
-		/// * Collection Owner
-		/// * Collection Admin
-		///
-		/// # Arguments
-		///
-		/// * collection_id.
-		///
-		/// * schema: String representing the offchain data schema.
-		#[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]
-		#[transactional]
-		pub fn set_variable_meta_data (
-			origin,
-			collection_id: CollectionId,
-			item_id: TokenId,
-			data: BoundedVec<u8, CustomDataLimit>,
-		) -> DispatchResultWithPostInfo {
-			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-
-			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))
 		}
 
 		/// Set meta_update_permission value for particular collection
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -364,28 +364,6 @@
 pub type CollectionPropertiesVec =
 	BoundedVec<Property, ConstU32<MAX_COLLECTION_PROPERTIES_ENCODE_LEN>>;
 
-#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
-#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct NftItemType<AccountId> {
-	pub owner: AccountId,
-	pub const_data: Vec<u8>,
-	pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]
-#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct FungibleItemType {
-	pub value: u128,
-}
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
-#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct ReFungibleItemType<AccountId> {
-	pub owner: Vec<Ownership<AccountId>>,
-	pub const_data: Vec<u8>,
-	pub variable_data: Vec<u8>,
-}
-
 /// All fields are wrapped in `Option`s, where None means chain default
 #[struct_versioning::versioned(version = 2, upper)]
 #[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
@@ -393,6 +371,8 @@
 pub struct CollectionLimits {
 	pub account_token_ownership_limit: Option<u32>,
 	pub sponsored_data_size: Option<u32>,
+
+	/// FIXME should we delete this or repurpose it?
 	/// None - setVariableMetadata is not sponsored
 	/// Some(v) - setVariableMetadata is sponsored
 	///           if there is v block between txs
@@ -490,9 +470,6 @@
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
-	#[derivative(Debug(format_with = "bounded::vec_debug"))]
-	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
@@ -512,9 +489,6 @@
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
-	#[derivative(Debug(format_with = "bounded::vec_debug"))]
-	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	pub pieces: u128,
 }
 
@@ -545,8 +519,6 @@
 pub struct CreateNftExData<CrossAccountId> {
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[derivative(Debug(format_with = "bounded::vec_debug"))]
-	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub properties: CollectionPropertiesVec,
 	pub owner: CrossAccountId,
@@ -557,8 +529,6 @@
 pub struct CreateRefungibleExData<CrossAccountId> {
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[derivative(Debug(format_with = "bounded::vec_debug"))]
-	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	#[derivative(Debug(format_with = "bounded::map_debug"))]
 	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
 }
@@ -586,8 +556,8 @@
 impl CreateItemData {
 	pub fn data_size(&self) -> usize {
 		match self {
-			CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),
-			CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),
+			CreateItemData::NFT(data) => data.const_data.len(),
+			CreateItemData::ReFungible(data) => data.const_data.len(),
 			_ => 0,
 		}
 	}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -42,7 +42,6 @@
 		fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
 		fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
 		fn const_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>>;
-		fn variable_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>>;
 
 		fn collection_properties(collection: CollectionId, properties: Vec<Vec<u8>>) -> Result<Vec<Property>>;
 
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -32,9 +32,6 @@
                 fn const_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>, DispatchError> {
                     dispatch_unique_runtime!(collection.const_metadata(token))
                 }
-                fn variable_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>, DispatchError> {
-                    dispatch_unique_runtime!(collection.variable_metadata(token))
-                }
 
                 fn collection_properties(
                     collection: CollectionId,
modifiedruntime/common/src/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/src/sponsoring.rs
+++ b/runtime/common/src/sponsoring.rs
@@ -21,7 +21,7 @@
 	storage::{StorageMap, StorageDoubleMap, StorageNMap},
 };
 use up_data_structs::{
-	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,
+	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
 	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode,
 	CreateItemData,
 };
@@ -30,7 +30,7 @@
 use pallet_evm::account::CrossAccountId;
 use pallet_unique::{
 	Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,
-	NftApproveBasket, VariableMetaDataBasket, CreateItemBasket, ReFungibleTransferBasket,
+	NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket,
 	FungibleTransferBasket, NftTransferBasket,
 };
 use pallet_fungible::Config as FungibleConfig;
@@ -139,64 +139,7 @@
 
 	Some(())
 }
-
-pub fn withdraw_set_variable_meta_data<T: Config>(
-	who: &T::CrossAccountId,
-	collection: &CollectionHandle<T>,
-	item_id: &TokenId,
-	data: &[u8],
-) -> Option<()> {
-	// TODO: make it work for admins
-	if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {
-		return None;
-	}
-	// preliminary sponsoring correctness check
-	match collection.mode {
-		CollectionMode::NFT => {
-			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
-			if !owner.conv_eq(who) {
-				return None;
-			}
-		}
-		CollectionMode::Fungible(_) => {
-			if item_id != &TokenId::default() {
-				return None;
-			}
-			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
-				return None;
-			}
-		}
-		CollectionMode::ReFungible => {
-			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
-				return None;
-			}
-		}
-	}
 
-	// Can't sponsor fungible collection, this tx will be rejected
-	// as invalid
-	if matches!(collection.mode, CollectionMode::Fungible(_)) {
-		return None;
-	}
-	if data.len() > collection.limits.sponsored_data_size() as usize {
-		return None;
-	}
-
-	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-	let limit = collection.limits.sponsored_data_rate_limit()?;
-
-	if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {
-		let timeout = last_tx_block + limit.into();
-		if block_number < timeout {
-			return None;
-		}
-	}
-
-	<VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);
-
-	Some(())
-}
-
 pub fn withdraw_approve<T: Config>(
 	collection: &CollectionHandle<T>,
 	who: &T::AccountId,
@@ -290,20 +233,6 @@
 			} => {
 				let (sponsor, collection) = load(*collection_id)?;
 				withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
-			}
-			UniqueCall::set_variable_meta_data {
-				collection_id,
-				item_id,
-				data,
-			} => {
-				let (sponsor, collection) = load(*collection_id)?;
-				withdraw_set_variable_meta_data::<T>(
-					&T::CrossAccountId::from_sub(who.clone()),
-					&collection,
-					item_id,
-					data,
-				)
-				.map(|()| sponsor)
 			}
 			_ => None,
 		}
modifiedruntime/common/src/weights.rsdiffbeforeafterboth
--- a/runtime/common/src/weights.rs
+++ b/runtime/common/src/weights.rs
@@ -86,10 +86,6 @@
 		dispatch_weight::<T>() + max_weight_of!(transfer_from())
 	}
 
-	fn set_variable_metadata(bytes: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(set_variable_metadata(bytes))
-	}
-
 	fn burn_from() -> Weight {
 		dispatch_weight::<T>() + max_weight_of!(burn_from())
 	}
modifiedruntime/tests/src/tests.rsdiffbeforeafterboth
--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -47,7 +47,6 @@
 fn default_nft_data() -> CreateNftData {
 	CreateNftData {
 		const_data: vec![1, 2, 3].try_into().unwrap(),
-		variable_data: vec![3, 2, 1].try_into().unwrap(),
 	}
 }
 
@@ -58,7 +57,6 @@
 fn default_re_fungible_data() -> CreateReFungibleData {
 	CreateReFungibleData {
 		const_data: vec![1, 2, 3].try_into().unwrap(),
-		variable_data: vec![3, 2, 1].try_into().unwrap(),
 		pieces: 1023,
 	}
 }
@@ -215,7 +213,6 @@
 
 		let item = <pallet_nonfungible::TokenData<Test>>::get((collection_id, 1)).unwrap();
 		assert_eq!(item.const_data, data.const_data.into_inner());
-		assert_eq!(item.variable_data, data.variable_data.into_inner());
 	});
 }
 
@@ -247,7 +244,6 @@
 			))
 			.unwrap();
 			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());
-			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());
 		}
 	});
 }
@@ -263,7 +259,6 @@
 		let balance =
 			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1)));
 		assert_eq!(item.const_data, data.const_data.into_inner());
-		assert_eq!(item.variable_data, data.variable_data.into_inner());
 		assert_eq!(balance, 1023);
 	});
 }
@@ -299,7 +294,6 @@
 			let balance =
 				<pallet_refungible::Balance<Test>>::get((CollectionId(1), TokenId(1), account(1)));
 			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());
-			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());
 			assert_eq!(balance, 1023);
 		}
 	});
@@ -413,7 +407,6 @@
 		create_test_item(collection_id, &data.clone().into());
 		let item = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));
 		assert_eq!(item.const_data, data.const_data.into_inner());
-		assert_eq!(item.variable_data, data.variable_data.into_inner());
 		assert_eq!(
 			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
 			1
@@ -2427,117 +2420,6 @@
 }
 
 #[test]
-fn set_variable_meta_data_on_nft_token_stores_variable_meta_data() {
-	new_test_ext().execute_with(|| {
-		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-
-		let data = default_nft_data();
-		create_test_item(CollectionId(1), &data.into());
-
-		let variable_data = b"test data".to_vec();
-		assert_ok!(Unique::set_variable_meta_data(
-			origin1,
-			collection_id,
-			TokenId(1),
-			variable_data.clone().try_into().unwrap()
-		));
-
-		assert_eq!(
-			<pallet_nonfungible::TokenData<Test>>::get((collection_id, 1))
-				.unwrap()
-				.variable_data,
-			variable_data
-		);
-	});
-}
-
-#[test]
-fn set_variable_meta_data_on_re_fungible_token_stores_variable_meta_data() {
-	new_test_ext().execute_with(|| {
-		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-
-		let data = default_re_fungible_data();
-		create_test_item(collection_id, &data.into());
-
-		let variable_data = b"test data".to_vec();
-		assert_ok!(Unique::set_variable_meta_data(
-			origin1,
-			collection_id,
-			TokenId(1),
-			variable_data.clone().try_into().unwrap()
-		));
-
-		assert_eq!(
-			<pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1))).variable_data,
-			variable_data
-		);
-	});
-}
-
-#[test]
-fn set_variable_meta_data_on_fungible_token_fails() {
-	new_test_ext().execute_with(|| {
-		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-
-		let data = default_fungible_data();
-		create_test_item(collection_id, &data.into());
-
-		let variable_data = b"test data".to_vec();
-		assert_noop!(
-			Unique::set_variable_meta_data(
-				origin1,
-				collection_id,
-				TokenId(0),
-				variable_data.try_into().unwrap()
-			)
-			.map_err(|e| e.error),
-			<pallet_fungible::Error<Test>>::FungibleItemsDontHaveData
-		);
-	});
-}
-
-#[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, CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-
-		let data = default_nft_data();
-		create_test_item(collection_id, &data.into());
-
-		assert_ok!(Unique::set_meta_update_permission_flag(
-			origin1.clone(),
-			collection_id,
-			MetaUpdatePermission::ItemOwner,
-		));
-
-		let variable_data = b"ten chars.".to_vec();
-		assert_ok!(Unique::set_variable_meta_data(
-			origin1,
-			collection_id,
-			TokenId(1),
-			variable_data.clone().try_into().unwrap()
-		));
-
-		assert_eq!(
-			<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))
-				.unwrap()
-				.variable_data,
-			variable_data
-		);
-	});
-}
-
-#[test]
 fn collection_transfer_flag_works() {
 	new_test_ext().execute_with(|| {
 		let origin1 = Origin::signed(1);
@@ -2590,105 +2472,6 @@
 }
 
 #[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, CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-		let origin2 = Origin::signed(2);
-
-		assert_ok!(Unique::set_mint_permission(
-			origin2.clone(),
-			collection_id,
-			true
-		));
-		assert_ok!(Unique::add_to_allow_list(
-			origin2.clone(),
-			collection_id,
-			account(1)
-		));
-
-		assert_ok!(Unique::add_collection_admin(
-			origin2.clone(),
-			collection_id,
-			account(1)
-		));
-
-		let data = default_nft_data();
-		create_test_item(collection_id, &data.into());
-
-		assert_ok!(Unique::set_meta_update_permission_flag(
-			origin2.clone(),
-			collection_id,
-			MetaUpdatePermission::Admin,
-		));
-
-		let variable_data = b"test.".to_vec();
-		assert_ok!(Unique::set_variable_meta_data(
-			origin1,
-			collection_id,
-			TokenId(1),
-			variable_data.clone().try_into().unwrap()
-		));
-
-		assert_eq!(
-			<pallet_nonfungible::TokenData<Test>>::get((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, CollectionId(1));
-
-		let origin1 = Origin::signed(1);
-		let origin2 = Origin::signed(2);
-
-		assert_ok!(Unique::set_mint_permission(
-			origin2.clone(),
-			collection_id,
-			true
-		));
-		assert_ok!(Unique::add_to_allow_list(
-			origin2.clone(),
-			collection_id,
-			account(1)
-		));
-
-		let data = default_nft_data();
-		create_test_item(collection_id, &data.into());
-
-		assert_ok!(Unique::set_meta_update_permission_flag(
-			origin2.clone(),
-			collection_id,
-			MetaUpdatePermission::Admin,
-		));
-
-		let variable_data = b"test.".to_vec();
-		assert_noop!(
-			Unique::set_variable_meta_data(
-				origin1,
-				collection_id,
-				TokenId(1),
-				variable_data.try_into().unwrap()
-			)
-			.map_err(|e| e.error),
-			CommonError::<Test>::NoPermission
-		);
-	});
-}
-
-#[test]
 fn set_variable_meta_flag_after_freeze() {
 	new_test_ext().execute_with(|| {
 		// default_limits();
@@ -2710,38 +2493,6 @@
 				MetaUpdatePermission::Admin
 			),
 			CommonError::<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, CollectionId(1));
-		let origin1 = Origin::signed(1);
-
-		let data = default_nft_data();
-		create_test_item(collection_id, &data.into());
-
-		assert_ok!(Unique::set_meta_update_permission_flag(
-			origin1.clone(),
-			collection_id,
-			MetaUpdatePermission::None,
-		));
-
-		let variable_data = b"test.".to_vec();
-		assert_noop!(
-			Unique::set_variable_meta_data(
-				origin1.clone(),
-				collection_id,
-				TokenId(1),
-				variable_data.try_into().unwrap()
-			)
-			.map_err(|e| e.error),
-			CommonError::<Test>::NoPermission
 		);
 	});
 }
modifiedsmart_contracs/transfer/lib.rsdiffbeforeafterboth
--- a/smart_contracs/transfer/lib.rs
+++ b/smart_contracs/transfer/lib.rs
@@ -58,14 +58,12 @@
 pub enum CreateItemData {
     Nft {
         const_data: Vec<u8>,
-        variable_data: Vec<u8>,
     },
     Fungible {
         value: u128,
     },
     ReFungible {
         const_data: Vec<u8>,
-        variable_data: Vec<u8>,
         pieces: u128,
     },
 }
@@ -88,8 +86,6 @@
     fn approve(spender: DefaultAccountId, collection_id: u32, item_id: u32, amount: u128);
     #[ink(extension = 4, returns_result = false)]
     fn transfer_from(owner: DefaultAccountId, recipient: DefaultAccountId, collection_id: u32, item_id: u32, amount: u128);
-    #[ink(extension = 5, returns_result = false)]
-    fn set_variable_meta_data(collection_id: u32, item_id: u32, data: Vec<u8>);
     #[ink(extension = 6, returns_result = false)]
     fn toggle_allow_list(collection_id: u32, address: DefaultAccountId, allowlisted: bool);
 }
@@ -143,12 +139,6 @@
             let _ = self.env()
                 .extension()
                 .transfer_from(owner, recipient, collection_id, item_id, amount);
-        }
-        #[ink(message)]
-        pub fn set_variable_meta_data(&mut self, collection_id: u32, item_id: u32, data: Vec<u8>) {
-            let _ = self.env()
-                .extension()
-                .set_variable_meta_data(collection_id, item_id, data);
         }
         #[ink(message)]
         pub fn toggle_allow_list(&mut self, collection_id: u32, address: AccountId, allowlisted: bool) {