git.delta.rocks / unique-network / refs/commits / 44071b633884

difftreelog

refactor use type-safe propertywriter to set/delete properties

Daniel Shiposha2023-09-30parent: #d2c9363.patch.diff
in: master

12 files changed

modifiedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -172,6 +172,20 @@
 		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
 	}
 
+	fn get_token_properties_map(&self, _token_id: TokenId) -> up_data_structs::TokenProperties {
+		// No token properties are defined on fungibles
+		up_data_structs::TokenProperties::new()
+	}
+
+	fn set_token_properties_map(&self, _token_id: TokenId, _map: up_data_structs::TokenProperties) {
+		// No token properties are defined on fungibles
+	}
+
+	fn properties_exist(&self, _token: TokenId) -> bool {
+		// No token properties are defined on fungibles
+		false
+	}
+
 	fn set_token_property_permissions(
 		&self,
 		_sender: &<T>::CrossAccountId,
@@ -277,6 +291,15 @@
 		Err(up_data_structs::TokenOwnerError::MultipleOwners)
 	}
 
+	fn check_token_indirect_owner(
+		&self,
+		_token: TokenId,
+		_maybe_owner: &<T>::CrossAccountId,
+		_nesting_budget: &dyn up_data_structs::budget::Budget,
+	) -> Result<bool, frame_support::sp_runtime::DispatchError> {
+		Ok(false)
+	}
+
 	fn token_owners(&self, _token: TokenId) -> Vec<<T>::CrossAccountId> {
 		vec![]
 	}
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -22,8 +22,9 @@
 use frame_benchmarking::{benchmarks, account};
 use up_data_structs::{
 	CollectionMode, CreateCollectionData, CollectionId, Property, PropertyKey, PropertyValue,
-	CollectionPermissions, NestingPermissions, AccessMode, MAX_COLLECTION_NAME_LENGTH,
-	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, MAX_PROPERTIES_PER_ITEM,
+	CollectionPermissions, NestingPermissions, AccessMode, PropertiesPermissionMap,
+	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+	MAX_PROPERTIES_PER_ITEM,
 };
 use frame_support::{
 	traits::{Get, fungible::Balanced, Imbalance, tokens::Precision},
@@ -123,6 +124,16 @@
 	)
 }
 
+pub fn load_is_admin_and_property_permissions<T: Config>(
+	collection: &CollectionHandle<T>,
+	sender: &T::CrossAccountId,
+) -> (bool, PropertiesPermissionMap) {
+	(
+		collection.is_owner_or_admin(sender),
+		<Pallet<T>>::property_permissions(collection.id),
+	)
+}
+
 /// Helper macros, which handles all benchmarking preparation in semi-declarative way
 ///
 /// `name` is a substrate account
@@ -215,4 +226,12 @@
 		assert_eq!(collection_handle.permissions.access(), AccessMode::AllowList);
 
 	}: {collection_handle.check_allowlist(&sender)?;}
+
+	init_token_properties_common {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: sub;
+			sender: cross_from_sub(sender);
+		};
+	}: {load_is_admin_and_property_permissions(&collection, &sender);}
 }
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -56,6 +56,7 @@
 use core::{
 	ops::{Deref, DerefMut},
 	slice::from_ref,
+	marker::PhantomData,
 };
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
 use sp_std::vec::Vec;
@@ -97,6 +98,9 @@
 pub mod helpers;
 #[allow(missing_docs)]
 pub mod weights;
+
+use weights::WeightInfo;
+
 /// Weight info.
 pub type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
@@ -864,19 +868,7 @@
 		QueryKind = OptionQuery,
 	>;
 }
-
-/// Represents the change mode for the token property.
-pub enum SetPropertyMode {
-	/// The token already exists.
-	ExistingToken,
 
-	/// New token.
-	NewToken {
-		/// The creator of the token is the recipient.
-		mint_target_is_sender: bool,
-	},
-}
-
 /// Value representation with delayed initialization time.
 pub struct LazyValue<T, F: FnOnce() -> T> {
 	value: Option<T>,
@@ -892,19 +884,33 @@
 		}
 	}
 
-	/// Get the value. If it call furst time the value will be initialized.
+	/// Get the value. If it is called the first time, the value will be initialized.
 	pub fn value(&mut self) -> &T {
-		if self.value.is_none() {
-			self.value = Some(self.f.take().unwrap()())
-		}
+		self.compute_value_if_not_already();
+		self.value.as_ref().unwrap()
+	}
 
-		self.value.as_ref().unwrap()
+	/// Get the value. If it is called the first time, the value will be initialized.
+	pub fn value_mut(&mut self) -> &mut T {
+		self.compute_value_if_not_already();
+		self.value.as_mut().unwrap()
 	}
 
-	/// Is value initialized.
+	fn into_inner(mut self) -> T {
+		self.compute_value_if_not_already();
+		self.value.unwrap()
+	}
+
+	/// Is value initialized?
 	pub fn has_value(&self) -> bool {
 		self.value.is_some()
 	}
+
+	fn compute_value_if_not_already(&mut self) {
+		if self.value.is_none() {
+			self.value = Some(self.f.take().unwrap()())
+		}
+	}
 }
 
 fn check_token_permissions<T, FCA, FTO, FTE>(
@@ -926,10 +932,19 @@
 		fail!(<Error<T>>::NoPermission);
 	}
 
-	let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;
-	if !token_certainly_exist && !is_token_exist.value() {
-		fail!(<Error<T>>::TokenNotFound);
+	let token_exist_due_to_owner_check_success =
+		is_token_owner.has_value() && (*is_token_owner.value())?;
+
+	// If the token owner check has occurred and succeeded,
+	// we know the token exists (otherwise, the owner check must fail).
+	if !token_exist_due_to_owner_check_success {
+		// If the token owner check didn't occur,
+		// we must check the token's existence ourselves.
+		if !is_token_exist.value() {
+			fail!(<Error<T>>::TokenNotFound);
+		}
 	}
+
 	Ok(())
 }
 
@@ -1312,92 +1327,6 @@
 		Ok(())
 	}
 
