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
56use core::{56use core::{
57 ops::{Deref, DerefMut},57 ops::{Deref, DerefMut},
58 slice::from_ref,58 slice::from_ref,
59 marker::PhantomData,
59};60};
60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
61use sp_std::vec::Vec;62use sp_std::vec::Vec;
98#[allow(missing_docs)]99#[allow(missing_docs)]
99pub mod weights;100pub mod weights;
101
102use weights::WeightInfo;
103
100/// Weight info.104/// Weight info.
101pub type SelfWeightOf<T> = <T as Config>::WeightInfo;105pub type SelfWeightOf<T> = <T as Config>::WeightInfo;
865 >;869 >;
866}870}
867
868/// Represents the change mode for the token property.
869pub enum SetPropertyMode {
870 /// The token already exists.
871 ExistingToken,
872
873 /// New token.
874 NewToken {
875 /// The creator of the token is the recipient.
876 mint_target_is_sender: bool,
877 },
878}
879871
880/// Value representation with delayed initialization time.872/// Value representation with delayed initialization time.
881pub struct LazyValue<T, F: FnOnce() -> T> {873pub struct LazyValue<T, F: FnOnce() -> T> {
892 }884 }
893 }885 }
894886
895 /// Get the value. If it call furst time the value will be initialized.887 /// Get the value. If it is called the first time, the value will be initialized.
896 pub fn value(&mut self) -> &T {888 pub fn value(&mut self) -> &T {
897 if self.value.is_none() {889 self.compute_value_if_not_already();
898 self.value = Some(self.f.take().unwrap()())
899 }
900
901 self.value.as_ref().unwrap()890 self.value.as_ref().unwrap()
902 }891 }
892
893 /// Get the value. If it is called the first time, the value will be initialized.
894 pub fn value_mut(&mut self) -> &mut T {
895 self.compute_value_if_not_already();
896 self.value.as_mut().unwrap()
897 }
898
899 fn into_inner(mut self) -> T {
900 self.compute_value_if_not_already();
901 self.value.unwrap()
902 }
903903
904 /// Is value initialized.904 /// Is value initialized?
905 pub fn has_value(&self) -> bool {905 pub fn has_value(&self) -> bool {
906 self.value.is_some()906 self.value.is_some()
907 }907 }
908
909 fn compute_value_if_not_already(&mut self) {
910 if self.value.is_none() {
911 self.value = Some(self.f.take().unwrap()())
912 }
913 }
908}914}
909915
910fn check_token_permissions<T, FCA, FTO, FTE>(916fn check_token_permissions<T, FCA, FTO, FTE>(
926 fail!(<Error<T>>::NoPermission);932 fail!(<Error<T>>::NoPermission);
927 }933 }
928934
929 let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;935 let token_exist_due_to_owner_check_success =
936 is_token_owner.has_value() && (*is_token_owner.value())?;
937
938 // If the token owner check has occurred and succeeded,
939 // we know the token exists (otherwise, the owner check must fail).
930 if !token_certainly_exist && !is_token_exist.value() {940 if !token_exist_due_to_owner_check_success {
941 // If the token owner check didn't occur,
942 // we must check the token's existence ourselves.
943 if !is_token_exist.value() {
931 fail!(<Error<T>>::TokenNotFound);944 fail!(<Error<T>>::TokenNotFound);
932 }945 }
946 }
947
933 Ok(())948 Ok(())
934}949}
1312 Ok(())1327 Ok(())
1313 }1328 }
1314
1315 /// A batch operation to add, edit or remove properties for a token.
1316 /// It sets or removes a token's properties according to
1317 /// `properties_updates` contents:
1318 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`
1319 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.
1320 ///
1321 /// All affected properties should have `mutable` permission
1322 /// to be **deleted** or to be **set more than once**,
1323 /// and the sender should have permission to edit those properties.
1324 ///
1325 /// This function fires an event for each property change.
1326 /// In case of an error, all the changes (including the events) will be reverted
1327 /// since the function is transactional.
1328 #[allow(clippy::too_many_arguments)]
1329 pub fn modify_token_properties<FTO, FTE>(
1330 collection: &CollectionHandle<T>,
1331 sender: &T::CrossAccountId,
1332 token_id: TokenId,
1333 is_token_exist: &mut LazyValue<bool, FTE>,
1334 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
1335 mut stored_properties: TokenProperties,
1336 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,
1337 set_token_properties: impl FnOnce(TokenProperties),
1338 log: evm_coder::ethereum::Log,
1339 ) -> DispatchResult
1340 where
1341 FTO: FnOnce() -> Result<bool, DispatchError>,
1342 FTE: FnOnce() -> bool,
1343 {
1344 let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));
1345 let mut permissions = LazyValue::new(|| Self::property_permissions(collection.id));
1346
1347 let mut changed = false;
1348 for (key, value) in properties_updates {
1349 let permission = permissions
1350 .value()
1351 .get(&key)
1352 .cloned()
1353 .unwrap_or_else(PropertyPermission::none);
1354
1355 let property_exists = stored_properties.get(&key).is_some();
1356
1357 match permission {
1358 PropertyPermission { mutable: false, .. } if property_exists => {
1359 return Err(<Error<T>>::NoPermission.into());
1360 }
1361
1362 PropertyPermission {
1363 collection_admin,
1364 token_owner,
1365 ..
1366 } => check_token_permissions::<T, _, FTO, FTE>(
1367 collection_admin,
1368 token_owner,
1369 &mut is_collection_admin,
1370 is_token_owner,
1371 is_token_exist,
1372 )?,
1373 }
1374
1375 match value {
1376 Some(value) => {
1377 stored_properties
1378 .try_set(key.clone(), value)
1379 .map_err(<Error<T>>::from)?;
1380
1381 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));
1382 }
1383 None => {
1384 stored_properties.remove(&key).map_err(<Error<T>>::from)?;
1385
1386 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));
1387 }
1388 }
1389
1390 changed = true;
1391 }
1392
1393 if changed {
1394 <PalletEvm<T>>::deposit_log(log);
1395 set_token_properties(stored_properties);
1396 }
1397
1398 Ok(())
1399 }
14001329
1401 /// Sets or unsets the approval of a given operator.1330 /// Sets or unsets the approval of a given operator.
1402 ///1331 ///
2166 budget: &dyn Budget,2095 budget: &dyn Budget,
2167 ) -> DispatchResultWithPostInfo;2096 ) -> DispatchResultWithPostInfo;
2097
2098 /// Get token properties raw map.
2099 ///
2100 /// * `token_id` - The token which properties are needed.
2101 fn get_token_properties_map(&self, token_id: TokenId) -> TokenProperties;
2102
2103 /// Set token properties raw map.
2104 ///
2105 /// * `token_id` - The token for which the properties are being set.
2106 /// * `map` - The raw map containing the token's properties.
2107 fn set_token_properties_map(&self, token_id: TokenId, map: TokenProperties);
2108
2109 /// Whether the given token has properties.
2110 ///
2111 /// * `token_id` - The token in question.
2112 fn properties_exist(&self, token: TokenId) -> bool;
21682113
2169 /// Set token property permissions.2114 /// Set token property permissions.
2170 ///2115 ///
2309 /// * `token` - The token for which you need to find out the owner.2254 /// * `token` - The token for which you need to find out the owner.
2310 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;2255 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;
2256
2257 /// Checks if the `maybe_owner` is the indirect owner of the `token`.
2258 ///
2259 /// * `token` - Id token to check.
2260 /// * `maybe_owner` - The account to check.
2261 /// * `nesting_budget` - A budget that can be spent on nesting tokens.
2262 fn check_token_indirect_owner(
2263 &self,
2264 token: TokenId,
2265 maybe_owner: &T::CrossAccountId,
2266 nesting_budget: &dyn Budget,
2267 ) -> Result<bool, DispatchError>;
23112268
2312 /// Returns 10 tokens owners in no particular order.2269 /// Returns 10 tokens owners in no particular order.
2313 ///2270 ///
2420 }2377 }
2421}2378}
2379
2380/// A marker structure that enables the writer implementation
2381/// to provide the interface to write properties to **newly created** tokens.
2382pub struct NewTokenPropertyWriter;
2383
2384/// A marker structure that enables the writer implementation
2385/// to provide the interface to write properties to **already existing** tokens.
2386pub struct ExistingTokenPropertyWriter;
2387
2388/// The type-safe interface for writing properties (setting or deleting) to tokens.
2389/// It has two distinct implementations for newly created tokens and existing ones.
2390///
2391/// This type utilizes the lazy evaluation to avoid repeating the computation
2392/// of several performance-heavy or PoV-heavy tasks,
2393/// such as checking the indirect ownership or reading the token property permissions.
2394pub struct PropertyWriter<
2395 'a,
2396 T,
2397 Handle,
2398 WriterVariant,
2399 FIsAdmin,
2400 FPropertyPermissions,
2401 FCheckTokenExist,
2402 FGetProperties,
2403> where
2404 T: Config,
2405 FIsAdmin: FnOnce() -> bool,
2406 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
2407{
2408 collection: &'a Handle,
2409 is_collection_admin: LazyValue<bool, FIsAdmin>,
2410 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,
2411 check_token_exist: FCheckTokenExist,
2412 get_properties: FGetProperties,
2413 _phantom: PhantomData<(T, WriterVariant)>,
2414}
2415
2416impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>
2417 PropertyWriter<
2418 'a,
2419 T,
2420 Handle,
2421 NewTokenPropertyWriter,
2422 FIsAdmin,
2423 FPropertyPermissions,
2424 FCheckTokenExist,
2425 FGetProperties,
2426 > where
2427 T: Config,
2428 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2429 FIsAdmin: FnOnce() -> bool,
2430 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
2431 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
2432 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
2433{
2434 /// A function to write properties to a **newly created** token.
2435 pub fn write_token_properties(
2436 &mut self,
2437 mint_target_is_sender: bool,
2438 token_id: TokenId,
2439 properties_updates: impl Iterator<Item = Property>,
2440 log: evm_coder::ethereum::Log,
2441 ) -> DispatchResult {
2442 self.internal_write_token_properties(
2443 token_id,
2444 properties_updates.map(|p| (p.key, Some(p.value))),
2445 |_| Ok(mint_target_is_sender),
2446 log,
2447 )
2448 }
2449}
2450
2451impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>
2452 PropertyWriter<
2453 'a,
2454 T,
2455 Handle,
2456 ExistingTokenPropertyWriter,
2457 FIsAdmin,
2458 FPropertyPermissions,
2459 FCheckTokenExist,
2460 FGetProperties,
2461 > where
2462 T: Config,
2463 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2464 FIsAdmin: FnOnce() -> bool,
2465 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
2466 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
2467 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
2468{
2469 /// A function to write properties to an **already existing** token.
2470 pub fn write_token_properties(
2471 &mut self,
2472 sender: &T::CrossAccountId,
2473 token_id: TokenId,
2474 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
2475 nesting_budget: &dyn Budget,
2476 log: evm_coder::ethereum::Log,
2477 ) -> DispatchResult {
2478 self.internal_write_token_properties(
2479 token_id,
2480 properties_updates,
2481 |collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),
2482 log,
2483 )
2484 }
2485}
2486
2487impl<
2488 'a,
2489 T,
2490 Handle,
2491 WriterVariant,
2492 FIsAdmin,
2493 FPropertyPermissions,
2494 FCheckTokenExist,
2495 FGetProperties,
2496 >
2497 PropertyWriter<
2498 'a,
2499 T,
2500 Handle,
2501 WriterVariant,
2502 FIsAdmin,
2503 FPropertyPermissions,
2504 FCheckTokenExist,
2505 FGetProperties,
2506 > where
2507 T: Config,
2508 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2509 FIsAdmin: FnOnce() -> bool,
2510 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
2511 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,
2512 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,
2513{
2514 fn internal_write_token_properties<FCheckTokenOwner>(
2515 &mut self,
2516 token_id: TokenId,
2517 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
2518 check_token_owner: FCheckTokenOwner,
2519 log: evm_coder::ethereum::Log,
2520 ) -> DispatchResult
2521 where
2522 FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,
2523 {
2524 let get_properties = self.get_properties;
2525 let mut stored_properties = LazyValue::new(move || get_properties(token_id));
2526
2527 let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));
2528
2529 let check_token_exist = self.check_token_exist;
2530 let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));
2531
2532 for (key, value) in properties_updates {
2533 let permission = self
2534 .property_permissions
2535 .value()
2536 .get(&key)
2537 .cloned()
2538 .unwrap_or_else(PropertyPermission::none);
2539
2540 match permission {
2541 PropertyPermission { mutable: false, .. }
2542 if stored_properties.value().get(&key).is_some() =>
2543 {
2544 return Err(<Error<T>>::NoPermission.into());
2545 }
2546
2547 PropertyPermission {
2548 collection_admin,
2549 token_owner,
2550 ..
2551 } => check_token_permissions::<T, _, _, _>(
2552 collection_admin,
2553 token_owner,
2554 &mut self.is_collection_admin,
2555 &mut is_token_owner,
2556 &mut is_token_exist,
2557 )?,
2558 }
2559
2560 match value {
2561 Some(value) => {
2562 stored_properties
2563 .value_mut()
2564 .try_set(key.clone(), value)
2565 .map_err(<Error<T>>::from)?;
2566
2567 <Pallet<T>>::deposit_event(Event::TokenPropertySet(
2568 self.collection.id,
2569 token_id,
2570 key,
2571 ));
2572 }
2573 None => {
2574 stored_properties
2575 .value_mut()
2576 .remove(&key)
2577 .map_err(<Error<T>>::from)?;
2578
2579 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(
2580 self.collection.id,
2581 token_id,
2582 key,
2583 ));
2584 }
2585 }
2586 }
2587
2588 let properties_changed = stored_properties.has_value();
2589 if properties_changed {
2590 <PalletEvm<T>>::deposit_log(log);
2591
2592 self.collection
2593 .set_token_properties_map(token_id, stored_properties.into_inner());
2594 }
2595
2596 Ok(())
2597 }
2598}
2599
2600/// Create a [`PropertyWriter`] for newly created tokens.
2601pub fn property_writer_for_new_token<'a, T, Handle>(
2602 collection: &'a Handle,
2603 sender: &'a T::CrossAccountId,
2604) -> PropertyWriter<
2605 'a,
2606 T,
2607 Handle,
2608 NewTokenPropertyWriter,
2609 impl FnOnce() -> bool + 'a,
2610 impl FnOnce() -> PropertiesPermissionMap + 'a,
2611 impl Copy + FnOnce(TokenId) -> bool + 'a,
2612 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,
2613>
2614where
2615 T: Config,
2616 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2617{
2618 PropertyWriter {
2619 collection,
2620 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
2621 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),
2622 check_token_exist: |token_id| {
2623 debug_assert!(collection.token_exists(token_id));
2624 true
2625 },
2626 get_properties: |token_id| {
2627 debug_assert!(!collection.properties_exist(token_id));
2628 TokenProperties::new()
2629 },
2630 _phantom: PhantomData,
2631 }
2632}
2633
2634#[cfg(feature = "runtime-benchmarks")]
2635/// Create a `PropertyWriter` with preloaded `is_collection_admin` and `property_permissions.
2636/// Also:
2637/// * it will return `true` for the token ownership check.
2638/// * it will return empty stored properties without reading them from the storage.
2639pub fn collection_info_loaded_property_writer<T, Handle>(
2640 collection: &Handle,
2641 is_collection_admin: bool,
2642 property_permissions: PropertiesPermissionMap,
2643) -> PropertyWriter<
2644 T,
2645 Handle,
2646 NewTokenPropertyWriter,
2647 impl FnOnce() -> bool,
2648 impl FnOnce() -> PropertiesPermissionMap,
2649 impl Copy + FnOnce(TokenId) -> bool,
2650 impl Copy + FnOnce(TokenId) -> TokenProperties,
2651>
2652where
2653 T: Config,
2654 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2655{
2656 PropertyWriter {
2657 collection,
2658 is_collection_admin: LazyValue::new(move || is_collection_admin),
2659 property_permissions: LazyValue::new(move || property_permissions),
2660 check_token_exist: |_token_id| true,
2661 get_properties: |_token_id| TokenProperties::new(),
2662 _phantom: PhantomData,
2663 }
2664}
2665
2666/// Create a [`PropertyWriter`] for already existing tokens.
2667pub fn property_writer_for_existing_token<'a, T, Handle>(
2668 collection: &'a Handle,
2669 sender: &'a T::CrossAccountId,
2670) -> PropertyWriter<
2671 'a,
2672 T,
2673 Handle,
2674 ExistingTokenPropertyWriter,
2675 impl FnOnce() -> bool + 'a,
2676 impl FnOnce() -> PropertiesPermissionMap + 'a,
2677 impl Copy + FnOnce(TokenId) -> bool + 'a,
2678 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,
2679>
2680where
2681 T: Config,
2682 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
2683{
2684 PropertyWriter {
2685 collection,
2686 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
2687 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),
2688 check_token_exist: |token_id| collection.token_exists(token_id),
2689 get_properties: |token_id| collection.get_token_properties_map(token_id),
2690 _phantom: PhantomData,
2691 }
2692}
2693
2694/// Computes the weight delta for newly created tokens with properties.
2695/// * `properties_nums` - The properties num of each created token.
2696/// * `init_token_properties` - The function to obtain the weight from a token's properties num.
2697pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(
2698 properties_nums: impl Iterator<Item = u32>,
2699 init_token_properties: I,
2700) -> Weight {
2701 let mut delta = properties_nums
2702 .filter_map(|properties_num| {
2703 if properties_num > 0 {
2704 Some(init_token_properties(properties_num))
2705 } else {
2706 None
2707 }
2708 })
2709 .fold(Weight::zero(), |a, b| a.saturating_add(b));
2710
2711 // If at least once the `init_token_properties` was called,
2712 // it means at least one newly created token has properties.
2713 // Becuase of that, some common collection data also was loaded and we need to add this weight.
2714 // However, these common data was loaded only once which is guaranteed by the `PropertyWriter`.
2715 if !delta.is_zero() {
2716 delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())
2717 }
2718
2719 delta
2720}
24222721
2423#[cfg(any(feature = "tests", test))]2722#[cfg(any(feature = "tests", test))]
2424#[allow(missing_docs)]2723#[allow(missing_docs)]
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
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -20,20 +20,20 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
 use up_data_structs::{
 	CollectionId, TokenId, CreateItemExData, budget::Budget, Property, PropertyKey, PropertyValue,
-	PropertyKeyPermission, CollectionPropertiesVec, CreateRefungibleExMultipleOwners,
-	CreateRefungibleExSingleOwner, TokenOwnerError,
+	PropertyKeyPermission, CreateRefungibleExMultipleOwners, CreateRefungibleExSingleOwner,
+	TokenOwnerError,
 };
 use pallet_common::{
 	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
-	weights::WeightInfo as _,
+	weights::WeightInfo as _, init_token_properties_delta,
 };
-use pallet_structure::Error as StructureError;
+use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
 use sp_runtime::{DispatchError};
 use sp_std::{vec::Vec, vec};
 
 use crate::{
 	AccountBalance, Allowance, Balance, Config, Error, Owned, Pallet, RefungibleHandle,
-	SelfWeightOf, weights::WeightInfo, TokensMinted, TotalSupply, CreateItemData,
+	SelfWeightOf, weights::WeightInfo, TokensMinted, TotalSupply, CreateItemData, TokenProperties,
 };
 
 macro_rules! max_weight_of {
@@ -45,26 +45,19 @@
 	};
 }
 
-fn properties_weight<T: Config>(properties: &CollectionPropertiesVec) -> Weight {
-	if properties.len() > 0 {
-		<SelfWeightOf<T>>::reset_token_properties(properties.len() as u32)
-	} else {
-		Weight::zero()
-	}
-}
-
 pub struct CommonWeights<T: Config>(PhantomData<T>);
 impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
 		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
-			data.iter()
-				.map(|data| match data {
+			init_token_properties_delta::<T, _>(
+				data.iter().map(|data| match data {
 					up_data_structs::CreateItemData::ReFungible(rft_data) => {
-						properties_weight::<T>(&rft_data.properties)
+						rft_data.properties.len() as u32
 					}
-					_ => Weight::zero(),
-				})
-				.fold(Weight::zero(), |a, b| a.saturating_add(b)),
+					_ => 0,
+				}),
+				<SelfWeightOf<T>>::init_token_properties,
+			),
 		)
 	}
 