-	/// A batch operation to add, edit or remove properties for a token.
-	/// It sets or removes a token's properties according to
-	/// `properties_updates` contents:
-	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`
-	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.
-	///
-	/// All affected properties should have `mutable` permission
-	/// to be **deleted** or to be **set more than once**,
-	/// and the sender should have permission to edit those properties.
-	///
-	/// This function fires an event for each property change.
-	/// In case of an error, all the changes (including the events) will be reverted
-	/// since the function is transactional.
-	#[allow(clippy::too_many_arguments)]
-	pub fn modify_token_properties<FTO, FTE>(
-		collection: &CollectionHandle<T>,
-		sender: &T::CrossAccountId,
-		token_id: TokenId,
-		is_token_exist: &mut LazyValue<bool, FTE>,
-		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
-		mut stored_properties: TokenProperties,
-		is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,
-		set_token_properties: impl FnOnce(TokenProperties),
-		log: evm_coder::ethereum::Log,
-	) -> DispatchResult
-	where
-		FTO: FnOnce() -> Result<bool, DispatchError>,
-		FTE: FnOnce() -> bool,
-	{
-		let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));
-		let mut permissions = LazyValue::new(|| Self::property_permissions(collection.id));
-
-		let mut changed = false;
-		for (key, value) in properties_updates {
-			let permission = permissions
-				.value()
-				.get(&key)
-				.cloned()
-				.unwrap_or_else(PropertyPermission::none);
-
-			let property_exists = stored_properties.get(&key).is_some();
-
-			match permission {
-				PropertyPermission { mutable: false, .. } if property_exists => {
-					return Err(<Error<T>>::NoPermission.into());
-				}
-
-				PropertyPermission {
-					collection_admin,
-					token_owner,
-					..
-				} => check_token_permissions::<T, _, FTO, FTE>(
-					collection_admin,
-					token_owner,
-					&mut is_collection_admin,
-					is_token_owner,
-					is_token_exist,
-				)?,
-			}
-
-			match value {
-				Some(value) => {
-					stored_properties
-						.try_set(key.clone(), value)
-						.map_err(<Error<T>>::from)?;
-
-					Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));
-				}
-				None => {
-					stored_properties.remove(&key).map_err(<Error<T>>::from)?;
-
-					Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));
-				}
-			}
-
-			changed = true;
-		}
-
-		if changed {
-			<PalletEvm<T>>::deposit_log(log);
-			set_token_properties(stored_properties);
-		}
-
-		Ok(())
-	}
-
 	/// Sets or unsets the approval of a given operator.
 	///
 	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.
@@ -2166,6 +2095,22 @@
 		budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo;
 
+	/// Get token properties raw map.
+	///
+	/// * `token_id` - The token which properties are needed.
+	fn get_token_properties_map(&self, token_id: TokenId) -> TokenProperties;
+
+	/// Set token properties raw map.
+	///
+	/// * `token_id` - The token for which the properties are being set.
+	/// * `map` - The raw map containing the token's properties.
+	fn set_token_properties_map(&self, token_id: TokenId, map: TokenProperties);
+
+	/// Whether the given token has properties.
+	///
+	/// * `token_id` - The token in question.
+	fn properties_exist(&self, token: TokenId) -> bool;
+
 	/// Set token property permissions.
 	///
 	/// * `sender` - Must be either the owner of the token or its admin.
@@ -2309,6 +2254,18 @@
 	/// * `token` - The token for which you need to find out the owner.
 	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;
 
+	/// Checks if the `maybe_owner` is the indirect owner of the `token`.
+	///
+	/// * `token` - Id token to check.
+	/// * `maybe_owner` - The account to check.
+	/// * `nesting_budget` - A budget that can be spent on nesting tokens.
+	fn check_token_indirect_owner(
+		&self,
+		token: TokenId,
+		maybe_owner: &T::CrossAccountId,
+		nesting_budget: &dyn Budget,
+	) -> Result<bool, DispatchError>;
+
 	/// Returns 10 tokens owners in no particular order.
 	///
 	/// * `token` - The token for which you need to find out the owners.
@@ -2420,6 +2377,348 @@
 	}
 }
 
+/// A marker structure that enables the writer implementation
+/// to provide the interface to write properties to **newly created** tokens.
+pub struct NewTokenPropertyWriter;
+
+/// A marker structure that enables the writer implementation
+/// to provide the interface to write properties to **already existing** tokens.
+pub struct ExistingTokenPropertyWriter;
+
+/// The type-safe interface for writing properties (setting or deleting) to tokens.
+/// It has two distinct implementations for newly created tokens and existing ones.
+///
+/// This type utilizes the lazy evaluation to avoid repeating the computation
+/// of several performance-heavy or PoV-heavy tasks,
+/// such as checking the indirect ownership or reading the token property permissions.
+pub struct PropertyWriter<
+	'a,
+	T,
+	Handle,
+	WriterVariant,
+	FIsAdmin,
+	FPropertyPermissions,
+	FCheckTokenExist,
+	FGetProperties,
+> where
+	T: Config,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
+{
+	collection: &'a Handle,
+	is_collection_admin: LazyValue<bool, FIsAdmin>,
+	property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,
+	check_token_exist: FCheckTokenExist,
+	get_properties: FGetProperties,
+	_phantom: PhantomData<(T, WriterVariant)>,
+}
+
+impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>
+	PropertyWriter<
+		'a,
+		T,
+		Handle,
+		NewTokenPropertyWriter,
+		FIsAdmin,
+		FPropertyPermissions,
+		FCheckTokenExist,
+		FGetProperties,
+	> where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
+	FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
+	FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
+{
+	/// A function to write properties to a **newly created** token.
+	pub fn write_token_properties(
+		&mut self,
+		mint_target_is_sender: bool,
+		token_id: TokenId,
+		properties_updates: impl Iterator<Item = Property>,
+		log: evm_coder::ethereum::Log,
+	) -> DispatchResult {
+		self.internal_write_token_properties(
+			token_id,
+			properties_updates.map(|p| (p.key, Some(p.value))),
+			|_| Ok(mint_target_is_sender),
+			log,
+		)
+	}
+}
+
+impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>
+	PropertyWriter<
+		'a,
+		T,
+		Handle,
+		ExistingTokenPropertyWriter,
+		FIsAdmin,
+		FPropertyPermissions,
+		FCheckTokenExist,
+		FGetProperties,
+	> where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
+	FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
+	FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
+{
+	/// A function to write properties to an **already existing** token.
+	pub fn write_token_properties(
+		&mut self,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
+		nesting_budget: &dyn Budget,
+		log: evm_coder::ethereum::Log,
+	) -> DispatchResult {
+		self.internal_write_token_properties(
+			token_id,
+			properties_updates,
+			|collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),
+			log,
+		)
+	}
+}
+
+impl<
+		'a,
+		T,
+		Handle,
+		WriterVariant,
+		FIsAdmin,
+		FPropertyPermissions,
+		FCheckTokenExist,
+		FGetProperties,
+	>
+	PropertyWriter<
+		'a,
+		T,
+		Handle,
+		WriterVariant,
+		FIsAdmin,
+		FPropertyPermissions,
+		FCheckTokenExist,
+		FGetProperties,
+	> where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
+	FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
+	FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
+{
+	fn internal_write_token_properties<FCheckTokenOwner>(
+		&mut self,
+		token_id: TokenId,
+		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
+		check_token_owner: FCheckTokenOwner,
+		log: evm_coder::ethereum::Log,
+	) -> DispatchResult
+	where
+		FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,
+	{
+		let get_properties = self.get_properties;
+		let mut stored_properties = LazyValue::new(move || get_properties(token_id));
+
+		let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));
+
+		let check_token_exist = self.check_token_exist;
+		let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));
+
+		for (key, value) in properties_updates {
+			let permission = self
+				.property_permissions
+				.value()
+				.get(&key)
+				.cloned()
+				.unwrap_or_else(PropertyPermission::none);
+
+			match permission {
+				PropertyPermission { mutable: false, .. }
+					if stored_properties.value().get(&key).is_some() =>
+				{
+					return Err(<Error<T>>::NoPermission.into());
+				}
+
+				PropertyPermission {
+					collection_admin,
+					token_owner,
+					..
+				} => check_token_permissions::<T, _, _, _>(
+					collection_admin,
+					token_owner,
+					&mut self.is_collection_admin,
+					&mut is_token_owner,
+					&mut is_token_exist,
+				)?,
+			}
+
+			match value {
+				Some(value) => {
+					stored_properties
+						.value_mut()
+						.try_set(key.clone(), value)
+						.map_err(<Error<T>>::from)?;
+
+					<Pallet<T>>::deposit_event(Event::TokenPropertySet(
+						self.collection.id,
+						token_id,
+						key,
+					));
+				}
+				None => {
+					stored_properties
+						.value_mut()
+						.remove(&key)
+						.map_err(<Error<T>>::from)?;
+
+					<Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(
+						self.collection.id,
+						token_id,
+						key,
+					));
+				}
+			}
+		}
+
+		let properties_changed = stored_properties.has_value();
+		if properties_changed {
+			<PalletEvm<T>>::deposit_log(log);
+
+			self.collection
+				.set_token_properties_map(token_id, stored_properties.into_inner());
+		}
+
+		Ok(())
+	}
+}
+
+/// Create a [`PropertyWriter`] for newly created tokens.
+pub fn property_writer_for_new_token<'a, T, Handle>(
+	collection: &'a Handle,
+	sender: &'a T::CrossAccountId,
+) -> PropertyWriter<
+	'a,
+	T,
+	Handle,
+	NewTokenPropertyWriter,
+	impl FnOnce() -> bool + 'a,
+	impl FnOnce() -> PropertiesPermissionMap + 'a,
+	impl Copy + FnOnce(TokenId) -> bool + 'a,
+	impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,
+>
+where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+{
+	PropertyWriter {
+		collection,
+		is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
+		property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),
+		check_token_exist: |token_id| {
+			debug_assert!(collection.token_exists(token_id));
+			true
+		},
+		get_properties: |token_id| {
+			debug_assert!(!collection.properties_exist(token_id));
+			TokenProperties::new()
+		},
+		_phantom: PhantomData,
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+/// Create a `PropertyWriter` with preloaded `is_collection_admin` and `property_permissions.
+/// Also:
+/// * it will return `true` for the token ownership check.
+/// * it will return empty stored properties without reading them from the storage.
+pub fn collection_info_loaded_property_writer<T, Handle>(
+	collection: &Handle,
+	is_collection_admin: bool,
+	property_permissions: PropertiesPermissionMap,
+) -> PropertyWriter<
+	T,
+	Handle,
+	NewTokenPropertyWriter,
+	impl FnOnce() -> bool,
+	impl FnOnce() -> PropertiesPermissionMap,
+	impl Copy + FnOnce(TokenId) -> bool,
+	impl Copy + FnOnce(TokenId) -> TokenProperties,
+>
+where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+{
+	PropertyWriter {
+		collection,
+		is_collection_admin: LazyValue::new(move || is_collection_admin),
+		property_permissions: LazyValue::new(move || property_permissions),
+		check_token_exist: |_token_id| true,
+		get_properties: |_token_id| TokenProperties::new(),
+		_phantom: PhantomData,
+	}
+}
+
+/// Create a [`PropertyWriter`] for already existing tokens.
+pub fn property_writer_for_existing_token<'a, T, Handle>(
+	collection: &'a Handle,
+	sender: &'a T::CrossAccountId,
+) -> PropertyWriter<
+	'a,
+	T,
+	Handle,
+	ExistingTokenPropertyWriter,
+	impl FnOnce() -> bool + 'a,
+	impl FnOnce() -> PropertiesPermissionMap + 'a,
+	impl Copy + FnOnce(TokenId) -> bool + 'a,
+	impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,
+>
+where
+	T: Config,
+	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+{
+	PropertyWriter {
+		collection,
+		is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
+		property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),
+		check_token_exist: |token_id| collection.token_exists(token_id),
+		get_properties: |token_id| collection.get_token_properties_map(token_id),
+		_phantom: PhantomData,
+	}
+}
+
+/// Computes the weight delta for newly created tokens with properties.
+/// * `properties_nums` - The properties num of each created token.
+/// * `init_token_properties` - The function to obtain the weight from a token's properties num.
+pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(
+	properties_nums: impl Iterator<Item = u32>,
+	init_token_properties: I,
+) -> Weight {
+	let mut delta = properties_nums
+		.filter_map(|properties_num| {
+			if properties_num > 0 {
+				Some(init_token_properties(properties_num))
+			} else {
+				None
+			}
+		})
+		.fold(Weight::zero(), |a, b| a.saturating_add(b));
+
+	// If at least once the `init_token_properties` was called,
+	// it means at least one newly created token has properties.
+	// Becuase of that, some common collection data also was loaded and we need to add this weight.
+	// However, these common data was loaded only once which is guaranteed by the `PropertyWriter`.
+	if !delta.is_zero() {
+		delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())
+	}
+
+	delta
+}
+
 #[cfg(any(feature = "tests", test))]
 #[allow(missing_docs)]
 pub mod tests {
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -25,7 +25,7 @@
 	weights::WeightInfo as _, SelfWeightOf as PalletCommonWeightOf,
 };
 use pallet_structure::Error as StructureError;
-use sp_runtime::ArithmeticError;
+use sp_runtime::{ArithmeticError, DispatchError};
 use sp_std::{vec::Vec, vec};
 use up_data_structs::{Property, PropertyKey, PropertyValue, PropertyKeyPermission};
 
@@ -364,6 +364,20 @@
 		fail!(<Error<T>>::SettingPropertiesNotAllowed)
 	}
 
+	fn get_token_properties_map(&self, _token_id: TokenId) -> up_data_structs::TokenProperties {
+		// No token properties are defined on fungibles
+		up_data_structs::TokenProperties::new()
+	}
+
+	fn set_token_properties_map(&self, _token_id: TokenId, _map: up_data_structs::TokenProperties) {
+		// No token properties are defined on fungibles
+	}
+
+	fn properties_exist(&self, _token: TokenId) -> bool {
+		// No token properties are defined on fungibles
+		false
+	}
+
 	fn check_nesting(
 		&self,
 		_sender: <T>::CrossAccountId,
@@ -402,6 +416,15 @@
 		Err(TokenOwnerError::MultipleOwners)
 	}
 
+	fn check_token_indirect_owner(
+		&self,
+		_token: TokenId,
+		_maybe_owner: &T::CrossAccountId,
+		_nesting_budget: &dyn Budget,
+	) -> Result<bool, DispatchError> {
+		Ok(false)
+	}
+
 	/// Returns 10 tokens owners in no particular order.
 	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
 		<Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -20,7 +20,9 @@
 use frame_benchmarking::{benchmarks, account};
 use pallet_common::{
 	bench_init,
-	benchmarking::{create_collection_raw, property_key, property_value},
+	benchmarking::{
+		create_collection_raw, property_key, property_value, load_is_admin_and_property_permissions,
+	},
 	CommonCollectionOperations,
 };
 use sp_std::prelude::*;