@@ -72,15 +65,17 @@
 		match call {
 			CreateItemExData::RefungibleMultipleOwners(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
-					.saturating_add(properties_weight::<T>(&i.properties))
+					.saturating_add(init_token_properties_delta::<T, _>(
+						[i.properties.len() as u32].into_iter(),
+						<SelfWeightOf<T>>::init_token_properties,
+					))
 			}
 			CreateItemExData::RefungibleMultipleItems(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
-					.saturating_add(
-						i.iter()
-							.map(|d| properties_weight::<T>(&d.properties))
-							.fold(Weight::zero(), |a, b| a.saturating_add(b)),
-					)
+					.saturating_add(init_token_properties_delta::<T, _>(
+						i.iter().map(|d| d.properties.len() as u32),
+						<SelfWeightOf<T>>::init_token_properties,
+					))
 			}
 			_ => Weight::zero(),
 		}
@@ -399,7 +394,6 @@
 				&sender,
 				token_id,
 				properties.into_iter(),
-				pallet_common::SetPropertyMode::ExistingToken,
 				nesting_budget,
 			),
 			weight,
@@ -441,6 +435,18 @@
 		)
 	}
 
+	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 properties_exist(&self, token: TokenId) -> bool {
+		<TokenProperties<T>>::contains_key((self.id, token))
+	}
+
 	fn check_nesting(
 		&self,
 		_sender: <T>::CrossAccountId,
@@ -479,6 +485,29 @@
 		<Pallet<T>>::token_owner(self.id, token)
 	}
 
+	fn check_token_indirect_owner(
+		&self,
+		token: TokenId,
+		maybe_owner: &T::CrossAccountId,
+		nesting_budget: &dyn Budget,
+	) -> Result<bool, DispatchError> {
+		let balance = self.balance(maybe_owner.clone(), token);
+		let total_pieces: u128 = <Pallet<T>>::total_pieces(self.id, token).unwrap_or(u128::MAX);
+		if balance != total_pieces {
+			return Ok(false);
+		}
+
+		let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
+			maybe_owner.clone(),
+			self.id,
+			token,
+			None,
+			nesting_budget,
+		)?;
+
+		Ok(is_bundle_owner)
+	}
+
 	/// Returns 10 token in no particular order.
 	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
 		<Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
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));
 				}