@@ -198,14 +200,15 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::ExistingToken, &Unlimited)?}
+	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?}
 
-	reset_token_properties {
+	init_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
 		bench_init!{
 			owner: sub; collection: collection(owner);
 			owner: cross_from_sub;
 		};
+
 		let perms = (0..b).map(|k| PropertyKeyPermission {
 			key: property_key(k as usize),
 			permission: PropertyPermission {
@@ -220,8 +223,26 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::NewToken { mint_target_is_sender: true }, &Unlimited)?}
 
+		let (is_collection_admin, property_permissions) = load_is_admin_and_property_permissions(&collection, &owner);
+	}: {
+		let mut property_writer = pallet_common::collection_info_loaded_property_writer(
+			&collection,
+			is_collection_admin,
+			property_permissions,
+		);
+
+		property_writer.write_token_properties(
+			true,
+			item,
+			props.into_iter(),
+			crate::erc::ERC721TokenEvent::TokenChanged {
+				token_id: item.into(),
+			}
+			.to_log(T::ContractAddress::get()),
+		)?
+	}
+
 	delete_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
 		bench_init!{
@@ -242,7 +263,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::ExistingToken, &Unlimited)?;
+		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?;
 		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
 
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -23,49 +23,40 @@
 };
 use pallet_common::{
 	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
-	weights::WeightInfo as _, SelfWeightOf as PalletCommonWeightOf,
+	weights::WeightInfo as _, SelfWeightOf as PalletCommonWeightOf, init_token_properties_delta,
 };
+use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::DispatchError;
 use sp_std::{vec::Vec, vec};
 
 use crate::{
 	AccountBalance, Allowance, Config, CreateItemData, Error, NonfungibleHandle, Owned, Pallet,
-	SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,
+	SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted, TokenProperties,
 };
 
 pub struct CommonWeights<T: Config>(PhantomData<T>);
 impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
 		match data {
-			CreateItemExData::NFT(t) => {
-				<SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32)
-					+ t.iter()
-						.filter_map(|t| {
-							if t.properties.len() > 0 {
-								Some(<SelfWeightOf<T>>::reset_token_properties(
-									t.properties.len() as u32,
-								))
-							} else {
-								None
-							}
-						})
-						.fold(Weight::zero(), |a, b| a.saturating_add(b))
-			}
+			CreateItemExData::NFT(t) => <SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32)
+				.saturating_add(init_token_properties_delta::<T, _>(
+					t.iter().map(|t| t.properties.len() as u32),
+					<SelfWeightOf<T>>::init_token_properties,
+				)),
 			_ => Weight::zero(),
 		}
 	}
 
 	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
-		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32)
-			+ data
-				.iter()
-				.filter_map(|t| match t {
-					up_data_structs::CreateItemData::NFT(n) if n.properties.len() > 0 => Some(
-						<SelfWeightOf<T>>::reset_token_properties(n.properties.len() as u32),
-					),
-					_ => None,
-				})
-				.fold(Weight::zero(), |a, b| a.saturating_add(b))
+		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
+			init_token_properties_delta::<T, _>(
+				data.iter().map(|t| match t {
+					up_data_structs::CreateItemData::NFT(n) => n.properties.len() as u32,
+					_ => 0,
+				}),
+				<SelfWeightOf<T>>::init_token_properties,
+			),
+		)
 	}
 
 	fn burn_item() -> Weight {
@@ -247,7 +238,6 @@
 				&sender,
 				token_id,
 				properties.into_iter(),
-				pallet_common::SetPropertyMode::ExistingToken,
 				nesting_budget,
 			),
 			weight,
@@ -275,6 +265,14 @@
 		)
 	}
 
+	fn get_token_properties_map(&self, token_id: TokenId) -> up_data_structs::TokenProperties {
+		<TokenProperties<T>>::get((self.id, token_id))
+	}
+
+	fn set_token_properties_map(&self, token_id: TokenId, map: up_data_structs::TokenProperties) {
+		<TokenProperties<T>>::set((self.id, token_id), map)
+	}
+
 	fn set_token_property_permissions(
 		&self,
 		sender: &T::CrossAccountId,
@@ -289,6 +287,10 @@
 		)
 	}
 
+	fn properties_exist(&self, token: TokenId) -> bool {
+		<TokenProperties<T>>::contains_key((self.id, token))
+	}
+
 	fn burn_item(
 		&self,
 		sender: T::CrossAccountId,
@@ -459,6 +461,21 @@
 			.ok_or(TokenOwnerError::NotFound)
 	}
 
+	fn check_token_indirect_owner(
+		&self,
+		token: TokenId,
+		maybe_owner: &T::CrossAccountId,
+		nesting_budget: &dyn Budget,
+	) -> Result<bool, DispatchError> {
+		<PalletStructure<T>>::check_indirectly_owned(
+			maybe_owner.clone(),
+			self.id,
+			token,
+			None,
+			nesting_budget,
+		)
+	}
+
 	/// Returns token owners.
 	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
 		self.token_owner(token).map_or_else(|_| vec![], |t| vec![t])
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -203,7 +203,6 @@
 			&caller,
 			TokenId(token_id),
 			properties.into_iter(),
-			pallet_common::SetPropertyMode::ExistingToken,
 			&nesting_budget,
 		)
 		.map_err(dispatch_to_evm::<T>)
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -109,7 +109,7 @@
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
 	eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,
-	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info, SetPropertyMode,
+	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,
 };
 use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -598,58 +598,16 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
-		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let mut is_token_owner = pallet_common::LazyValue::new(|| {
-			if let SetPropertyMode::NewToken {
-				mint_target_is_sender,
-			} = mode
-			{
-				return Ok(mint_target_is_sender);
-			}
-
-			let is_owned = <PalletStructure<T>>::check_indirectly_owned(
-				sender.clone(),
-				collection.id,
-				token_id,
-				None,
-				nesting_budget,
-			)?;
-
-			Ok(is_owned)
-		});
-
-		let is_new_token = matches!(mode, SetPropertyMode::NewToken { .. });
-
-		let mut is_token_exist = pallet_common::LazyValue::new(|| {
-			if is_new_token {
-				debug_assert!(Self::token_exists(collection, token_id));
-				true
-			} else {
-				Self::token_exists(collection, token_id)
-			}
-		});
-
-		let stored_properties = if is_new_token {
-			debug_assert!(!<TokenProperties<T>>::contains_key((
-				collection.id,
-				token_id
-			)));
-			TokenPropertiesT::new()
-		} else {
-			<TokenProperties<T>>::get((collection.id, token_id))
-		};
+		let mut property_writer =
+			pallet_common::property_writer_for_existing_token(collection, sender);
 
-		<PalletCommon<T>>::modify_token_properties(
-			collection,
+		property_writer.write_token_properties(
 			sender,
 			token_id,
-			&mut is_token_exist,
 			properties_updates,
-			stored_properties,
-			&mut is_token_owner,
-			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+			nesting_budget,
 			erc::ERC721TokenEvent::TokenChanged {
 				token_id: token_id.into(),
 			}
@@ -680,7 +638,6 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties: impl Iterator<Item = Property>,
-		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::modify_token_properties(
@@ -688,7 +645,6 @@
 			sender,
 			token_id,
 			properties.map(|p| (p.key, Some(p.value))),
-			mode,
 			nesting_budget,
 		)
 	}
@@ -710,7 +666,6 @@
 			sender,
 			token_id,
 			[property].into_iter(),
-			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -732,7 +687,6 @@
 			sender,
 			token_id,
 			property_keys.into_iter().map(|key| (key, None)),
-			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -994,6 +948,8 @@
 
 		// =========
 
+		let mut property_writer = pallet_common::property_writer_for_new_token(collection, sender);
+
 		with_transaction(|| {
 			for (i, data) in data.iter().enumerate() {
 				let token = first_token + i as u32 + 1;
@@ -1006,21 +962,22 @@
 					},
 				);
 
+				let token = TokenId(token);
+
 				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
 					&data.owner,
 					collection.id,
-					TokenId(token),
+					token,
 				);
 
-				if let Err(e) = Self::set_token_properties(
-					collection,
-					sender,
-					TokenId(token),
+				if let Err(e) = property_writer.write_token_properties(
+					sender.conv_eq(&data.owner),
+					token,
 					data.properties.clone().into_iter(),
-					SetPropertyMode::NewToken {
-						mint_target_is_sender: sender.conv_eq(&data.owner),
-					},
-					nesting_budget,
+					erc::ERC721TokenEvent::TokenChanged {
+						token_id: token.into(),
+					}
+					.to_log(T::ContractAddress::get()),
 				) {
 					return TransactionOutcome::Rollback(Err(e));
 				}
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -22,7 +22,9 @@
 use frame_benchmarking::{benchmarks, account};
 use pallet_common::{
 	bench_init,
-	benchmarking::{create_collection_raw, property_key, property_value},
+	benchmarking::{
+		create_collection_raw, property_key, property_value, load_is_admin_and_property_permissions,
+	},
 };
 use sp_std::prelude::*;
 use up_data_structs::{
@@ -255,14 +257,15 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::ExistingToken, &Unlimited)?}
+	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?}
 
-	reset_token_properties {
+	init_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
 		bench_init!{
 			owner: sub; collection: collection(owner);
 			owner: cross_from_sub;
 		};
+
 		let perms = (0..b).map(|k| PropertyKeyPermission {
 			key: property_key(k as usize),
 			permission: PropertyPermission {
@@ -277,8 +280,26 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::NewToken { mint_target_is_sender: true }, &Unlimited)?}
 
+		let (is_collection_admin, property_permissions) = load_is_admin_and_property_permissions(&collection, &owner);
+	}: {
+		let mut property_writer = pallet_common::collection_info_loaded_property_writer(
+			&collection,
+			is_collection_admin,
+			property_permissions,
+		);
+
+		property_writer.write_token_properties(
+			true,
+			item,
+			props.into_iter(),
+			crate::erc::ERC721TokenEvent::TokenChanged {
+				token_id: item.into(),
+			}
+			.to_log(T::ContractAddress::get()),
+		)?
+	}
+
 	delete_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
 		bench_init!{
@@ -299,7 +320,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
-		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), SetPropertyMode::ExistingToken, &Unlimited)?;
+		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?;
 		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
 
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
after · pallets/refungible/src/common.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/>.1617use core::marker::PhantomData;1819use sp_std::collections::btree_map::BTreeMap;20use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};21use up_data_structs::{22	CollectionId, TokenId, CreateItemExData, budget::Budget, Property, PropertyKey, PropertyValue,23	PropertyKeyPermission, CreateRefungibleExMultipleOwners, CreateRefungibleExSingleOwner,24	TokenOwnerError,25};26use pallet_common::{27	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,28	weights::WeightInfo as _, init_token_properties_delta,29};30use pallet_structure::{Pallet as PalletStructure, Error as StructureError};31use sp_runtime::{DispatchError};32use sp_std::{vec::Vec, vec};3334use crate::{35	AccountBalance, Allowance, Balance, Config, Error, Owned, Pallet, RefungibleHandle,36	SelfWeightOf, weights::WeightInfo, TokensMinted, TotalSupply, CreateItemData, TokenProperties,37};3839macro_rules! max_weight_of {40	($($method:ident ($($args:tt)*)),*) => {41		Weight::zero()42		$(43			.max(<SelfWeightOf<T>>::$method($($args)*))44		)*45	};46}4748pub struct CommonWeights<T: Config>(PhantomData<T>);49impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {50	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {51		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(52			init_token_properties_delta::<T, _>(53				data.iter().map(|data| match data {54					up_data_structs::CreateItemData::ReFungible(rft_data) => {55						rft_data.properties.len() as u3256					}57					_ => 0,58				}),59				<SelfWeightOf<T>>::init_token_properties,60			),61		)62	}6364	fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {65		match call {66			CreateItemExData::RefungibleMultipleOwners(i) => {67				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)68					.saturating_add(init_token_properties_delta::<T, _>(69						[i.properties.len() as u32].into_iter(),70						<SelfWeightOf<T>>::init_token_properties,71					))72			}73			CreateItemExData::RefungibleMultipleItems(i) => {74				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)75					.saturating_add(init_token_properties_delta::<T, _>(76						i.iter().map(|d| d.properties.len() as u32),77						<SelfWeightOf<T>>::init_token_properties,78					))79			}80			_ => Weight::zero(),81		}82	}8384	fn burn_item() -> Weight {85		max_weight_of!(burn_item_partial(), burn_item_fully())86	}8788	fn set_collection_properties(amount: u32) -> Weight {89		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)90	}9192	fn delete_collection_properties(amount: u32) -> Weight {93		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)94	}9596	fn set_token_properties(amount: u32) -> Weight {97		<SelfWeightOf<T>>::set_token_properties(amount)98	}99100	fn delete_token_properties(amount: u32) -> Weight {101		<SelfWeightOf<T>>::delete_token_properties(amount)102	}103104	fn set_token_property_permissions(amount: u32) -> Weight {105		<SelfWeightOf<T>>::set_token_property_permissions(amount)106	}107108	fn transfer() -> Weight {109		max_weight_of!(110			transfer_normal(),111			transfer_creating(),112			transfer_removing(),113			transfer_creating_removing()114		)115	}116117	fn approve() -> Weight {118		<SelfWeightOf<T>>::approve()119	}120121	fn approve_from() -> Weight {122		<SelfWeightOf<T>>::approve_from()123	}124125	fn transfer_from() -> Weight {126		max_weight_of!(127			transfer_from_normal(),128			transfer_from_creating(),129			transfer_from_removing(),130			transfer_from_creating_removing()131		)132	}133134	fn burn_from() -> Weight {135		<SelfWeightOf<T>>::burn_from()136	}137138	fn burn_recursively_self_raw() -> Weight {139		// Read to get total balance140		Self::burn_item() + T::DbWeight::get().reads(1)141	}142	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {143		// Refungible token can't have children144		Weight::zero()145	}146147	fn token_owner() -> Weight {148		<SelfWeightOf<T>>::token_owner()149	}150151	fn set_allowance_for_all() -> Weight {152		<SelfWeightOf<T>>::set_allowance_for_all()153	}154155	fn force_repair_item() -> Weight {156		<SelfWeightOf<T>>::repair_item()157	}158}159160fn map_create_data<T: Config>(161	data: up_data_structs::CreateItemData,162	to: &T::CrossAccountId,163) -> Result<CreateItemData<T>, DispatchError> {164	match data {165		up_data_structs::CreateItemData::ReFungible(data) => Ok(CreateItemData::<T> {166			users: {167				let mut out = BTreeMap::new();168				out.insert(to.clone(), data.pieces);169				out.try_into().expect("limit > 0")170			},171			properties: data.properties,172		}),173		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),174	}175}176177/// Implementation of `CommonCollectionOperations` for `RefungibleHandle`. It wraps Refungible Pallete178/// methods and adds weight info.179impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {180	fn create_item(181		&self,182		sender: T::CrossAccountId,183		to: T::CrossAccountId,184		data: up_data_structs::CreateItemData,185		nesting_budget: &dyn Budget,186	) -> DispatchResultWithPostInfo {187		let weight = <CommonWeights<T>>::create_item(&data);188		with_weight(189			<Pallet<T>>::create_item(190				self,191				&sender,192				map_create_data::<T>(data, &to)?,193				nesting_budget,194			),195			weight,196		)197	}198199	fn create_multiple_items(200		&self,201		sender: T::CrossAccountId,202		to: T::CrossAccountId,203		data: Vec<up_data_structs::CreateItemData>,204		nesting_budget: &dyn Budget,205	) -> DispatchResultWithPostInfo {206		let weight = <CommonWeights<T>>::create_multiple_items(&data);207		let data = data208			.into_iter()209			.map(|d| map_create_data::<T>(d, &to))210			.collect::<Result<Vec<_>, DispatchError>>()?;211212		with_weight(213			<Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),214			weight,215		)216	}217218	fn create_multiple_items_ex(219		&self,220		sender: <T>::CrossAccountId,221		data: CreateItemExData<T::CrossAccountId>,222		nesting_budget: &dyn Budget,223	) -> DispatchResultWithPostInfo {224		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);225		let data = match data {226			CreateItemExData::RefungibleMultipleOwners(CreateRefungibleExMultipleOwners {227				users,228				properties,229			}) => vec![CreateItemData::<T> { users, properties }],230			CreateItemExData::RefungibleMultipleItems(r) => r231				.into_inner()232				.into_iter()233				.map(234					|CreateRefungibleExSingleOwner {235					     user,236					     pieces,237					     properties,238					 }| CreateItemData::<T> {239						users: BTreeMap::from([(user, pieces)])240							.try_into()241							.expect("limit >= 1"),242						properties,243					},244				)245				.collect(),246			_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),247		};248249		with_weight(250			<Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),251			weight,252		)253	}254255	fn burn_item(256		&self,257		sender: T::CrossAccountId,258		token: TokenId,259		amount: u128,260	) -> DispatchResultWithPostInfo {261		with_weight(262			<Pallet<T>>::burn(self, &sender, token, amount),263			<CommonWeights<T>>::burn_item(),264		)265	}266267	fn burn_item_recursively(268		&self,269		sender: T::CrossAccountId,270		token: TokenId,271		self_budget: &dyn Budget,272		_breadth_budget: &dyn Budget,273	) -> DispatchResultWithPostInfo {274		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);275		with_weight(276			<Pallet<T>>::burn(277				self,278				&sender,279				token,280				<Balance<T>>::get((self.id, token, &sender)),281			),282			<CommonWeights<T>>::burn_recursively_self_raw(),283		)284	}285286	fn transfer(287		&self,288		from: T::CrossAccountId,289		to: T::CrossAccountId,290		token: TokenId,291		amount: u128,292		nesting_budget: &dyn Budget,293	) -> DispatchResultWithPostInfo {294		with_weight(295			<Pallet<T>>::transfer(self, &from, &to, token, amount, nesting_budget),296			<CommonWeights<T>>::transfer(),297		)298	}299300	fn approve(301		&self,302		sender: T::CrossAccountId,303		spender: T::CrossAccountId,304		token: TokenId,305		amount: u128,306	) -> DispatchResultWithPostInfo {307		with_weight(308			<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),309			<CommonWeights<T>>::approve(),310		)311	}312313	fn approve_from(314		&self,315		sender: T::CrossAccountId,316		from: T::CrossAccountId,317		to: T::CrossAccountId,318		token_id: TokenId,319		amount: u128,320	) -> DispatchResultWithPostInfo {321		with_weight(322			<Pallet<T>>::set_allowance_from(self, &sender, &from, &to, token_id, amount),323			<CommonWeights<T>>::approve_from(),324		)325	}326327	fn transfer_from(328		&self,329		sender: T::CrossAccountId,330		from: T::CrossAccountId,331		to: T::CrossAccountId,332		token: TokenId,333		amount: u128,334		nesting_budget: &dyn Budget,335	) -> DispatchResultWithPostInfo {336		with_weight(337			<Pallet<T>>::transfer_from(self, &sender, &from, &to, token, amount, nesting_budget),338			<CommonWeights<T>>::transfer_from(),339		)340	}341342	fn burn_from(343		&self,344		sender: T::CrossAccountId,345		from: T::CrossAccountId,346		token: TokenId,347		amount: u128,348		nesting_budget: &dyn Budget,349	) -> DispatchResultWithPostInfo {350		with_weight(351			<Pallet<T>>::burn_from(self, &sender, &from, token, amount, nesting_budget),352			<CommonWeights<T>>::burn_from(),353		)354	}355356	fn set_collection_properties(357		&self,358		sender: T::CrossAccountId,359		properties: Vec<Property>,360	) -> DispatchResultWithPostInfo {361		let weight = <CommonWeights<T>>::set_collection_properties(properties.len() as u32);362363		with_weight(364			<Pallet<T>>::set_collection_properties(self, &sender, properties),365			weight,366		)367	}368369	fn delete_collection_properties(370		&self,371		sender: &T::CrossAccountId,372		property_keys: Vec<PropertyKey>,373	) -> DispatchResultWithPostInfo {374		let weight = <CommonWeights<T>>::delete_collection_properties(property_keys.len() as u32);375376		with_weight(377			<Pallet<T>>::delete_collection_properties(self, sender, property_keys),378			weight,379		)380	}381382	fn set_token_properties(383		&self,384		sender: T::CrossAccountId,385		token_id: TokenId,386		properties: Vec<Property>,387		nesting_budget: &dyn Budget,388	) -> DispatchResultWithPostInfo {389		let weight = <CommonWeights<T>>::set_token_properties(properties.len() as u32);390391		with_weight(392			<Pallet<T>>::set_token_properties(393				self,394				&sender,395				token_id,396				properties.into_iter(),397				nesting_budget,398			),399			weight,400		)401	}402403	fn set_token_property_permissions(404		&self,405		sender: &T::CrossAccountId,406		property_permissions: Vec<PropertyKeyPermission>,407	) -> DispatchResultWithPostInfo {408		let weight =409			<CommonWeights<T>>::set_token_property_permissions(property_permissions.len() as u32);410411		with_weight(412			<Pallet<T>>::set_token_property_permissions(self, sender, property_permissions),413			weight,414		)415	}416417	fn delete_token_properties(418		&self,419		sender: T::CrossAccountId,420		token_id: TokenId,421		property_keys: Vec<PropertyKey>,422		nesting_budget: &dyn Budget,423	) -> DispatchResultWithPostInfo {424		let weight = <CommonWeights<T>>::delete_token_properties(property_keys.len() as u32);425426		with_weight(427			<Pallet<T>>::delete_token_properties(428				self,429				&sender,430				token_id,431				property_keys.into_iter(),432				nesting_budget,433			),434			weight,435		)436	}437438	fn get_token_properties_map(&self, token_id: TokenId) -> up_data_structs::TokenProperties {439		<TokenProperties<T>>::get((self.id, token_id))440	}441442	fn set_token_properties_map(&self, token_id: TokenId, map: up_data_structs::TokenProperties) {443		<TokenProperties<T>>::set((self.id, token_id), map)444	}445446	fn properties_exist(&self, token: TokenId) -> bool {447		<TokenProperties<T>>::contains_key((self.id, token))448	}449450	fn check_nesting(451		&self,452		_sender: <T>::CrossAccountId,453		_from: (CollectionId, TokenId),454		_under: TokenId,455		_nesting_budget: &dyn Budget,456	) -> sp_runtime::DispatchResult {457		fail!(<Error<T>>::RefungibleDisallowsNesting)458	}459460	fn nest(&self, _under: TokenId, _to_nest: (CollectionId, TokenId)) {}461462	fn unnest(&self, _under: TokenId, _to_nest: (CollectionId, TokenId)) {}463464	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {465		<Owned<T>>::iter_prefix((self.id, account))466			.map(|(id, _)| id)467			.collect()468	}469470	fn collection_tokens(&self) -> Vec<TokenId> {471		<TotalSupply<T>>::iter_prefix((self.id,))472			.map(|(id, _)| id)473			.collect()474	}475476	fn token_exists(&self, token: TokenId) -> bool {477		<Pallet<T>>::token_exists(self, token)478	}479480	fn last_token_id(&self) -> TokenId {481		TokenId(<TokensMinted<T>>::get(self.id))482	}483484	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {485		<Pallet<T>>::token_owner(self.id, token)486	}487488	fn check_token_indirect_owner(489		&self,490		token: TokenId,491		maybe_owner: &T::CrossAccountId,492		nesting_budget: &dyn Budget,493	) -> Result<bool, DispatchError> {494		let balance = self.balance(maybe_owner.clone(), token);495		let total_pieces: u128 = <Pallet<T>>::total_pieces(self.id, token).unwrap_or(u128::MAX);496		if balance != total_pieces {497			return Ok(false);498		}499500		let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(501			maybe_owner.clone(),502			self.id,503			token,504			None,505			nesting_budget,506		)?;507508		Ok(is_bundle_owner)509	}510511	/// Returns 10 token in no particular order.512	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {513		<Pallet<T>>::token_owners(self.id, token).unwrap_or_default()514	}515516	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {517		<Pallet<T>>::token_properties((self.id, token_id))518			.get(key)519			.cloned()520	}521522	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property> {523		let properties = <Pallet<T>>::token_properties((self.id, token_id));524525		keys.map(|keys| {526			keys.into_iter()527				.filter_map(|key| {528					properties.get(&key).map(|value| Property {529						key,530						value: value.clone(),531					})532				})533				.collect()534		})535		.unwrap_or_else(|| {536			properties537				.into_iter()538				.map(|(key, value)| Property { key, value })539				.collect()540		})541	}542543	fn total_supply(&self) -> u32 {544		<Pallet<T>>::total_supply(self)545	}546547	fn account_balance(&self, account: T::CrossAccountId) -> u32 {548		<AccountBalance<T>>::get((self.id, account))549	}550551	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {552		<Balance<T>>::get((self.id, token, account))553	}554555	fn allowance(556		&self,557		sender: T::CrossAccountId,558		spender: T::CrossAccountId,559		token: TokenId,560	) -> u128 {561		<Allowance<T>>::get((self.id, token, sender, spender))562	}563564	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {565		Some(self)566	}567568	fn total_pieces(&self, token: TokenId) -> Option<u128> {569		<Pallet<T>>::total_pieces(self.id, token)570	}571572	fn set_allowance_for_all(573		&self,574		owner: T::CrossAccountId,575		operator: T::CrossAccountId,576		approve: bool,577	) -> DispatchResultWithPostInfo {578		with_weight(579			<Pallet<T>>::set_allowance_for_all(self, &owner, &operator, approve),580			<CommonWeights<T>>::set_allowance_for_all(),581		)582	}583584	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {585		<Pallet<T>>::allowance_for_all(self, &owner, &operator)586	}587588	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo {589		with_weight(590			<Pallet<T>>::repair_item(self, token),591			<CommonWeights<T>>::force_repair_item(),592		)593	}594}595596impl<T: Config> RefungibleExtensions<T> for RefungibleHandle<T> {597	fn repartition(598		&self,599		owner: &T::CrossAccountId,600		token: TokenId,601		amount: u128,602	) -> DispatchResultWithPostInfo {603		with_weight(604			<Pallet<T>>::repartition(self, owner, token, amount),605			<SelfWeightOf<T>>::repartition_item(),606		)607	}608}
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -214,7 +214,6 @@
 			&caller,
 			TokenId(token_id),
 			properties.into_iter(),
-			pallet_common::SetPropertyMode::ExistingToken,
 			&nesting_budget,
 		)
 		.map_err(dispatch_to_evm::<T>)
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -96,8 +96,8 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_evm_coder_substrate::WithRecorder;
 use pallet_common::{
-	CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
-	Event as CommonEvent, Pallet as PalletCommon, SetPropertyMode,
+	Error as CommonError, eth::collection_id_to_address, Event as CommonEvent,
+	Pallet as PalletCommon,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_core::{Get, H160};
@@ -533,66 +533,16 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
-		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let mut is_token_owner =
-			pallet_common::LazyValue::new(|| -> Result<bool, DispatchError> {
-				if let SetPropertyMode::NewToken {
-					mint_target_is_sender,
-				} = mode
-				{
-					return Ok(mint_target_is_sender);
-				}
-
-				let balance = collection.balance(sender.clone(), token_id);
-				let total_pieces: u128 =
-					Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
-				if balance != total_pieces {
-					return Ok(false);
-				}
-
-				let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
-					sender.clone(),
-					collection.id,
-					token_id,
-					None,
-					nesting_budget,
-				)?;
-
-				Ok(is_bundle_owner)
-			});
-
-		let is_new_token = matches!(mode, SetPropertyMode::NewToken { .. });
-
-		let mut is_token_exist = pallet_common::LazyValue::new(|| {
-			if is_new_token {
-				debug_assert!(Self::token_exists(collection, token_id));
-				true
-			} else {
-				Self::token_exists(collection, token_id)
-			}
-		});
-
-		let stored_properties = if is_new_token {
-			debug_assert!(!<TokenProperties<T>>::contains_key((
-				collection.id,
-				token_id
-			)));
-			TokenPropertiesT::new()
-		} else {
-			<TokenProperties<T>>::get((collection.id, token_id))
-		};
+		let mut property_writer =
+			pallet_common::property_writer_for_existing_token(collection, sender);
 
-		<PalletCommon<T>>::modify_token_properties(
-			collection,
+		property_writer.write_token_properties(
 			sender,
 			token_id,
-			&mut is_token_exist,
 			properties_updates,
-			stored_properties,
-			&mut is_token_owner,
-			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+			nesting_budget,
 			erc::ERC721TokenEvent::TokenChanged {
 				token_id: token_id.into(),
 			}
@@ -618,7 +568,6 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties: impl Iterator<Item = Property>,
-		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::modify_token_properties(
@@ -626,7 +575,6 @@
 			sender,
 			token_id,
 			properties.map(|p| (p.key, Some(p.value))),
-			mode,
 			nesting_budget,
 		)
 	}
@@ -643,7 +591,6 @@
 			sender,
 			token_id,
 			[property].into_iter(),
-			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -660,7 +607,6 @@
 			sender,
 			token_id,
 			property_keys.into_iter().map(|key| (key, None)),
-			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -941,11 +887,15 @@
 
 		// =========
 
+		let mut property_writer = pallet_common::property_writer_for_new_token(collection, sender);
+
 		with_transaction(|| {
 			for (i, data) in data.iter().enumerate() {
 				let token_id = first_token_id + i as u32 + 1;
 				<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
 
+				let token = TokenId(token_id);
+
 				let mut mint_target_is_sender = true;
 				for (user, amount) in data.users.iter() {
 					if *amount == 0 {
@@ -955,23 +905,22 @@
 					mint_target_is_sender = mint_target_is_sender && sender.conv_eq(user);
 
 					<Balance<T>>::insert((collection.id, token_id, &user), amount);
-					<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
+					<Owned<T>>::insert((collection.id, &user, token), true);
 					<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
 						user,
 						collection.id,
-						TokenId(token_id),
+						token,
 					);
 				}
 
-				if let Err(e) = Self::set_token_properties(
-					collection,
-					sender,
-					TokenId(token_id),
+				if let Err(e) = property_writer.write_token_properties(
+					mint_target_is_sender,
+					token,
 					data.properties.clone().into_iter(),
-					SetPropertyMode::NewToken {
-						mint_target_is_sender,
-					},
-					nesting_budget,
+					erc::ERC721TokenEvent::TokenChanged {
+						token_id: token.into(),
+					}
+					.to_log(T::ContractAddress::get()),
 				) {
 					return TransactionOutcome::Rollback(Err(e));
 				}