git.delta.rocks / unique-network / refs/commits / 437764b749e7

difftreelog

refactor property writer / fix set_token_props weight

Daniel Shiposha2023-10-05parent: #90ad566.patch.diff
in: master

27 files changed

modifiedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -30,15 +30,7 @@
 		Weight::default()
 	}
 
-	fn delete_collection_properties(_amount: u32) -> Weight {
-		Weight::default()
-	}
-
 	fn set_token_properties(_amount: u32) -> Weight {
-		Weight::default()
-	}
-
-	fn delete_token_properties(_amount: u32) -> Weight {
 		Weight::default()
 	}
 
@@ -63,18 +55,6 @@
 	}
 
 	fn burn_from() -> Weight {
-		Weight::default()
-	}
-
-	fn burn_recursively_self_raw() -> Weight {
-		Weight::default()
-	}
-
-	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
-		Weight::default()
-	}
-
-	fn token_owner() -> Weight {
 		Weight::default()
 	}
 
@@ -124,16 +104,6 @@
 		_sender: <T>::CrossAccountId,
 		_token: TokenId,
 		_amount: u128,
-	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
-	}
-
-	fn burn_item_recursively(
-		&self,
-		_sender: <T>::CrossAccountId,
-		_token: TokenId,
-		_self_budget: &dyn up_data_structs::budget::Budget,
-		_breadth_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
 		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
 	}
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -34,7 +34,7 @@
 	MAX_TOKEN_PREFIX_LENGTH,
 };
 
-use crate::{CollectionHandle, Config, Pallet};
+use crate::{BenchmarkPropertyWriter, CollectionHandle, Config, Pallet};
 
 const SEED: u32 = 1;
 
@@ -123,16 +123,6 @@
 		CollectionMode::NFT,
 		|owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
 		|h| h,
-	)
-}
-
-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),
 	)
 }
 
@@ -272,7 +262,7 @@
 
 		#[block]
 		{
-			load_is_admin_and_property_permissions(&collection, &sender);
+			<BenchmarkPropertyWriter<T>>::load_collection_info(&&collection, &sender);
 		}
 
 		Ok(())
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -872,7 +872,7 @@
 }
 
 /// Value representation with delayed initialization time.
-pub struct LazyValue<T, F: FnOnce() -> T> {
+pub struct LazyValue<T, F> {
 	value: Option<T>,
 	f: Option<F>,
 }
@@ -1902,7 +1902,9 @@
 	/// Collection property deletion weight.
 	///
 	/// * `amount`- The number of properties to set.
-	fn delete_collection_properties(amount: u32) -> Weight;
+	fn delete_collection_properties(amount: u32) -> Weight {
+		Self::set_collection_properties(amount)
+	}
 
 	/// Token property setting weight.
 	///
@@ -1912,7 +1914,9 @@
 	/// Token property deletion weight.
 	///
 	/// * `amount`- The number of properties to delete.
-	fn delete_token_properties(amount: u32) -> Weight;
+	fn delete_token_properties(amount: u32) -> Weight {
+		Self::set_token_properties(amount)
+	}
 
 	/// Token property permissions set weight.
 	///
@@ -1934,30 +1938,6 @@
 	/// The price of burning a token from another user.
 	fn burn_from() -> Weight;
 
-	/// Differs from burn_item in case of Fungible and Refungible, as it should burn
-	/// whole users's balance.
-	///
-	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead
-	fn burn_recursively_self_raw() -> Weight;
-
-	/// Cost of iterating over `amount` children while burning, without counting child burning itself.
-	///
-	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead
-	fn burn_recursively_breadth_raw(amount: u32) -> Weight;
-
-	/// The price of recursive burning a token.
-	///
-	/// `max_selfs` - The maximum burning weight of the token itself.
-	/// `max_breadth` - The maximum number of nested tokens to burn.
-	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {
-		Self::burn_recursively_self_raw()
-			.saturating_mul(max_selfs.max(1) as u64)
-			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))
-	}
-
-	/// The price of retrieving token owner
-	fn token_owner() -> Weight;
-
 	/// The price of setting approval for all
 	fn set_allowance_for_all() -> Weight;
 
@@ -2029,20 +2009,6 @@
 		amount: u128,
 	) -> DispatchResultWithPostInfo;
 
-	/// Burn token and all nested tokens recursievly.
-	///
-	/// * `sender` - The user who owns the token.
-	/// * `token` - Token id that will burned.
-	/// * `self_budget` - The budget that can be spent on burning tokens.
-	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.
-	fn burn_item_recursively(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo;
-
 	/// Set collection properties.
 	///
 	/// * `sender` - Must be either the owner of the collection or its admin.
@@ -2373,14 +2339,6 @@
 		}
 	}
 }
-
-/// 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.
@@ -2388,146 +2346,39 @@
 /// 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,
-{
+pub struct PropertyWriter<'a, WriterVariant, T, Handle, FIsAdmin, FPropertyPermissions> {
 	collection: &'a Handle,
-	is_collection_admin: LazyValue<bool, FIsAdmin>,
-	property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,
-	check_token_exist: FCheckTokenExist,
-	get_properties: FGetProperties,
+	collection_lazy_info: PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions>,
 	_phantom: PhantomData<(T, WriterVariant)>,
 }
 
-impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>
-	PropertyWriter<
-		'a,
-		T,
-		Handle,
-		NewTokenPropertyWriter,
-		FIsAdmin,
-		FPropertyPermissions,
-		FCheckTokenExist,
-		FGetProperties,
-	> where
+impl<'a, T, Handle, WriterVariant, FIsAdmin, FPropertyPermissions>
+	PropertyWriter<'a, WriterVariant, T, Handle, FIsAdmin, FPropertyPermissions>
+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(
+	fn internal_write_token_properties<FCheckTokenExist, FCheckTokenOwner, FGetProperties>(
 		&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,
+		mut token_lazy_info: PropertyWriterLazyTokenInfo<
+			FCheckTokenExist,
+			FCheckTokenOwner,
+			FGetProperties,
+		>,
 		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>,
+		FCheckTokenExist: FnOnce() -> bool,
+		FCheckTokenOwner: FnOnce() -> Result<bool, DispatchError>,
+		FGetProperties: FnOnce() -> TokenProperties,
 	{
-		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
+				.collection_lazy_info
 				.property_permissions
 				.value()
 				.get(&key)
@@ -2536,7 +2387,11 @@
 
 			match permission {
 				PropertyPermission { mutable: false, .. }
-					if stored_properties.value().get(&key).is_some() =>
+					if token_lazy_info
+						.stored_properties
+						.value()
+						.get(&key)
+						.is_some() =>
 				{
 					return Err(<Error<T>>::NoPermission.into());
 				}
@@ -2548,15 +2403,16 @@
 				} => check_token_permissions::<T, _, _, _>(
 					collection_admin,
 					token_owner,
-					&mut self.is_collection_admin,
-					&mut is_token_owner,
-					&mut is_token_exist,
+					&mut self.collection_lazy_info.is_collection_admin,
+					&mut token_lazy_info.is_token_owner,
+					&mut token_lazy_info.is_token_exist,
 				)?,
 			}
 
 			match value {
 				Some(value) => {
-					stored_properties
+					token_lazy_info
+						.stored_properties
 						.value_mut()
 						.try_set(key.clone(), value)
 						.map_err(<Error<T>>::from)?;
@@ -2568,7 +2424,8 @@
 					));
 				}
 				None => {
-					stored_properties
+					token_lazy_info
+						.stored_properties
 						.value_mut()
 						.remove(&key)
 						.map_err(<Error<T>>::from)?;
@@ -2582,142 +2439,330 @@
 			}
 		}
 
-		let properties_changed = stored_properties.has_value();
+		let properties_changed = token_lazy_info.stored_properties.has_value();
 		if properties_changed {
 			<PalletEvm<T>>::deposit_log(log);
 
 			self.collection
-				.set_token_properties_raw(token_id, stored_properties.into_inner());
+				.set_token_properties_raw(token_id, token_lazy_info.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,
->
+/// A helper structure for the [`PropertyWriter`] that holds
+/// the collection-related info. The info is loaded using lazy evaluation.
+/// This info is common for any token for which we write properties.
+pub struct PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions> {
+	is_collection_admin: LazyValue<bool, FIsAdmin>,
+	property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,
+}
+
+/// A helper structure for the [`PropertyWriter`] that holds
+/// the token-related info. The info is loaded using lazy evaluation.
+pub struct PropertyWriterLazyTokenInfo<FCheckTokenExist, FCheckTokenOwner, FGetProperties> {
+	is_token_exist: LazyValue<bool, FCheckTokenExist>,
+	is_token_owner: LazyValue<Result<bool, DispatchError>, FCheckTokenOwner>,
+	stored_properties: LazyValue<TokenProperties, FGetProperties>,
+}
+
+impl<FCheckTokenExist, FCheckTokenOwner, FGetProperties>
+	PropertyWriterLazyTokenInfo<FCheckTokenExist, FCheckTokenOwner, FGetProperties>
 where
+	FCheckTokenExist: FnOnce() -> bool,
+	FCheckTokenOwner: FnOnce() -> Result<bool, DispatchError>,
+	FGetProperties: FnOnce() -> TokenProperties,
+{
+	/// Create a lazy token info.
+	pub fn new(
+		check_token_exist: FCheckTokenExist,
+		check_token_owner: FCheckTokenOwner,
+		get_token_properties: FGetProperties,
+	) -> Self {
+		Self {
+			is_token_exist: LazyValue::new(check_token_exist),
+			is_token_owner: LazyValue::new(check_token_owner),
+			stored_properties: LazyValue::new(get_token_properties),
+		}
+	}
+}
+
+/// A marker structure that enables the writer implementation
+/// to provide the interface to write properties to **newly created** tokens.
+pub struct NewTokenPropertyWriter<T>(PhantomData<T>);
+impl<T: Config> NewTokenPropertyWriter<T> {
+	/// Creates a [`PropertyWriter`] for **newly created** tokens.
+	pub fn new<'a, Handle>(
+		collection: &'a Handle,
+		sender: &'a T::CrossAccountId,
+	) -> PropertyWriter<
+		'a,
+		Self,
+		T,
+		Handle,
+		impl FnOnce() -> bool + 'a,
+		impl FnOnce() -> PropertiesPermissionMap + 'a,
+	>
+	where
+		T: Config,
+		Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	{
+		PropertyWriter {
+			collection,
+			collection_lazy_info: PropertyWriterLazyCollectionInfo {
+				is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
+				property_permissions: LazyValue::new(|| {
+					<Pallet<T>>::property_permissions(collection.id)
+				}),
+			},
+			_phantom: PhantomData,
+		}
+	}
+}
+
+impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>
+	PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>
+where
 	T: Config,
 	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
 {
-	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));
+	/// 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 {
+		let check_token_exist = || {
+			debug_assert!(self.collection.token_exists(token_id));
 			true
-		},
-		get_properties: |token_id| {
-			debug_assert!(collection.get_token_properties_raw(token_id).is_none());
+		};
+
+		let check_token_owner = || Ok(mint_target_is_sender);
+
+		let get_token_properties = || {
+			debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());
 			TokenProperties::new()
-		},
-		_phantom: PhantomData,
+		};
+
+		self.internal_write_token_properties(
+			token_id,
+			PropertyWriterLazyTokenInfo::new(
+				check_token_exist,
+				check_token_owner,
+				get_token_properties,
+			),
+			properties_updates.map(|p| (p.key, Some(p.value))),
+			log,
+		)
 	}
 }
 
-#[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,
->
+/// A marker structure that enables the writer implementation
+/// to provide the interface to write properties to **already existing** tokens.
+pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);
+impl<T: Config> ExistingTokenPropertyWriter<T> {
+	/// Creates a [`PropertyWriter`] for **already existing** tokens.
+	pub fn new<'a, Handle>(
+		collection: &'a Handle,
+		sender: &'a T::CrossAccountId,
+	) -> PropertyWriter<
+		'a,
+		Self,
+		T,
+		Handle,
+		impl FnOnce() -> bool + 'a,
+		impl FnOnce() -> PropertiesPermissionMap + 'a,
+	>
+	where
+		Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	{
+		PropertyWriter {
+			collection,
+			collection_lazy_info: PropertyWriterLazyCollectionInfo {
+				is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),
+				property_permissions: LazyValue::new(|| {
+					<Pallet<T>>::property_permissions(collection.id)
+				}),
+			},
+			_phantom: PhantomData,
+		}
+	}
+}
+
+impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>
+	PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>
 where
 	T: Config,
 	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
 {
-	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,
+	/// 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 {
+		let check_token_exist = || self.collection.token_exists(token_id);
+		let check_token_owner = || {
+			self.collection
+				.check_token_indirect_owner(token_id, sender, nesting_budget)
+		};
+		let get_token_properties = || {
+			self.collection
+				.get_token_properties_raw(token_id)
+				.unwrap_or_default()
+		};
+
+		self.internal_write_token_properties(
+			token_id,
+			PropertyWriterLazyTokenInfo::new(
+				check_token_exist,
+				check_token_owner,
+				get_token_properties,
+			),
+			properties_updates,
+			log,
+		)
 	}
 }
 
-/// 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,
->
+/// A marker structure that enables the writer implementation
+/// to benchmark the token properties writing.
+#[cfg(feature = "runtime-benchmarks")]
+pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);
+
+#[cfg(feature = "runtime-benchmarks")]
+impl<T: Config> BenchmarkPropertyWriter<T> {
+	/// Creates a [`PropertyWriter`] for benchmarking tokens properties writing.
+	pub fn new<'a, Handle, FIsAdmin, FPropertyPermissions>(
+		collection: &Handle,
+		collection_lazy_info: PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions>,
+	) -> PropertyWriter<Self, T, Handle, FIsAdmin, FPropertyPermissions>
+	where
+		Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+		FIsAdmin: FnOnce() -> bool,
+		FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
+	{
+		PropertyWriter {
+			collection,
+			collection_lazy_info,
+			_phantom: PhantomData,
+		}
+	}
+
+	/// Load the [`PropertyWriterLazyCollectionInfo`] from the storage.
+	pub fn load_collection_info<Handle>(
+		collection_handle: &Handle,
+		sender: &T::CrossAccountId,
+	) -> PropertyWriterLazyCollectionInfo<
+		impl FnOnce() -> bool,
+		impl FnOnce() -> PropertiesPermissionMap,
+	>
+	where
+		Handle: Deref<Target = CollectionHandle<T>>,
+	{
+		let is_collection_admin = collection_handle.is_owner_or_admin(sender);
+		let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);
+
+		PropertyWriterLazyCollectionInfo {
+			is_collection_admin: LazyValue::new(move || is_collection_admin),
+			property_permissions: LazyValue::new(move || property_permissions),
+		}
+	}
+
+	/// Load the [`PropertyWriterLazyTokenInfo`] with token properties from the storage.
+	pub fn load_token_properties<Handle>(
+		collection: &Handle,
+		token_id: TokenId,
+	) -> PropertyWriterLazyTokenInfo<
+		impl FnOnce() -> bool,
+		impl FnOnce() -> Result<bool, DispatchError>,
+		impl FnOnce() -> TokenProperties,
+	>
+	where
+		Handle: CommonCollectionOperations<T>,
+	{
+		let stored_properties = collection
+			.get_token_properties_raw(token_id)
+			.unwrap_or_default();
+
+		PropertyWriterLazyTokenInfo {
+			is_token_exist: LazyValue::new(|| true),
+			is_token_owner: LazyValue::new(|| Ok(true)),
+			stored_properties: LazyValue::new(move || stored_properties),
+		}
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>
+	PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>
 where
 	T: Config,
 	Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
+	FIsAdmin: FnOnce() -> bool,
+	FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,
 {
-	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_raw(token_id)
-				.unwrap_or_default()
-		},
-		_phantom: PhantomData,
+	/// A function to benchmark the writing of token properties.
+	pub fn write_token_properties(
+		&mut self,
+		token_id: TokenId,
+		properties_updates: impl Iterator<Item = Property>,
+		log: evm_coder::ethereum::Log,
+	) -> DispatchResult {
+		let check_token_exist = || true;
+		let check_token_owner = || Ok(true);
+		let get_token_properties = || TokenProperties::new();
+
+		self.internal_write_token_properties(
+			token_id,
+			PropertyWriterLazyTokenInfo::new(
+				check_token_exist,
+				check_token_owner,
+				get_token_properties,
+			),
+			properties_updates.map(|p| (p.key, Some(p.value))),
+			log,
+		)
 	}
 }
 
-/// Computes the weight delta for newly created tokens with properties.
+/// Computes the weight of writing properties to tokens.
 /// * `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>(
+/// * `per_token_weight_weight` - The function to obtain the weight
+/// of writing properties from a token's properties num.
+pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(
 	properties_nums: impl Iterator<Item = u32>,
-	init_token_properties: I,
+	per_token_weight: I,
 ) -> Weight {
-	let mut delta = properties_nums
+	let mut weight = properties_nums
 		.filter_map(|properties_num| {
 			if properties_num > 0 {
-				Some(init_token_properties(properties_num))
+				Some(per_token_weight(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())
+	if !weight.is_zero() {
+		// If we are here, it means the token properties were written at least once.
+		// Because of that, some common collection data was also loaded; we must add this weight.
+		// However, this common data was loaded only once, which is guaranteed by the `PropertyWriter`.
+
+		weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());
 	}
 
-	delta
+	weight
 }
 
 #[cfg(any(feature = "tests", test))]
modifiedpallets/common/src/weights.rsdiffbeforeafterboth
--- a/pallets/common/src/weights.rs
+++ b/pallets/common/src/weights.rs
@@ -3,9 +3,9 @@
 //! Autogenerated weights for pallet_common
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-09-30, STEPS: `50`, REPEAT: `400`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-10-05, STEPS: `50`, REPEAT: `80`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `bench-host`, CPU: `Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz`
+//! HOSTNAME: `hearthstone`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -20,7 +20,7 @@
 // *
 // --template=.maintain/frame-weight-template.hbs
 // --steps=50
-// --repeat=400
+// --repeat=80
 // --heap-pages=4096
 // --output=./pallets/common/src/weights.rs
 
@@ -36,7 +36,7 @@
 	fn set_collection_properties(b: u32, ) -> Weight;
 	fn delete_collection_properties(b: u32, ) -> Weight;
 	fn check_accesslist() -> Weight;
-	fn init_token_properties_common() -> Weight;
+	fn property_writer_load_collection_info() -> Weight;
 }
 
 /// Weights for pallet_common using the Substrate node and recommended hardware.
@@ -49,10 +49,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `298`
 		//  Estimated: `44457`
-		// Minimum execution time: 4_987_000 picoseconds.
-		Weight::from_parts(5_119_000, 44457)
-			// Standard Error: 7_609
-			.saturating_add(Weight::from_parts(5_750_459, 0).saturating_mul(b.into()))
+		// Minimum execution time: 2_840_000 picoseconds.
+		Weight::from_parts(1_988_405, 44457)
+			// Standard Error: 7_834
+			.saturating_add(Weight::from_parts(3_053_965, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -63,10 +63,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `303 + b * (33030 ±0)`
 		//  Estimated: `44457`
-		// Minimum execution time: 4_923_000 picoseconds.
-		Weight::from_parts(5_074_000, 44457)
-			// Standard Error: 36_651
-			.saturating_add(Weight::from_parts(23_145_677, 0).saturating_mul(b.into()))
+		// Minimum execution time: 2_770_000 picoseconds.
+		Weight::from_parts(2_940_000, 44457)
+			// Standard Error: 30_686
+			.saturating_add(Weight::from_parts(9_801_835, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -76,20 +76,20 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `373`
 		//  Estimated: `3535`
-		// Minimum execution time: 4_271_000 picoseconds.
-		Weight::from_parts(4_461_000, 3535)
+		// Minimum execution time: 2_830_000 picoseconds.
+		Weight::from_parts(2_950_000, 3535)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
 	/// Storage: Common IsAdmin (r:1 w:0)
 	/// Proof: Common IsAdmin (max_values: None, max_size: Some(70), added: 2545, mode: MaxEncodedLen)
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
 	/// Proof: Common CollectionPropertyPermissions (max_values: None, max_size: Some(16726), added: 19201, mode: MaxEncodedLen)
-	fn init_token_properties_common() -> Weight {
+	fn property_writer_load_collection_info() -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `20191`
-		// Minimum execution time: 5_889_000 picoseconds.
-		Weight::from_parts(6_138_000, 20191)
+		// Minimum execution time: 3_970_000 picoseconds.
+		Weight::from_parts(4_140_000, 20191)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 	}
 }
@@ -103,10 +103,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `298`
 		//  Estimated: `44457`
-		// Minimum execution time: 4_987_000 picoseconds.
-		Weight::from_parts(5_119_000, 44457)
-			// Standard Error: 7_609
-			.saturating_add(Weight::from_parts(5_750_459, 0).saturating_mul(b.into()))
+		// Minimum execution time: 2_840_000 picoseconds.
+		Weight::from_parts(1_988_405, 44457)
+			// Standard Error: 7_834
+			.saturating_add(Weight::from_parts(3_053_965, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -117,10 +117,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `303 + b * (33030 ±0)`
 		//  Estimated: `44457`
-		// Minimum execution time: 4_923_000 picoseconds.
-		Weight::from_parts(5_074_000, 44457)
-			// Standard Error: 36_651
-			.saturating_add(Weight::from_parts(23_145_677, 0).saturating_mul(b.into()))
+		// Minimum execution time: 2_770_000 picoseconds.
+		Weight::from_parts(2_940_000, 44457)
+			// Standard Error: 30_686
+			.saturating_add(Weight::from_parts(9_801_835, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -130,20 +130,20 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `373`
 		//  Estimated: `3535`
-		// Minimum execution time: 4_271_000 picoseconds.
-		Weight::from_parts(4_461_000, 3535)
+		// Minimum execution time: 2_830_000 picoseconds.
+		Weight::from_parts(2_950_000, 3535)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
 	/// Storage: Common IsAdmin (r:1 w:0)
 	/// Proof: Common IsAdmin (max_values: None, max_size: Some(70), added: 2545, mode: MaxEncodedLen)
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
 	/// Proof: Common CollectionPropertyPermissions (max_values: None, max_size: Some(16726), added: 19201, mode: MaxEncodedLen)
-	fn init_token_properties_common() -> Weight {
+	fn property_writer_load_collection_info() -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `20191`
-		// Minimum execution time: 5_889_000 picoseconds.
-		Weight::from_parts(6_138_000, 20191)
+		// Minimum execution time: 3_970_000 picoseconds.
+		Weight::from_parts(4_140_000, 20191)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 	}
 }
modifiedpallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -84,7 +84,7 @@
 }
 impl<T: Config> budget::Budget for GasCallsBudget<'_, T> {
 	fn consume_custom(&self, calls: u32) -> bool {
-		let (gas, overflown) = (calls as u64).overflowing_add(self.gas_per_call);
+		let (gas, overflown) = (calls as u64).overflowing_mul(self.gas_per_call);
 		if overflown {
 			return false;
 		}
modifiedpallets/foreign-assets/src/impl_fungibles.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/impl_fungibles.rs
+++ b/pallets/foreign-assets/src/impl_fungibles.rs
@@ -23,7 +23,7 @@
 use pallet_common::{CollectionHandle, CommonCollectionOperations};
 use pallet_fungible::FungibleHandle;
 use sp_runtime::traits::{CheckedAdd, CheckedSub};
-use up_data_structs::budget::Value;
+use up_data_structs::budget;
 
 use super::*;
 
@@ -327,7 +327,7 @@
 					&collection,
 					&account,
 					amount_data,
-					&Value::new(0),
+					&budget::Value::new(0),
 				)?;
 
 				Ok(amount)
@@ -440,7 +440,7 @@
 					&T::CrossAccountId::from_sub(source.clone()),
 					&T::CrossAccountId::from_sub(dest.clone()),
 					amount.into(),
-					&Value::new(0),
+					&budget::Value::new(0),
 				)
 				.map_err(|e| e.error)?;
 
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -16,14 +16,11 @@
 
 use core::marker::PhantomData;
 
-use frame_support::{
-	dispatch::DispatchResultWithPostInfo, ensure, fail, traits::Get, weights::Weight,
-};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
 	weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,
 	RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
 };
-use pallet_structure::Error as StructureError;
 use sp_runtime::{ArithmeticError, DispatchError};
 use sp_std::{vec, vec::Vec};
 use up_data_structs::{
@@ -58,18 +55,9 @@
 
 	fn set_collection_properties(amount: u32) -> Weight {
 		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
-	}
-
-	fn delete_collection_properties(amount: u32) -> Weight {
-		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
 	}
 
 	fn set_token_properties(_amount: u32) -> Weight {
-		// Error
-		Weight::zero()
-	}
-
-	fn delete_token_properties(_amount: u32) -> Weight {
 		// Error
 		Weight::zero()
 	}
@@ -80,7 +68,8 @@
 	}
 
 	fn transfer() -> Weight {
-		<SelfWeightOf<T>>::transfer_raw() + <PalletCommonWeightOf<T>>::check_accesslist() * 2
+		<SelfWeightOf<T>>::transfer_raw()
+			.saturating_add(<PalletCommonWeightOf<T>>::check_accesslist().saturating_mul(2))
 	}
 
 	fn approve() -> Weight {
@@ -93,28 +82,14 @@
 
 	fn transfer_from() -> Weight {
 		Self::transfer()
-			+ <SelfWeightOf<T>>::check_allowed_raw()
-			+ <SelfWeightOf<T>>::set_allowance_unchecked_raw()
+			.saturating_add(<SelfWeightOf<T>>::check_allowed_raw())
+			.saturating_add(<SelfWeightOf<T>>::set_allowance_unchecked_raw())
 	}
 
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
 	}
 
-	fn burn_recursively_self_raw() -> Weight {
-		// Read to get total balance
-		Self::burn_item() + T::DbWeight::get().reads(1)
-	}
-
-	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
-		// Fungible tokens can't have children
-		Weight::zero()
-	}
-
-	fn token_owner() -> Weight {
-		Weight::zero()
-	}
-
 	fn set_allowance_for_all() -> Weight {
 		Weight::zero()
 	}
@@ -200,26 +175,6 @@
 		with_weight(
 			<Pallet<T>>::burn(self, &sender, amount),
 			<CommonWeights<T>>::burn_item(),
-		)
-	}
-
-	fn burn_item_recursively(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		_breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo {
-		// Should not happen?
-		ensure!(
-			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
-		);
-		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
-
-		with_weight(
-			<Pallet<T>>::burn(self, &sender, <Balance<T>>::get((self.id, &sender))),
-			<CommonWeights<T>>::burn_recursively_self_raw(),
 		)
 	}
 
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -32,12 +32,12 @@
 use pallet_evm_coder_substrate::{
 	call, dispatch_to_evm,
 	execution::{PreDispatch, Result},
-	frontier_contract,
+	frontier_contract, SubstrateRecorder,
 };
 use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};
 use sp_core::{Get, U256};
 use sp_std::vec::Vec;
-use up_data_structs::CollectionMode;
+use up_data_structs::{budget::Budget, CollectionMode};
 
 use crate::{
 	common::CommonWeights, weights::WeightInfo, Allowance, Balance, Config, FungibleHandle, Pallet,
@@ -73,6 +73,10 @@
 	amount: U256,
 }
 
+fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {
+	recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())
+}
+
 #[solidity_interface(name = ERC20, events(ERC20Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x942e8b22)]
 impl<T: Config> FungibleHandle<T> {
 	fn name(&self) -> Result<String> {
@@ -106,11 +110,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &nesting_budget(&self.recorder))
 			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
@@ -127,12 +128,16 @@
 		let from = T::CrossAccountId::from_eth(from);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
-			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		<Pallet<T>>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
 	#[weight(<SelfWeightOf<T>>::approve())]
@@ -164,10 +169,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-		<Pallet<T>>::create_item(self, &caller, (to, amount), &budget)
+
+		<Pallet<T>>::create_item(self, &caller, (to, amount), &nesting_budget(&self.recorder))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
@@ -201,10 +204,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-		<Pallet<T>>::create_item(self, &caller, (to, amount), &budget)
+
+		<Pallet<T>>::create_item(self, &caller, (to, amount), &nesting_budget(&self.recorder))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
@@ -236,12 +237,15 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = T::CrossAccountId::from_eth(from);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -260,12 +264,15 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = from.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -274,9 +281,6 @@
 	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]
 	fn mint_bulk(&mut self, caller: Caller, amounts: Vec<AmountForAddress>) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 		let amounts = amounts
 			.into_iter()
 			.map(|AmountForAddress { to, amount }| {
@@ -287,7 +291,7 @@
 			})
 			.collect::<Result<_>>()?;
 
-		<Pallet<T>>::create_multiple_items(self, &caller, amounts, &budget)
+		<Pallet<T>>::create_multiple_items(self, &caller, amounts, &nesting_budget(&self.recorder))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
@@ -297,11 +301,9 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &nesting_budget(&self.recorder))
+			.map_err(|_| "transfer error")?;
 		Ok(true)
 	}
 
@@ -317,12 +319,16 @@
 		let from = from.into_sub_cross_account::<T>()?;
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
-			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		<Pallet<T>>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
 
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -262,9 +262,82 @@
 		{
 			<Pallet<T>>::burn_from(&collection, &burner, &sender, item, &Unlimited)?;
 		}
+	}
 
-		Ok(())
-	}
+	// set_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 {
+	// 			mutable: false,
+	// 			collection_admin: true,
+	// 			token_owner: true,
+	// 		},
+	// 	}).collect::<Vec<_>>();
+	// 	<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+	// 	let props = (0..b).map(|k| Property {
+	// 		key: property_key(k as usize),
+	// 		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(), &Unlimited)?}
+
+	// load_token_properties {
+	// 	bench_init!{
+	// 		owner: sub; collection: collection(owner);
+	// 		owner: cross_from_sub;
+	// 	};
+
+	// 	let item = create_max_item(&collection, &owner, owner.clone())?;
+	// }: {
+	// 	pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(
+	// 		&collection,
+	// 		item,
+	// 	)
+	// }
+
+	// write_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 {
+	// 			mutable: false,
+	// 			collection_admin: true,
+	// 			token_owner: true,
+	// 		},
+	// 	}).collect::<Vec<_>>();
+	// 	<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+	// 	let props = (0..b).map(|k| Property {
+	// 		key: property_key(k as usize),
+	// 		value: property_value(),
+	// 	}).collect::<Vec<_>>();
+	// 	let item = create_max_item(&collection, &owner, owner.clone())?;
+
+	// 	let lazy_collection_info = pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(
+	// 		&collection,
+	// 		&owner,
+	// 	);
+	// }: {
+	// 	let mut property_writer = pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+	// 	property_writer.write_token_properties(
+	// 		item,
+	// 		props.into_iter(),
+	// 		crate::erc::ERC721TokenEvent::TokenChanged {
+	// 			token_id: item.into(),
+	// 		}
+	// 		.to_log(T::ContractAddress::get()),
+	// 	)?
+	// }
 
 	#[benchmark]
 	fn set_token_property_permissions(
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -18,8 +18,9 @@
 
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
-	init_token_properties_delta, weights::WeightInfo as _, with_weight, CommonCollectionOperations,
-	CommonWeightInfo, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+	weights::WeightInfo as _, with_weight, write_token_properties_total_weight,
+	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions,
+	SelfWeightOf as PalletCommonWeightOf,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::DispatchError;
@@ -39,9 +40,9 @@
 	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)
-				.saturating_add(init_token_properties_delta::<T, _>(
+				.saturating_add(write_token_properties_total_weight::<T, _>(
 					t.iter().map(|t| t.properties.len() as u32),
-					<SelfWeightOf<T>>::init_token_properties,
+					<SelfWeightOf<T>>::write_token_properties,
 				)),
 			_ => Weight::zero(),
 		}
@@ -49,12 +50,12 @@
 
 	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
 		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
-			init_token_properties_delta::<T, _>(
+			write_token_properties_total_weight::<T, _>(
 				data.iter().map(|t| match t {
 					up_data_structs::CreateItemData::NFT(n) => n.properties.len() as u32,
 					_ => 0,
 				}),
-				<SelfWeightOf<T>>::init_token_properties,
+				<SelfWeightOf<T>>::write_token_properties,
 			),
 		)
 	}
@@ -67,16 +68,15 @@
 		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
 	}
 
-	fn delete_collection_properties(amount: u32) -> Weight {
-		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
-	}
-
 	fn set_token_properties(amount: u32) -> Weight {
-		<SelfWeightOf<T>>::set_token_properties(amount)
+		write_token_properties_total_weight::<T, _>([amount].into_iter(), |amount| {
+			<SelfWeightOf<T>>::load_token_properties()
+				.saturating_add(<SelfWeightOf<T>>::write_token_properties(amount))
+		})
 	}
 
 	fn delete_token_properties(amount: u32) -> Weight {
-		<SelfWeightOf<T>>::delete_token_properties(amount)
+		Self::set_token_properties(amount)
 	}
 
 	fn set_token_property_permissions(amount: u32) -> Weight {
@@ -84,7 +84,8 @@
 	}
 
 	fn transfer() -> Weight {
-		<SelfWeightOf<T>>::transfer_raw() + <PalletCommonWeightOf<T>>::check_accesslist() * 2
+		<SelfWeightOf<T>>::transfer_raw()
+			.saturating_add(<PalletCommonWeightOf<T>>::check_accesslist().saturating_mul(2))
 	}
 
 	fn approve() -> Weight {
@@ -96,24 +97,11 @@
 	}
 
 	fn transfer_from() -> Weight {
-		Self::transfer() + <SelfWeightOf<T>>::check_allowed_raw()
+		Self::transfer().saturating_add(<SelfWeightOf<T>>::check_allowed_raw())
 	}
 
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
-	}
-
-	fn burn_recursively_self_raw() -> Weight {
-		<SelfWeightOf<T>>::burn_recursively_self_raw()
-	}
-
-	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
-		<SelfWeightOf<T>>::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount)
-			.saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1))
-	}
-
-	fn token_owner() -> Weight {
-		<SelfWeightOf<T>>::token_owner()
 	}
 
 	fn set_allowance_for_all() -> Weight {
@@ -306,16 +294,6 @@
 			<Pallet<T>>::check_token_immediate_ownership(self, token, &sender)?;
 			Ok(().into())
 		}
-	}
-
-	fn burn_item_recursively(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo {
-		<Pallet<T>>::burn_recursively(self, &sender, token, self_budget, breadth_budget)
 	}
 
 	fn transfer(
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
before · pallets/nonfungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26	char::{decode_utf16, REPLACEMENT_CHARACTER},27	convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::BoundedVec;32use pallet_common::{33	erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34	eth::{self, TokenUri},35	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39	call, dispatch_to_evm,40	execution::{Error, PreDispatch, Result},41	frontier_contract,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47	CollectionId, CollectionPropertiesVec, Property, PropertyKey, PropertyKeyPermission,48	PropertyPermission, TokenId,49};5051use crate::{52	common::CommonWeights, weights::WeightInfo, AccountBalance, Config, CreateItemData,53	NonfungibleHandle, Pallet, SelfWeightOf, TokenData, TokenProperties, TokensMinted,54};5556/// Nft events.57#[derive(ToLog)]58pub enum ERC721TokenEvent {59	/// The token has been changed.60	TokenChanged {61		/// Token ID.62		#[indexed]63		token_id: U256,64	},65}6667/// Token minting parameters68#[derive(AbiCoder, Default, Debug)]69pub struct MintTokenData {70	/// Minted token owner71	pub owner: eth::CrossAddress,72	/// Minted token properties73	pub properties: Vec<eth::Property>,74}7576frontier_contract! {77	macro_rules! NonfungibleHandle_result {...}78	impl<T: Config> Contract for NonfungibleHandle<T> {...}79}8081/// @title A contract that allows to set and delete token properties and change token property permissions.82#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]83impl<T: Config> NonfungibleHandle<T> {84	/// @notice Set permissions for token property.85	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.86	/// @param key Property key.87	/// @param isMutable Permission to mutate property.88	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.89	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.90	#[solidity(hide)]91	#[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]92	fn set_token_property_permission(93		&mut self,94		caller: Caller,95		key: String,96		is_mutable: bool,97		collection_admin: bool,98		token_owner: bool,99	) -> Result<()> {100		let caller = T::CrossAccountId::from_eth(caller);101		<Pallet<T>>::set_token_property_permissions(102			self,103			&caller,104			vec![PropertyKeyPermission {105				key: <Vec<u8>>::from(key)106					.try_into()107					.map_err(|_| "too long key")?,108				permission: PropertyPermission {109					mutable: is_mutable,110					collection_admin,111					token_owner,112				},113			}],114		)115		.map_err(dispatch_to_evm::<T>)116	}117118	/// @notice Set permissions for token property.119	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.120	/// @param permissions Permissions for keys.121	#[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]122	fn set_token_property_permissions(123		&mut self,124		caller: Caller,125		permissions: Vec<eth::TokenPropertyPermission>,126	) -> Result<()> {127		let caller = T::CrossAccountId::from_eth(caller);128		let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;129130		<Pallet<T>>::set_token_property_permissions(self, &caller, perms)131			.map_err(dispatch_to_evm::<T>)132	}133134	/// @notice Get permissions for token properties.135	fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {136		let perms = <Pallet<T>>::token_property_permission(self.id);137		Ok(perms138			.into_iter()139			.map(eth::TokenPropertyPermission::from)140			.collect())141	}142143	/// @notice Set token property value.144	/// @dev Throws error if `msg.sender` has no permission to edit the property.145	/// @param tokenId ID of the token.146	/// @param key Property key.147	/// @param value Property value.148	#[solidity(hide)]149	#[weight(<SelfWeightOf<T>>::set_token_properties(1))]150	fn set_property(151		&mut self,152		caller: Caller,153		token_id: U256,154		key: String,155		value: Bytes,156	) -> Result<()> {157		let caller = T::CrossAccountId::from_eth(caller);158		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;159		let key = <Vec<u8>>::from(key)160			.try_into()161			.map_err(|_| "key too long")?;162		let value = value.0.try_into().map_err(|_| "value too long")?;163164		let nesting_budget = self165			.recorder166			.weight_calls_budget(<StructureWeight<T>>::find_parent());167168		<Pallet<T>>::set_token_property(169			self,170			&caller,171			TokenId(token_id),172			Property { key, value },173			&nesting_budget,174		)175		.map_err(dispatch_to_evm::<T>)176	}177178	/// @notice Set token properties value.179	/// @dev Throws error if `msg.sender` has no permission to edit the property.180	/// @param tokenId ID of the token.181	/// @param properties settable properties182	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]183	fn set_properties(184		&mut self,185		caller: Caller,186		token_id: U256,187		properties: Vec<eth::Property>,188	) -> Result<()> {189		let caller = T::CrossAccountId::from_eth(caller);190		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;191192		let nesting_budget = self193			.recorder194			.weight_calls_budget(<StructureWeight<T>>::find_parent());195196		let properties = properties197			.into_iter()198			.map(eth::Property::try_into)199			.collect::<Result<Vec<_>>>()?;200201		<Pallet<T>>::set_token_properties(202			self,203			&caller,204			TokenId(token_id),205			properties.into_iter(),206			&nesting_budget,207		)208		.map_err(dispatch_to_evm::<T>)209	}210211	/// @notice Delete token property value.212	/// @dev Throws error if `msg.sender` has no permission to edit the property.213	/// @param tokenId ID of the token.214	/// @param key Property key.215	#[solidity(hide)]216	#[weight(<SelfWeightOf<T>>::delete_token_properties(1))]217	fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {218		let caller = T::CrossAccountId::from_eth(caller);219		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;220		let key = <Vec<u8>>::from(key)221			.try_into()222			.map_err(|_| "key too long")?;223224		let nesting_budget = self225			.recorder226			.weight_calls_budget(<StructureWeight<T>>::find_parent());227228		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)229			.map_err(dispatch_to_evm::<T>)230	}231232	/// @notice Delete token properties value.233	/// @dev Throws error if `msg.sender` has no permission to edit the property.234	/// @param tokenId ID of the token.235	/// @param keys Properties key.236	#[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]237	fn delete_properties(238		&mut self,239		token_id: U256,240		caller: Caller,241		keys: Vec<String>,242	) -> Result<()> {243		let caller = T::CrossAccountId::from_eth(caller);244		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;245		let keys = keys246			.into_iter()247			.map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))248			.collect::<Result<Vec<_>>>()?;249250		let nesting_budget = self251			.recorder252			.weight_calls_budget(<StructureWeight<T>>::find_parent());253254		<Pallet<T>>::delete_token_properties(255			self,256			&caller,257			TokenId(token_id),258			keys.into_iter(),259			&nesting_budget,260		)261		.map_err(dispatch_to_evm::<T>)262	}263264	/// @notice Get token property value.265	/// @dev Throws error if key not found266	/// @param tokenId ID of the token.267	/// @param key Property key.268	/// @return Property value bytes269	fn property(&self, token_id: U256, key: String) -> Result<Bytes> {270		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;271		let key = <Vec<u8>>::from(key)272			.try_into()273			.map_err(|_| "key too long")?;274275		let props =276			<TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;277		let prop = props.get(&key).ok_or("key not found")?;278279		Ok(prop.to_vec().into())280	}281}282283#[derive(ToLog)]284pub enum ERC721Events {285	/// @dev This emits when ownership of any NFT changes by any mechanism.286	///  This event emits when NFTs are created (`from` == 0) and destroyed287	///  (`to` == 0). Exception: during contract creation, any number of NFTs288	///  may be created and assigned without emitting Transfer. At the time of289	///  any transfer, the approved address for that NFT (if any) is reset to none.290	Transfer {291		#[indexed]292		from: Address,293		#[indexed]294		to: Address,295		#[indexed]296		token_id: U256,297	},298	/// @dev This emits when the approved address for an NFT is changed or299	///  reaffirmed. The zero address indicates there is no approved address.300	///  When a Transfer event emits, this also indicates that the approved301	///  address for that NFT (if any) is reset to none.302	Approval {303		#[indexed]304		owner: Address,305		#[indexed]306		approved: Address,307		#[indexed]308		token_id: U256,309	},310	/// @dev This emits when an operator is enabled or disabled for an owner.311	///  The operator can manage all NFTs of the owner.312	#[allow(dead_code)]313	ApprovalForAll {314		#[indexed]315		owner: Address,316		#[indexed]317		operator: Address,318		approved: bool,319	},320}321322/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension323/// @dev See https://eips.ethereum.org/EIPS/eip-721324#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]325impl<T: Config> NonfungibleHandle<T>326where327	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,328{329	/// @notice A descriptive name for a collection of NFTs in this contract330	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`331	#[solidity(hide, rename_selector = "name")]332	fn name_proxy(&self) -> String {333		self.name()334	}335336	/// @notice An abbreviated name for NFTs in this contract337	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`338	#[solidity(hide, rename_selector = "symbol")]339	fn symbol_proxy(&self) -> String {340		self.symbol()341	}342343	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.344	///345	/// @dev If the token has a `url` property and it is not empty, it is returned.346	///  Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.347	///  If the collection property `baseURI` is empty or absent, return "" (empty string)348	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix349	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).350	///351	/// @return token's const_metadata352	#[solidity(rename_selector = "tokenURI")]353	fn token_uri(&self, token_id: U256) -> Result<String> {354		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;355356		match get_token_property(self, token_id_u32, &key::url()).as_deref() {357			Err(_) | Ok("") => (),358			Ok(url) => {359				return Ok(url.into());360			}361		};362363		let base_uri =364			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())365				.map(BoundedVec::into_inner)366				.map(String::from_utf8)367				.transpose()368				.map_err(|e| {369					Error::Revert(alloc::format!(370						"can not convert value \"baseURI\" to string with error \"{e}\""371					))372				})?;373374		let base_uri = match base_uri.as_deref() {375			None | Some("") => {376				return Ok("".into());377			}378			Some(base_uri) => base_uri.into(),379		};380381		Ok(382			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {383				Err(_) | Ok("") => base_uri,384				Ok(suffix) => base_uri + suffix,385			},386		)387	}388}389390/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension391/// @dev See https://eips.ethereum.org/EIPS/eip-721392#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]393impl<T: Config> NonfungibleHandle<T> {394	/// @notice Enumerate valid NFTs395	/// @param index A counter less than `totalSupply()`396	/// @return The token identifier for the `index`th NFT,397	///  (sort order not specified)398	fn token_by_index(&self, index: U256) -> U256 {399		index400	}401402	/// @dev Not implemented403	fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {404		// TODO: Not implemetable405		Err("not implemented".into())406	}407408	/// @notice Count NFTs tracked by this contract409	/// @return A count of valid NFTs tracked by this contract, where each one of410	///  them has an assigned and queryable owner not equal to the zero address411	fn total_supply(&self) -> Result<U256> {412		self.consume_store_reads(1)?;413		Ok(<Pallet<T>>::total_supply(self).into())414	}415}416417/// @title ERC-721 Non-Fungible Token Standard418/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md419#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]420impl<T: Config> NonfungibleHandle<T> {421	/// @notice Count all NFTs assigned to an owner422	/// @dev NFTs assigned to the zero address are considered invalid, and this423	///  function throws for queries about the zero address.424	/// @param owner An address for whom to query the balance425	/// @return The number of NFTs owned by `owner`, possibly zero426	fn balance_of(&self, owner: Address) -> Result<U256> {427		self.consume_store_reads(1)?;428		let owner = T::CrossAccountId::from_eth(owner);429		let balance = <AccountBalance<T>>::get((self.id, owner));430		Ok(balance.into())431	}432	/// @notice Find the owner of an NFT433	/// @dev NFTs assigned to zero address are considered invalid, and queries434	///  about them do throw.435	/// @param tokenId The identifier for an NFT436	/// @return The address of the owner of the NFT437	fn owner_of(&self, token_id: U256) -> Result<Address> {438		self.consume_store_reads(1)?;439		let token: TokenId = token_id.try_into()?;440		Ok(*<TokenData<T>>::get((self.id, token))441			.ok_or("token not found")?442			.owner443			.as_eth())444	}445	/// @dev Not implemented446	#[solidity(rename_selector = "safeTransferFrom")]447	fn safe_transfer_from_with_data(448		&mut self,449		_from: Address,450		_to: Address,451		_token_id: U256,452		_data: Bytes,453	) -> Result<()> {454		// TODO: Not implemetable455		Err("not implemented".into())456	}457	/// @dev Not implemented458	fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {459		// TODO: Not implemetable460		Err("not implemented".into())461	}462463	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE464	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE465	///  THEY MAY BE PERMANENTLY LOST466	/// @dev Throws unless `msg.sender` is the current owner or an authorized467	///  operator for this NFT. Throws if `from` is not the current owner. Throws468	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.469	/// @param from The current owner of the NFT470	/// @param to The new owner471	/// @param tokenId The NFT to transfer472	#[weight(<CommonWeights<T>>::transfer_from())]473	fn transfer_from(474		&mut self,475		caller: Caller,476		from: Address,477		to: Address,478		token_id: U256,479	) -> Result<()> {480		let caller = T::CrossAccountId::from_eth(caller);481		let from = T::CrossAccountId::from_eth(from);482		let to = T::CrossAccountId::from_eth(to);483		let token = token_id.try_into()?;484		let budget = self485			.recorder486			.weight_calls_budget(<StructureWeight<T>>::find_parent());487488		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)489			.map_err(|e| dispatch_to_evm::<T>(e.error))?;490		Ok(())491	}492493	/// @notice Set or reaffirm the approved address for an NFT494	/// @dev The zero address indicates there is no approved address.495	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized496	///  operator of the current owner.497	/// @param approved The new approved NFT controller498	/// @param tokenId The NFT to approve499	#[weight(<SelfWeightOf<T>>::approve())]500	fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {501		let caller = T::CrossAccountId::from_eth(caller);502		let approved = T::CrossAccountId::from_eth(approved);503		let token = token_id.try_into()?;504505		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))506			.map_err(dispatch_to_evm::<T>)?;507		Ok(())508	}509510	/// @notice Sets or unsets the approval of a given operator.511	/// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.512	/// @param operator Operator513	/// @param approved Should operator status be granted or revoked?514	#[weight(<SelfWeightOf<T>>::set_allowance_for_all())]515	fn set_approval_for_all(516		&mut self,517		caller: Caller,518		operator: Address,519		approved: bool,520	) -> Result<()> {521		let caller = T::CrossAccountId::from_eth(caller);522		let operator = T::CrossAccountId::from_eth(operator);523524		<Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)525			.map_err(dispatch_to_evm::<T>)?;526		Ok(())527	}528529	/// @notice Get the approved address for a single NFT530	/// @dev Throws if `tokenId` is not a valid NFT531	/// @param tokenId The NFT to find the approved address for532	/// @return The approved address for this NFT, or the zero address if there is none533	fn get_approved(&self, token_id: U256) -> Result<Address> {534		let token_id = token_id.try_into()?;535		let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;536		Ok(if let Some(operator) = operator {537			*operator.as_eth()538		} else {539			Address::zero()540		})541	}542543	/// @notice Tells whether the given `owner` approves the `operator`.544	#[weight(<SelfWeightOf<T>>::allowance_for_all())]545	fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {546		let owner = T::CrossAccountId::from_eth(owner);547		let operator = T::CrossAccountId::from_eth(operator);548549		Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))550	}551}552553/// @title ERC721 Token that can be irreversibly burned (destroyed).554#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556	/// @notice Burns a specific ERC721 token.557	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized558	///  operator of the current owner.559	/// @param tokenId The NFT to approve560	#[weight(<SelfWeightOf<T>>::burn_item())]561	fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {562		let caller = T::CrossAccountId::from_eth(caller);563		let token = token_id.try_into()?;564565		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;566		Ok(())567	}568}569570/// @title ERC721 minting logic.571#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]572impl<T: Config> NonfungibleHandle<T> {573	/// @notice Function to mint a token.574	/// @param to The new owner575	/// @return uint256 The id of the newly minted token576	#[weight(<SelfWeightOf<T>>::create_item())]577	fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {578		let token_id: U256 = <TokensMinted<T>>::get(self.id)579			.checked_add(1)580			.ok_or("item id overflow")?581			.into();582		self.mint_check_id(caller, to, token_id)?;583		Ok(token_id)584	}585586	/// @notice Function to mint a token.587	/// @dev `tokenId` should be obtained with `nextTokenId` method,588	///  unlike standard, you can't specify it manually589	/// @param to The new owner590	/// @param tokenId ID of the minted NFT591	#[solidity(hide, rename_selector = "mint")]592	#[weight(<SelfWeightOf<T>>::create_item())]593	fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {594		let caller = T::CrossAccountId::from_eth(caller);595		let to = T::CrossAccountId::from_eth(to);596		let token_id: u32 = token_id.try_into()?;597		let budget = self598			.recorder599			.weight_calls_budget(<StructureWeight<T>>::find_parent());600601		if <TokensMinted<T>>::get(self.id)602			.checked_add(1)603			.ok_or("item id overflow")?604			!= token_id605		{606			return Err("item id should be next".into());607		}608609		<Pallet<T>>::create_item(610			self,611			&caller,612			CreateItemData::<T> {613				properties: BoundedVec::default(),614				owner: to,615			},616			&budget,617		)618		.map_err(dispatch_to_evm::<T>)?;619620		Ok(true)621	}622623	/// @notice Function to mint token with the given tokenUri.624	/// @param to The new owner625	/// @param tokenUri Token URI that would be stored in the NFT properties626	/// @return uint256 The id of the newly minted token627	#[solidity(rename_selector = "mintWithTokenURI")]628	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]629	fn mint_with_token_uri(630		&mut self,631		caller: Caller,632		to: Address,633		token_uri: String,634	) -> Result<U256> {635		let token_id: U256 = <TokensMinted<T>>::get(self.id)636			.checked_add(1)637			.ok_or("item id overflow")?638			.into();639		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;640		Ok(token_id)641	}642643	/// @notice Function to mint token with the given tokenUri.644	/// @dev `tokenId` should be obtained with `nextTokenId` method,645	///  unlike standard, you can't specify it manually646	/// @param to The new owner647	/// @param tokenId ID of the minted NFT648	/// @param tokenUri Token URI that would be stored in the NFT properties649	#[solidity(hide, rename_selector = "mintWithTokenURI")]650	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]651	fn mint_with_token_uri_check_id(652		&mut self,653		caller: Caller,654		to: Address,655		token_id: U256,656		token_uri: String,657	) -> Result<bool> {658		let key = key::url();659		let permission = get_token_permission::<T>(self.id, &key)?;660		if !permission.collection_admin {661			return Err("operation is not allowed".into());662		}663664		let caller = T::CrossAccountId::from_eth(caller);665		let to = T::CrossAccountId::from_eth(to);666		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;667		let budget = self668			.recorder669			.weight_calls_budget(<StructureWeight<T>>::find_parent());670671		if <TokensMinted<T>>::get(self.id)672			.checked_add(1)673			.ok_or("item id overflow")?674			!= token_id675		{676			return Err("item id should be next".into());677		}678679		let mut properties = CollectionPropertiesVec::default();680		properties681			.try_push(Property {682				key,683				value: token_uri684					.into_bytes()685					.try_into()686					.map_err(|_| "token uri is too long")?,687			})688			.map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;689690		<Pallet<T>>::create_item(691			self,692			&caller,693			CreateItemData::<T> {694				properties,695				owner: to,696			},697			&budget,698		)699		.map_err(dispatch_to_evm::<T>)?;700		Ok(true)701	}702}703704fn get_token_property<T: Config>(705	collection: &CollectionHandle<T>,706	token_id: u32,707	key: &up_data_structs::PropertyKey,708) -> Result<String> {709	collection.consume_store_reads(1)?;710	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))711		.map_err(|_| Error::Revert("token properties not found".into()))?;712	if let Some(property) = properties.get(key) {713		return Ok(String::from_utf8_lossy(property).into());714	}715716	Err("property tokenURI not found".into())717}718719fn get_token_permission<T: Config>(720	collection_id: CollectionId,721	key: &PropertyKey,722) -> Result<PropertyPermission> {723	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)724		.map_err(|_| Error::Revert("no permissions for collection".into()))?;725	let a = token_property_permissions726		.get(key)727		.map(Clone::clone)728		.ok_or_else(|| {729			let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();730			Error::Revert(alloc::format!("no permission for key {key}"))731		})?;732	Ok(a)733}734735/// @title Unique extensions for ERC721.736#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]737impl<T: Config> NonfungibleHandle<T>738where739	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,740{741	/// @notice A descriptive name for a collection of NFTs in this contract742	fn name(&self) -> String {743		decode_utf16(self.name.iter().copied())744			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))745			.collect::<String>()746	}747748	/// @notice An abbreviated name for NFTs in this contract749	fn symbol(&self) -> String {750		String::from_utf8_lossy(&self.token_prefix).into()751	}752753	/// @notice A description for the collection.754	fn description(&self) -> String {755		decode_utf16(self.description.iter().copied())756			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))757			.collect::<String>()758	}759760	/// Returns the owner (in cross format) of the token.761	///762	/// @param tokenId Id for the token.763	#[solidity(hide)]764	fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {765		Self::owner_of_cross(self, token_id)766	}767768	/// Returns the owner (in cross format) of the token.769	///770	/// @param tokenId Id for the token.771	fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {772		Self::token_owner(self, token_id.try_into()?)773			.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))774			.map_err(|_| Error::Revert("token not found".into()))775	}776777	/// @notice Count all NFTs assigned to an owner778	/// @param owner An cross address for whom to query the balance779	/// @return The number of NFTs owned by `owner`, possibly zero780	fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {781		self.consume_store_reads(1)?;782		let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));783		Ok(balance.into())784	}785786	/// Returns the token properties.787	///788	/// @param tokenId Id for the token.789	/// @param keys Properties keys. Empty keys for all propertyes.790	/// @return Vector of properties key/value pairs.791	fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {792		let keys = keys793			.into_iter()794			.map(|key| {795				<Vec<u8>>::from(key)796					.try_into()797					.map_err(|_| Error::Revert("key too large".into()))798			})799			.collect::<Result<Vec<_>>>()?;800801		<Self as CommonCollectionOperations<T>>::token_properties(802			self,803			token_id.try_into()?,804			if keys.is_empty() { None } else { Some(keys) },805		)806		.into_iter()807		.map(eth::Property::try_from)808		.collect::<Result<Vec<_>>>()809	}810811	/// @notice Set or reaffirm the approved address for an NFT812	/// @dev The zero address indicates there is no approved address.813	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized814	///  operator of the current owner.815	/// @param approved The new substrate address approved NFT controller816	/// @param tokenId The NFT to approve817	#[weight(<SelfWeightOf<T>>::approve())]818	fn approve_cross(819		&mut self,820		caller: Caller,821		approved: eth::CrossAddress,822		token_id: U256,823	) -> Result<()> {824		let caller = T::CrossAccountId::from_eth(caller);825		let approved = approved.into_sub_cross_account::<T>()?;826		let token = token_id.try_into()?;827828		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))829			.map_err(dispatch_to_evm::<T>)?;830		Ok(())831	}832833	/// @notice Transfer ownership of an NFT834	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`835	///  is the zero address. Throws if `tokenId` is not a valid NFT.836	/// @param to The new owner837	/// @param tokenId The NFT to transfer838	#[weight(<CommonWeights<T>>::transfer())]839	fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {840		let caller = T::CrossAccountId::from_eth(caller);841		let to = T::CrossAccountId::from_eth(to);842		let token = token_id.try_into()?;843		let budget = self844			.recorder845			.weight_calls_budget(<StructureWeight<T>>::find_parent());846847		<Pallet<T>>::transfer(self, &caller, &to, token, &budget)848			.map_err(|e| dispatch_to_evm::<T>(e.error))?;849		Ok(())850	}851852	/// @notice Transfer ownership of an NFT853	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`854	///  is the zero address. Throws if `tokenId` is not a valid NFT.855	/// @param to The new owner856	/// @param tokenId The NFT to transfer857	#[weight(<CommonWeights<T>>::transfer())]858	fn transfer_cross(859		&mut self,860		caller: Caller,861		to: eth::CrossAddress,862		token_id: U256,863	) -> Result<()> {864		let caller = T::CrossAccountId::from_eth(caller);865		let to = to.into_sub_cross_account::<T>()?;866		let token = token_id.try_into()?;867		let budget = self868			.recorder869			.weight_calls_budget(<StructureWeight<T>>::find_parent());870871		<Pallet<T>>::transfer(self, &caller, &to, token, &budget)872			.map_err(|e| dispatch_to_evm::<T>(e.error))?;873		Ok(())874	}875876	/// @notice Transfer ownership of an NFT from cross account address to cross account address877	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`878	///  is the zero address. Throws if `tokenId` is not a valid NFT.879	/// @param from Cross acccount address of current owner880	/// @param to Cross acccount address of new owner881	/// @param tokenId The NFT to transfer882	#[weight(<CommonWeights<T>>::transfer_from())]883	fn transfer_from_cross(884		&mut self,885		caller: Caller,886		from: eth::CrossAddress,887		to: eth::CrossAddress,888		token_id: U256,889	) -> Result<()> {890		let caller = T::CrossAccountId::from_eth(caller);891		let from = from.into_sub_cross_account::<T>()?;892		let to = to.into_sub_cross_account::<T>()?;893		let token_id = token_id.try_into()?;894		let budget = self895			.recorder896			.weight_calls_budget(<StructureWeight<T>>::find_parent());897		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)898			.map_err(|e| dispatch_to_evm::<T>(e.error))?;899		Ok(())900	}901902	/// @notice Burns a specific ERC721 token.903	/// @dev Throws unless `msg.sender` is the current owner or an authorized904	///  operator for this NFT. Throws if `from` is not the current owner. Throws905	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.906	/// @param from The current owner of the NFT907	/// @param tokenId The NFT to transfer908	#[solidity(hide)]909	#[weight(<SelfWeightOf<T>>::burn_from())]910	fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {911		let caller = T::CrossAccountId::from_eth(caller);912		let from = T::CrossAccountId::from_eth(from);913		let token = token_id.try_into()?;914		let budget = self915			.recorder916			.weight_calls_budget(<StructureWeight<T>>::find_parent());917918		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)919			.map_err(dispatch_to_evm::<T>)?;920		Ok(())921	}922923	/// @notice Burns a specific ERC721 token.924	/// @dev Throws unless `msg.sender` is the current owner or an authorized925	///  operator for this NFT. Throws if `from` is not the current owner. Throws926	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.927	/// @param from The current owner of the NFT928	/// @param tokenId The NFT to transfer929	#[weight(<SelfWeightOf<T>>::burn_from())]930	fn burn_from_cross(931		&mut self,932		caller: Caller,933		from: eth::CrossAddress,934		token_id: U256,935	) -> Result<()> {936		let caller = T::CrossAccountId::from_eth(caller);937		let from = from.into_sub_cross_account::<T>()?;938		let token = token_id.try_into()?;939		let budget = self940			.recorder941			.weight_calls_budget(<StructureWeight<T>>::find_parent());942943		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)944			.map_err(dispatch_to_evm::<T>)?;945		Ok(())946	}947948	/// @notice Returns next free NFT ID.949	fn next_token_id(&self) -> Result<U256> {950		self.consume_store_reads(1)?;951		Ok(<Pallet<T>>::next_token_id(self)952			.map_err(dispatch_to_evm::<T>)?953			.into())954	}955956	/// @notice Function to mint multiple tokens.957	/// @dev `tokenIds` should be an array of consecutive numbers and first number958	///  should be obtained with `nextTokenId` method959	/// @param to The new owner960	/// @param tokenIds IDs of the minted NFTs961	#[solidity(hide)]962	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]963	fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {964		let caller = T::CrossAccountId::from_eth(caller);965		let to = T::CrossAccountId::from_eth(to);966		let mut expected_index = <TokensMinted<T>>::get(self.id)967			.checked_add(1)968			.ok_or("item id overflow")?;969		let budget = self970			.recorder971			.weight_calls_budget(<StructureWeight<T>>::find_parent());972973		let total_tokens = token_ids.len();974		for id in token_ids.into_iter() {975			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;976			if id != expected_index {977				return Err("item id should be next".into());978			}979			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;980		}981		let data = (0..total_tokens)982			.map(|_| CreateItemData::<T> {983				properties: BoundedVec::default(),984				owner: to.clone(),985			})986			.collect();987988		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)989			.map_err(dispatch_to_evm::<T>)?;990		Ok(true)991	}992993	/// @notice Function to mint a token.994	/// @param data Array of pairs of token owner and token's properties for minted token995	#[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]996	fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {997		let caller = T::CrossAccountId::from_eth(caller);998		let budget = self999			.recorder1000			.weight_calls_budget(<StructureWeight<T>>::find_parent());10011002		let mut create_nft_data = Vec::with_capacity(data.len());1003		for MintTokenData { owner, properties } in data {1004			let owner = owner.into_sub_cross_account::<T>()?;1005			create_nft_data.push(CreateItemData::<T> {1006				properties: properties1007					.into_iter()1008					.map(|property| property.try_into())1009					.collect::<Result<Vec<_>>>()?1010					.try_into()1011					.map_err(|_| "too many properties")?,1012				owner,1013			});1014		}10151016		<Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)1017			.map_err(dispatch_to_evm::<T>)?;1018		Ok(true)1019	}10201021	/// @notice Function to mint multiple tokens with the given tokenUris.1022	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1023	///  numbers and first number should be obtained with `nextTokenId` method1024	/// @param to The new owner1025	/// @param tokens array of pairs of token ID and token URI for minted tokens1026	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1027	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1028	fn mint_bulk_with_token_uri(1029		&mut self,1030		caller: Caller,1031		to: Address,1032		tokens: Vec<TokenUri>,1033	) -> Result<bool> {1034		let key = key::url();1035		let caller = T::CrossAccountId::from_eth(caller);1036		let to = T::CrossAccountId::from_eth(to);1037		let mut expected_index = <TokensMinted<T>>::get(self.id)1038			.checked_add(1)1039			.ok_or("item id overflow")?;1040		let budget = self1041			.recorder1042			.weight_calls_budget(<StructureWeight<T>>::find_parent());10431044		let mut data = Vec::with_capacity(tokens.len());1045		for TokenUri { id, uri } in tokens {1046			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1047			if id != expected_index {1048				return Err("item id should be next".into());1049			}1050			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10511052			let mut properties = CollectionPropertiesVec::default();1053			properties1054				.try_push(Property {1055					key: key.clone(),1056					value: uri1057						.into_bytes()1058						.try_into()1059						.map_err(|_| "token uri is too long")?,1060				})1061				.map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10621063			data.push(CreateItemData::<T> {1064				properties,1065				owner: to.clone(),1066			});1067		}10681069		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1070			.map_err(dispatch_to_evm::<T>)?;1071		Ok(true)1072	}10731074	/// @notice Function to mint a token.1075	/// @param to The new owner crossAccountId1076	/// @param properties Properties of minted token1077	/// @return uint256 The id of the newly minted token1078	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1079	fn mint_cross(1080		&mut self,1081		caller: Caller,1082		to: eth::CrossAddress,1083		properties: Vec<eth::Property>,1084	) -> Result<U256> {1085		let token_id = <TokensMinted<T>>::get(self.id)1086			.checked_add(1)1087			.ok_or("item id overflow")?;10881089		let to = to.into_sub_cross_account::<T>()?;10901091		let properties = properties1092			.into_iter()1093			.map(eth::Property::try_into)1094			.collect::<Result<Vec<_>>>()?1095			.try_into()1096			.map_err(|_| Error::Revert("too many properties".to_string()))?;10971098		let caller = T::CrossAccountId::from_eth(caller);10991100		let budget = self1101			.recorder1102			.weight_calls_budget(<StructureWeight<T>>::find_parent());11031104		<Pallet<T>>::create_item(1105			self,1106			&caller,1107			CreateItemData::<T> {1108				properties,1109				owner: to,1110			},1111			&budget,1112		)1113		.map_err(dispatch_to_evm::<T>)?;11141115		Ok(token_id.into())1116	}11171118	/// @notice Returns collection helper contract address1119	fn collection_helper_address(&self) -> Address {1120		T::ContractAddress::get()1121	}1122}11231124#[solidity_interface(1125	name = UniqueNFT,1126	is(1127		ERC721,1128		ERC721Enumerable,1129		ERC721UniqueExtensions,1130		ERC721UniqueMintable,1131		ERC721Burnable,1132		ERC721Metadata(if(this.flags.erc721metadata)),1133		Collection(via(common_mut returns CollectionHandle<T>)),1134		TokenProperties,1135	),1136	enum(derive(PreDispatch)),1137)]1138impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11391140// Not a tests, but code generators1141generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1142generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11431144impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1145where1146	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1147{1148	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11491150	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1151		call::<T, UniqueNFTCall<T>, _, _>(handle, self)1152	}1153}
after · pallets/nonfungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26	char::{decode_utf16, REPLACEMENT_CHARACTER},27	convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::BoundedVec;32use pallet_common::{33	erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34	eth::{self, TokenUri},35	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39	call, dispatch_to_evm,40	execution::{Error, PreDispatch, Result},41	frontier_contract, SubstrateRecorder,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47	budget::Budget, CollectionId, CollectionPropertiesVec, Property, PropertyKey,48	PropertyKeyPermission, PropertyPermission, TokenId,49};5051use crate::{52	common::CommonWeights, weights::WeightInfo, AccountBalance, Config, CreateItemData,53	NonfungibleHandle, Pallet, SelfWeightOf, TokenData, TokenProperties, TokensMinted,54};5556/// Nft events.57#[derive(ToLog)]58pub enum ERC721TokenEvent {59	/// The token has been changed.60	TokenChanged {61		/// Token ID.62		#[indexed]63		token_id: U256,64	},65}6667/// Token minting parameters68#[derive(AbiCoder, Default, Debug)]69pub struct MintTokenData {70	/// Minted token owner71	pub owner: eth::CrossAddress,72	/// Minted token properties73	pub properties: Vec<eth::Property>,74}7576frontier_contract! {77	macro_rules! NonfungibleHandle_result {...}78	impl<T: Config> Contract for NonfungibleHandle<T> {...}79}8081fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {82	recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())83}8485/// @title A contract that allows to set and delete token properties and change token property permissions.86#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]87impl<T: Config> NonfungibleHandle<T> {88	/// @notice Set permissions for token property.89	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.90	/// @param key Property key.91	/// @param isMutable Permission to mutate property.92	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.93	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.94	#[solidity(hide)]95	#[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]96	fn set_token_property_permission(97		&mut self,98		caller: Caller,99		key: String,100		is_mutable: bool,101		collection_admin: bool,102		token_owner: bool,103	) -> Result<()> {104		let caller = T::CrossAccountId::from_eth(caller);105		<Pallet<T>>::set_token_property_permissions(106			self,107			&caller,108			vec![PropertyKeyPermission {109				key: <Vec<u8>>::from(key)110					.try_into()111					.map_err(|_| "too long key")?,112				permission: PropertyPermission {113					mutable: is_mutable,114					collection_admin,115					token_owner,116				},117			}],118		)119		.map_err(dispatch_to_evm::<T>)120	}121122	/// @notice Set permissions for token property.123	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.124	/// @param permissions Permissions for keys.125	#[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]126	fn set_token_property_permissions(127		&mut self,128		caller: Caller,129		permissions: Vec<eth::TokenPropertyPermission>,130	) -> Result<()> {131		let caller = T::CrossAccountId::from_eth(caller);132		let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;133134		<Pallet<T>>::set_token_property_permissions(self, &caller, perms)135			.map_err(dispatch_to_evm::<T>)136	}137138	/// @notice Get permissions for token properties.139	fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {140		let perms = <Pallet<T>>::token_property_permission(self.id);141		Ok(perms142			.into_iter()143			.map(eth::TokenPropertyPermission::from)144			.collect())145	}146147	/// @notice Set token property value.148	/// @dev Throws error if `msg.sender` has no permission to edit the property.149	/// @param tokenId ID of the token.150	/// @param key Property key.151	/// @param value Property value.152	#[solidity(hide)]153	#[weight(<CommonWeights<T>>::set_token_properties(1))]154	fn set_property(155		&mut self,156		caller: Caller,157		token_id: U256,158		key: String,159		value: Bytes,160	) -> Result<()> {161		let caller = T::CrossAccountId::from_eth(caller);162		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;163		let key = <Vec<u8>>::from(key)164			.try_into()165			.map_err(|_| "key too long")?;166		let value = value.0.try_into().map_err(|_| "value too long")?;167168		<Pallet<T>>::set_token_property(169			self,170			&caller,171			TokenId(token_id),172			Property { key, value },173			&nesting_budget(&self.recorder),174		)175		.map_err(dispatch_to_evm::<T>)176	}177178	/// @notice Set token properties value.179	/// @dev Throws error if `msg.sender` has no permission to edit the property.180	/// @param tokenId ID of the token.181	/// @param properties settable properties182	#[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]183	fn set_properties(184		&mut self,185		caller: Caller,186		token_id: U256,187		properties: Vec<eth::Property>,188	) -> Result<()> {189		let caller = T::CrossAccountId::from_eth(caller);190		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;191192		let properties = properties193			.into_iter()194			.map(eth::Property::try_into)195			.collect::<Result<Vec<_>>>()?;196197		<Pallet<T>>::set_token_properties(198			self,199			&caller,200			TokenId(token_id),201			properties.into_iter(),202			&nesting_budget(&self.recorder),203		)204		.map_err(dispatch_to_evm::<T>)205	}206207	/// @notice Delete token property value.208	/// @dev Throws error if `msg.sender` has no permission to edit the property.209	/// @param tokenId ID of the token.210	/// @param key Property key.211	#[solidity(hide)]212	#[weight(<CommonWeights<T>>::delete_token_properties(1))]213	fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {214		let caller = T::CrossAccountId::from_eth(caller);215		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;216		let key = <Vec<u8>>::from(key)217			.try_into()218			.map_err(|_| "key too long")?;219220		<Pallet<T>>::delete_token_property(221			self,222			&caller,223			TokenId(token_id),224			key,225			&nesting_budget(&self.recorder),226		)227		.map_err(dispatch_to_evm::<T>)228	}229230	/// @notice Delete token properties value.231	/// @dev Throws error if `msg.sender` has no permission to edit the property.232	/// @param tokenId ID of the token.233	/// @param keys Properties key.234	#[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]235	fn delete_properties(236		&mut self,237		token_id: U256,238		caller: Caller,239		keys: Vec<String>,240	) -> Result<()> {241		let caller = T::CrossAccountId::from_eth(caller);242		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;243		let keys = keys244			.into_iter()245			.map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))246			.collect::<Result<Vec<_>>>()?;247248		<Pallet<T>>::delete_token_properties(249			self,250			&caller,251			TokenId(token_id),252			keys.into_iter(),253			&nesting_budget(&self.recorder),254		)255		.map_err(dispatch_to_evm::<T>)256	}257258	/// @notice Get token property value.259	/// @dev Throws error if key not found260	/// @param tokenId ID of the token.261	/// @param key Property key.262	/// @return Property value bytes263	fn property(&self, token_id: U256, key: String) -> Result<Bytes> {264		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;265		let key = <Vec<u8>>::from(key)266			.try_into()267			.map_err(|_| "key too long")?;268269		let props =270			<TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;271		let prop = props.get(&key).ok_or("key not found")?;272273		Ok(prop.to_vec().into())274	}275}276277#[derive(ToLog)]278pub enum ERC721Events {279	/// @dev This emits when ownership of any NFT changes by any mechanism.280	///  This event emits when NFTs are created (`from` == 0) and destroyed281	///  (`to` == 0). Exception: during contract creation, any number of NFTs282	///  may be created and assigned without emitting Transfer. At the time of283	///  any transfer, the approved address for that NFT (if any) is reset to none.284	Transfer {285		#[indexed]286		from: Address,287		#[indexed]288		to: Address,289		#[indexed]290		token_id: U256,291	},292	/// @dev This emits when the approved address for an NFT is changed or293	///  reaffirmed. The zero address indicates there is no approved address.294	///  When a Transfer event emits, this also indicates that the approved295	///  address for that NFT (if any) is reset to none.296	Approval {297		#[indexed]298		owner: Address,299		#[indexed]300		approved: Address,301		#[indexed]302		token_id: U256,303	},304	/// @dev This emits when an operator is enabled or disabled for an owner.305	///  The operator can manage all NFTs of the owner.306	#[allow(dead_code)]307	ApprovalForAll {308		#[indexed]309		owner: Address,310		#[indexed]311		operator: Address,312		approved: bool,313	},314}315316/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension317/// @dev See https://eips.ethereum.org/EIPS/eip-721318#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]319impl<T: Config> NonfungibleHandle<T>320where321	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,322{323	/// @notice A descriptive name for a collection of NFTs in this contract324	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`325	#[solidity(hide, rename_selector = "name")]326	fn name_proxy(&self) -> String {327		self.name()328	}329330	/// @notice An abbreviated name for NFTs in this contract331	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`332	#[solidity(hide, rename_selector = "symbol")]333	fn symbol_proxy(&self) -> String {334		self.symbol()335	}336337	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.338	///339	/// @dev If the token has a `url` property and it is not empty, it is returned.340	///  Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.341	///  If the collection property `baseURI` is empty or absent, return "" (empty string)342	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix343	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).344	///345	/// @return token's const_metadata346	#[solidity(rename_selector = "tokenURI")]347	fn token_uri(&self, token_id: U256) -> Result<String> {348		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;349350		match get_token_property(self, token_id_u32, &key::url()).as_deref() {351			Err(_) | Ok("") => (),352			Ok(url) => {353				return Ok(url.into());354			}355		};356357		let base_uri =358			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())359				.map(BoundedVec::into_inner)360				.map(String::from_utf8)361				.transpose()362				.map_err(|e| {363					Error::Revert(alloc::format!(364						"can not convert value \"baseURI\" to string with error \"{e}\""365					))366				})?;367368		let base_uri = match base_uri.as_deref() {369			None | Some("") => {370				return Ok("".into());371			}372			Some(base_uri) => base_uri.into(),373		};374375		Ok(376			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {377				Err(_) | Ok("") => base_uri,378				Ok(suffix) => base_uri + suffix,379			},380		)381	}382}383384/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension385/// @dev See https://eips.ethereum.org/EIPS/eip-721386#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]387impl<T: Config> NonfungibleHandle<T> {388	/// @notice Enumerate valid NFTs389	/// @param index A counter less than `totalSupply()`390	/// @return The token identifier for the `index`th NFT,391	///  (sort order not specified)392	fn token_by_index(&self, index: U256) -> U256 {393		index394	}395396	/// @dev Not implemented397	fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {398		// TODO: Not implemetable399		Err("not implemented".into())400	}401402	/// @notice Count NFTs tracked by this contract403	/// @return A count of valid NFTs tracked by this contract, where each one of404	///  them has an assigned and queryable owner not equal to the zero address405	fn total_supply(&self) -> Result<U256> {406		self.consume_store_reads(1)?;407		Ok(<Pallet<T>>::total_supply(self).into())408	}409}410411/// @title ERC-721 Non-Fungible Token Standard412/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md413#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]414impl<T: Config> NonfungibleHandle<T> {415	/// @notice Count all NFTs assigned to an owner416	/// @dev NFTs assigned to the zero address are considered invalid, and this417	///  function throws for queries about the zero address.418	/// @param owner An address for whom to query the balance419	/// @return The number of NFTs owned by `owner`, possibly zero420	fn balance_of(&self, owner: Address) -> Result<U256> {421		self.consume_store_reads(1)?;422		let owner = T::CrossAccountId::from_eth(owner);423		let balance = <AccountBalance<T>>::get((self.id, owner));424		Ok(balance.into())425	}426	/// @notice Find the owner of an NFT427	/// @dev NFTs assigned to zero address are considered invalid, and queries428	///  about them do throw.429	/// @param tokenId The identifier for an NFT430	/// @return The address of the owner of the NFT431	fn owner_of(&self, token_id: U256) -> Result<Address> {432		self.consume_store_reads(1)?;433		let token: TokenId = token_id.try_into()?;434		Ok(*<TokenData<T>>::get((self.id, token))435			.ok_or("token not found")?436			.owner437			.as_eth())438	}439	/// @dev Not implemented440	#[solidity(rename_selector = "safeTransferFrom")]441	fn safe_transfer_from_with_data(442		&mut self,443		_from: Address,444		_to: Address,445		_token_id: U256,446		_data: Bytes,447	) -> Result<()> {448		// TODO: Not implemetable449		Err("not implemented".into())450	}451	/// @dev Not implemented452	fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {453		// TODO: Not implemetable454		Err("not implemented".into())455	}456457	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE458	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE459	///  THEY MAY BE PERMANENTLY LOST460	/// @dev Throws unless `msg.sender` is the current owner or an authorized461	///  operator for this NFT. Throws if `from` is not the current owner. Throws462	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.463	/// @param from The current owner of the NFT464	/// @param to The new owner465	/// @param tokenId The NFT to transfer466	#[weight(<CommonWeights<T>>::transfer_from())]467	fn transfer_from(468		&mut self,469		caller: Caller,470		from: Address,471		to: Address,472		token_id: U256,473	) -> Result<()> {474		let caller = T::CrossAccountId::from_eth(caller);475		let from = T::CrossAccountId::from_eth(from);476		let to = T::CrossAccountId::from_eth(to);477		let token = token_id.try_into()?;478479		<Pallet<T>>::transfer_from(480			self,481			&caller,482			&from,483			&to,484			token,485			&nesting_budget(&self.recorder),486		)487		.map_err(|e| dispatch_to_evm::<T>(e.error))?;488		Ok(())489	}490491	/// @notice Set or reaffirm the approved address for an NFT492	/// @dev The zero address indicates there is no approved address.493	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized494	///  operator of the current owner.495	/// @param approved The new approved NFT controller496	/// @param tokenId The NFT to approve497	#[weight(<SelfWeightOf<T>>::approve())]498	fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {499		let caller = T::CrossAccountId::from_eth(caller);500		let approved = T::CrossAccountId::from_eth(approved);501		let token = token_id.try_into()?;502503		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))504			.map_err(dispatch_to_evm::<T>)?;505		Ok(())506	}507508	/// @notice Sets or unsets the approval of a given operator.509	/// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.510	/// @param operator Operator511	/// @param approved Should operator status be granted or revoked?512	#[weight(<SelfWeightOf<T>>::set_allowance_for_all())]513	fn set_approval_for_all(514		&mut self,515		caller: Caller,516		operator: Address,517		approved: bool,518	) -> Result<()> {519		let caller = T::CrossAccountId::from_eth(caller);520		let operator = T::CrossAccountId::from_eth(operator);521522		<Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)523			.map_err(dispatch_to_evm::<T>)?;524		Ok(())525	}526527	/// @notice Get the approved address for a single NFT528	/// @dev Throws if `tokenId` is not a valid NFT529	/// @param tokenId The NFT to find the approved address for530	/// @return The approved address for this NFT, or the zero address if there is none531	fn get_approved(&self, token_id: U256) -> Result<Address> {532		let token_id = token_id.try_into()?;533		let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;534		Ok(if let Some(operator) = operator {535			*operator.as_eth()536		} else {537			Address::zero()538		})539	}540541	/// @notice Tells whether the given `owner` approves the `operator`.542	#[weight(<SelfWeightOf<T>>::allowance_for_all())]543	fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {544		let owner = T::CrossAccountId::from_eth(owner);545		let operator = T::CrossAccountId::from_eth(operator);546547		Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))548	}549}550551/// @title ERC721 Token that can be irreversibly burned (destroyed).552#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]553impl<T: Config> NonfungibleHandle<T> {554	/// @notice Burns a specific ERC721 token.555	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized556	///  operator of the current owner.557	/// @param tokenId The NFT to approve558	#[weight(<SelfWeightOf<T>>::burn_item())]559	fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {560		let caller = T::CrossAccountId::from_eth(caller);561		let token = token_id.try_into()?;562563		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;564		Ok(())565	}566}567568/// @title ERC721 minting logic.569#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]570impl<T: Config> NonfungibleHandle<T> {571	/// @notice Function to mint a token.572	/// @param to The new owner573	/// @return uint256 The id of the newly minted token574	#[weight(<SelfWeightOf<T>>::create_item())]575	fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {576		let token_id: U256 = <TokensMinted<T>>::get(self.id)577			.checked_add(1)578			.ok_or("item id overflow")?579			.into();580		self.mint_check_id(caller, to, token_id)?;581		Ok(token_id)582	}583584	/// @notice Function to mint a token.585	/// @dev `tokenId` should be obtained with `nextTokenId` method,586	///  unlike standard, you can't specify it manually587	/// @param to The new owner588	/// @param tokenId ID of the minted NFT589	#[solidity(hide, rename_selector = "mint")]590	#[weight(<SelfWeightOf<T>>::create_item())]591	fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {592		let caller = T::CrossAccountId::from_eth(caller);593		let to = T::CrossAccountId::from_eth(to);594		let token_id: u32 = token_id.try_into()?;595596		if <TokensMinted<T>>::get(self.id)597			.checked_add(1)598			.ok_or("item id overflow")?599			!= token_id600		{601			return Err("item id should be next".into());602		}603604		<Pallet<T>>::create_item(605			self,606			&caller,607			CreateItemData::<T> {608				properties: BoundedVec::default(),609				owner: to,610			},611			&nesting_budget(&self.recorder),612		)613		.map_err(dispatch_to_evm::<T>)?;614615		Ok(true)616	}617618	/// @notice Function to mint token with the given tokenUri.619	/// @param to The new owner620	/// @param tokenUri Token URI that would be stored in the NFT properties621	/// @return uint256 The id of the newly minted token622	#[solidity(rename_selector = "mintWithTokenURI")]623	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]624	fn mint_with_token_uri(625		&mut self,626		caller: Caller,627		to: Address,628		token_uri: String,629	) -> Result<U256> {630		let token_id: U256 = <TokensMinted<T>>::get(self.id)631			.checked_add(1)632			.ok_or("item id overflow")?633			.into();634		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;635		Ok(token_id)636	}637638	/// @notice Function to mint token with the given tokenUri.639	/// @dev `tokenId` should be obtained with `nextTokenId` method,640	///  unlike standard, you can't specify it manually641	/// @param to The new owner642	/// @param tokenId ID of the minted NFT643	/// @param tokenUri Token URI that would be stored in the NFT properties644	#[solidity(hide, rename_selector = "mintWithTokenURI")]645	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]646	fn mint_with_token_uri_check_id(647		&mut self,648		caller: Caller,649		to: Address,650		token_id: U256,651		token_uri: String,652	) -> Result<bool> {653		let key = key::url();654		let permission = get_token_permission::<T>(self.id, &key)?;655		if !permission.collection_admin {656			return Err("operation is not allowed".into());657		}658659		let caller = T::CrossAccountId::from_eth(caller);660		let to = T::CrossAccountId::from_eth(to);661		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;662663		if <TokensMinted<T>>::get(self.id)664			.checked_add(1)665			.ok_or("item id overflow")?666			!= token_id667		{668			return Err("item id should be next".into());669		}670671		let mut properties = CollectionPropertiesVec::default();672		properties673			.try_push(Property {674				key,675				value: token_uri676					.into_bytes()677					.try_into()678					.map_err(|_| "token uri is too long")?,679			})680			.map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;681682		<Pallet<T>>::create_item(683			self,684			&caller,685			CreateItemData::<T> {686				properties,687				owner: to,688			},689			&nesting_budget(&self.recorder),690		)691		.map_err(dispatch_to_evm::<T>)?;692		Ok(true)693	}694}695696fn get_token_property<T: Config>(697	collection: &CollectionHandle<T>,698	token_id: u32,699	key: &up_data_structs::PropertyKey,700) -> Result<String> {701	collection.consume_store_reads(1)?;702	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))703		.map_err(|_| Error::Revert("token properties not found".into()))?;704	if let Some(property) = properties.get(key) {705		return Ok(String::from_utf8_lossy(property).into());706	}707708	Err("property tokenURI not found".into())709}710711fn get_token_permission<T: Config>(712	collection_id: CollectionId,713	key: &PropertyKey,714) -> Result<PropertyPermission> {715	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)716		.map_err(|_| Error::Revert("no permissions for collection".into()))?;717	let a = token_property_permissions718		.get(key)719		.map(Clone::clone)720		.ok_or_else(|| {721			let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();722			Error::Revert(alloc::format!("no permission for key {key}"))723		})?;724	Ok(a)725}726727/// @title Unique extensions for ERC721.728#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]729impl<T: Config> NonfungibleHandle<T>730where731	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,732{733	/// @notice A descriptive name for a collection of NFTs in this contract734	fn name(&self) -> String {735		decode_utf16(self.name.iter().copied())736			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))737			.collect::<String>()738	}739740	/// @notice An abbreviated name for NFTs in this contract741	fn symbol(&self) -> String {742		String::from_utf8_lossy(&self.token_prefix).into()743	}744745	/// @notice A description for the collection.746	fn description(&self) -> String {747		decode_utf16(self.description.iter().copied())748			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))749			.collect::<String>()750	}751752	/// Returns the owner (in cross format) of the token.753	///754	/// @param tokenId Id for the token.755	#[solidity(hide)]756	fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {757		Self::owner_of_cross(self, token_id)758	}759760	/// Returns the owner (in cross format) of the token.761	///762	/// @param tokenId Id for the token.763	fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {764		Self::token_owner(self, token_id.try_into()?)765			.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))766			.map_err(|_| Error::Revert("token not found".into()))767	}768769	/// @notice Count all NFTs assigned to an owner770	/// @param owner An cross address for whom to query the balance771	/// @return The number of NFTs owned by `owner`, possibly zero772	fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {773		self.consume_store_reads(1)?;774		let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));775		Ok(balance.into())776	}777778	/// Returns the token properties.779	///780	/// @param tokenId Id for the token.781	/// @param keys Properties keys. Empty keys for all propertyes.782	/// @return Vector of properties key/value pairs.783	fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {784		let keys = keys785			.into_iter()786			.map(|key| {787				<Vec<u8>>::from(key)788					.try_into()789					.map_err(|_| Error::Revert("key too large".into()))790			})791			.collect::<Result<Vec<_>>>()?;792793		<Self as CommonCollectionOperations<T>>::token_properties(794			self,795			token_id.try_into()?,796			if keys.is_empty() { None } else { Some(keys) },797		)798		.into_iter()799		.map(eth::Property::try_from)800		.collect::<Result<Vec<_>>>()801	}802803	/// @notice Set or reaffirm the approved address for an NFT804	/// @dev The zero address indicates there is no approved address.805	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized806	///  operator of the current owner.807	/// @param approved The new substrate address approved NFT controller808	/// @param tokenId The NFT to approve809	#[weight(<SelfWeightOf<T>>::approve())]810	fn approve_cross(811		&mut self,812		caller: Caller,813		approved: eth::CrossAddress,814		token_id: U256,815	) -> Result<()> {816		let caller = T::CrossAccountId::from_eth(caller);817		let approved = approved.into_sub_cross_account::<T>()?;818		let token = token_id.try_into()?;819820		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))821			.map_err(dispatch_to_evm::<T>)?;822		Ok(())823	}824825	/// @notice Transfer ownership of an NFT826	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`827	///  is the zero address. Throws if `tokenId` is not a valid NFT.828	/// @param to The new owner829	/// @param tokenId The NFT to transfer830	#[weight(<CommonWeights<T>>::transfer())]831	fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {832		let caller = T::CrossAccountId::from_eth(caller);833		let to = T::CrossAccountId::from_eth(to);834		let token = token_id.try_into()?;835836		<Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))837			.map_err(|e| dispatch_to_evm::<T>(e.error))?;838		Ok(())839	}840841	/// @notice Transfer ownership of an NFT842	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`843	///  is the zero address. Throws if `tokenId` is not a valid NFT.844	/// @param to The new owner845	/// @param tokenId The NFT to transfer846	#[weight(<CommonWeights<T>>::transfer())]847	fn transfer_cross(848		&mut self,849		caller: Caller,850		to: eth::CrossAddress,851		token_id: U256,852	) -> Result<()> {853		let caller = T::CrossAccountId::from_eth(caller);854		let to = to.into_sub_cross_account::<T>()?;855		let token = token_id.try_into()?;856857		<Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))858			.map_err(|e| dispatch_to_evm::<T>(e.error))?;859		Ok(())860	}861862	/// @notice Transfer ownership of an NFT from cross account address to cross account address863	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`864	///  is the zero address. Throws if `tokenId` is not a valid NFT.865	/// @param from Cross acccount address of current owner866	/// @param to Cross acccount address of new owner867	/// @param tokenId The NFT to transfer868	#[weight(<CommonWeights<T>>::transfer_from())]869	fn transfer_from_cross(870		&mut self,871		caller: Caller,872		from: eth::CrossAddress,873		to: eth::CrossAddress,874		token_id: U256,875	) -> Result<()> {876		let caller = T::CrossAccountId::from_eth(caller);877		let from = from.into_sub_cross_account::<T>()?;878		let to = to.into_sub_cross_account::<T>()?;879		let token_id = token_id.try_into()?;880881		Pallet::<T>::transfer_from(882			self,883			&caller,884			&from,885			&to,886			token_id,887			&nesting_budget(&self.recorder),888		)889		.map_err(|e| dispatch_to_evm::<T>(e.error))?;890		Ok(())891	}892893	/// @notice Burns a specific ERC721 token.894	/// @dev Throws unless `msg.sender` is the current owner or an authorized895	///  operator for this NFT. Throws if `from` is not the current owner. Throws896	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.897	/// @param from The current owner of the NFT898	/// @param tokenId The NFT to transfer899	#[solidity(hide)]900	#[weight(<SelfWeightOf<T>>::burn_from())]901	fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {902		let caller = T::CrossAccountId::from_eth(caller);903		let from = T::CrossAccountId::from_eth(from);904		let token = token_id.try_into()?;905906		<Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))907			.map_err(dispatch_to_evm::<T>)?;908		Ok(())909	}910911	/// @notice Burns a specific ERC721 token.912	/// @dev Throws unless `msg.sender` is the current owner or an authorized913	///  operator for this NFT. Throws if `from` is not the current owner. Throws914	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.915	/// @param from The current owner of the NFT916	/// @param tokenId The NFT to transfer917	#[weight(<SelfWeightOf<T>>::burn_from())]918	fn burn_from_cross(919		&mut self,920		caller: Caller,921		from: eth::CrossAddress,922		token_id: U256,923	) -> Result<()> {924		let caller = T::CrossAccountId::from_eth(caller);925		let from = from.into_sub_cross_account::<T>()?;926		let token = token_id.try_into()?;927928		<Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))929			.map_err(dispatch_to_evm::<T>)?;930		Ok(())931	}932933	/// @notice Returns next free NFT ID.934	fn next_token_id(&self) -> Result<U256> {935		self.consume_store_reads(1)?;936		Ok(<Pallet<T>>::next_token_id(self)937			.map_err(dispatch_to_evm::<T>)?938			.into())939	}940941	/// @notice Function to mint multiple tokens.942	/// @dev `tokenIds` should be an array of consecutive numbers and first number943	///  should be obtained with `nextTokenId` method944	/// @param to The new owner945	/// @param tokenIds IDs of the minted NFTs946	#[solidity(hide)]947	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]948	fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {949		let caller = T::CrossAccountId::from_eth(caller);950		let to = T::CrossAccountId::from_eth(to);951		let mut expected_index = <TokensMinted<T>>::get(self.id)952			.checked_add(1)953			.ok_or("item id overflow")?;954955		let total_tokens = token_ids.len();956		for id in token_ids.into_iter() {957			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;958			if id != expected_index {959				return Err("item id should be next".into());960			}961			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;962		}963		let data = (0..total_tokens)964			.map(|_| CreateItemData::<T> {965				properties: BoundedVec::default(),966				owner: to.clone(),967			})968			.collect();969970		<Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))971			.map_err(dispatch_to_evm::<T>)?;972		Ok(true)973	}974975	/// @notice Function to mint a token.976	/// @param data Array of pairs of token owner and token's properties for minted token977	#[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]978	fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {979		let caller = T::CrossAccountId::from_eth(caller);980981		let mut create_nft_data = Vec::with_capacity(data.len());982		for MintTokenData { owner, properties } in data {983			let owner = owner.into_sub_cross_account::<T>()?;984			create_nft_data.push(CreateItemData::<T> {985				properties: properties986					.into_iter()987					.map(|property| property.try_into())988					.collect::<Result<Vec<_>>>()?989					.try_into()990					.map_err(|_| "too many properties")?,991				owner,992			});993		}994995		<Pallet<T>>::create_multiple_items(996			self,997			&caller,998			create_nft_data,999			&nesting_budget(&self.recorder),1000		)1001		.map_err(dispatch_to_evm::<T>)?;1002		Ok(true)1003	}10041005	/// @notice Function to mint multiple tokens with the given tokenUris.1006	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1007	///  numbers and first number should be obtained with `nextTokenId` method1008	/// @param to The new owner1009	/// @param tokens array of pairs of token ID and token URI for minted tokens1010	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1011	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1012	fn mint_bulk_with_token_uri(1013		&mut self,1014		caller: Caller,1015		to: Address,1016		tokens: Vec<TokenUri>,1017	) -> Result<bool> {1018		let key = key::url();1019		let caller = T::CrossAccountId::from_eth(caller);1020		let to = T::CrossAccountId::from_eth(to);1021		let mut expected_index = <TokensMinted<T>>::get(self.id)1022			.checked_add(1)1023			.ok_or("item id overflow")?;10241025		let mut data = Vec::with_capacity(tokens.len());1026		for TokenUri { id, uri } in tokens {1027			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1028			if id != expected_index {1029				return Err("item id should be next".into());1030			}1031			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10321033			let mut properties = CollectionPropertiesVec::default();1034			properties1035				.try_push(Property {1036					key: key.clone(),1037					value: uri1038						.into_bytes()1039						.try_into()1040						.map_err(|_| "token uri is too long")?,1041				})1042				.map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10431044			data.push(CreateItemData::<T> {1045				properties,1046				owner: to.clone(),1047			});1048		}10491050		<Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1051			.map_err(dispatch_to_evm::<T>)?;1052		Ok(true)1053	}10541055	/// @notice Function to mint a token.1056	/// @param to The new owner crossAccountId1057	/// @param properties Properties of minted token1058	/// @return uint256 The id of the newly minted token1059	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1060	fn mint_cross(1061		&mut self,1062		caller: Caller,1063		to: eth::CrossAddress,1064		properties: Vec<eth::Property>,1065	) -> Result<U256> {1066		let token_id = <TokensMinted<T>>::get(self.id)1067			.checked_add(1)1068			.ok_or("item id overflow")?;10691070		let to = to.into_sub_cross_account::<T>()?;10711072		let properties = properties1073			.into_iter()1074			.map(eth::Property::try_into)1075			.collect::<Result<Vec<_>>>()?1076			.try_into()1077			.map_err(|_| Error::Revert("too many properties".to_string()))?;10781079		let caller = T::CrossAccountId::from_eth(caller);10801081		<Pallet<T>>::create_item(1082			self,1083			&caller,1084			CreateItemData::<T> {1085				properties,1086				owner: to,1087			},1088			&nesting_budget(&self.recorder),1089		)1090		.map_err(dispatch_to_evm::<T>)?;10911092		Ok(token_id.into())1093	}10941095	/// @notice Returns collection helper contract address1096	fn collection_helper_address(&self) -> Address {1097		T::ContractAddress::get()1098	}1099}11001101#[solidity_interface(1102	name = UniqueNFT,1103	is(1104		ERC721,1105		ERC721Enumerable,1106		ERC721UniqueExtensions,1107		ERC721UniqueMintable,1108		ERC721Burnable,1109		ERC721Metadata(if(this.flags.erc721metadata)),1110		Collection(via(common_mut returns CollectionHandle<T>)),1111		TokenProperties,1112	),1113	enum(derive(PreDispatch)),1114)]1115impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11161117// Not a tests, but code generators1118generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1119generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11201121impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1122where1123	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1124{1125	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11261127	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1128		call::<T, UniqueNFTCall<T>, _, _>(handle, self)1129	}1130}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -109,7 +109,7 @@
 };
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use pallet_structure::{Error as StructureError, Pallet as PalletStructure};
+use pallet_structure::Pallet as PalletStructure;
 use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 use sp_core::{Get, H160};
@@ -502,52 +502,7 @@
 		));
 		Ok(())
 	}
-
-	/// Same as [`burn`] but burns all the tokens that are nested in the token first
-	///
-	/// - `self_budget`: Limit for searching children in depth.
-	/// - `breadth_budget`: Limit of breadth of searching children.
-	///
-	/// [`burn`]: struct.Pallet.html#method.burn
-	#[transactional]
-	pub fn burn_recursively(
-		collection: &NonfungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo {
-		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
 
-		let current_token_account =
-			T::CrossTokenAddressMapping::token_to_address(collection.id, token);
-
-		let mut weight = Weight::zero();
-
-		// This method is transactional, if user in fact doesn't have permissions to remove token -
-		// tokens removed here will be restored after rejected transaction
-		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {
-			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);
-			let PostDispatchInfo { actual_weight, .. } =
-				<PalletStructure<T>>::burn_item_recursively(
-					current_token_account.clone(),
-					collection,
-					token,
-					self_budget,
-					breadth_budget,
-				)?;
-			if let Some(actual_weight) = actual_weight {
-				weight = weight.saturating_add(actual_weight);
-			}
-		}
-
-		Self::burn(collection, sender, token)?;
-		DispatchResultWithPostInfo::Ok(PostDispatchInfo {
-			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),
-			pays_fee: Pays::Yes,
-		})
-	}
-
 	/// A batch operation to add, edit or remove properties for a token.
 	///
 	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
@@ -568,7 +523,7 @@
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		let mut property_writer =
-			pallet_common::property_writer_for_existing_token(collection, sender);
+			pallet_common::ExistingTokenPropertyWriter::new(collection, sender);
 
 		property_writer.write_token_properties(
 			sender,
@@ -915,7 +870,7 @@
 
 		// =========
 
-		let mut property_writer = pallet_common::property_writer_for_new_token(collection, sender);
+		let mut property_writer = pallet_common::NewTokenPropertyWriter::new(collection, sender);
 
 		with_transaction(|| {
 			for (i, data) in data.iter().enumerate() {
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -3,9 +3,9 @@
 //! Autogenerated weights for pallet_nonfungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-09-30, STEPS: `50`, REPEAT: `400`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-10-05, STEPS: `50`, REPEAT: `80`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `bench-host`, CPU: `Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz`
+//! HOSTNAME: `hearthstone`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -20,7 +20,7 @@
 // *
 // --template=.maintain/frame-weight-template.hbs
 // --steps=50
-// --repeat=400
+// --repeat=80
 // --heap-pages=4096
 // --output=./pallets/nonfungible/src/weights.rs
 
@@ -46,7 +46,8 @@
 	fn burn_from() -> Weight;
 	fn set_token_property_permissions(b: u32, ) -> Weight;
 	fn set_token_properties(b: u32, ) -> Weight;
-	fn init_token_properties(b: u32, ) -> Weight;
+	fn load_token_properties() -> Weight;
+	fn write_token_properties(b: u32, ) -> Weight;
 	fn delete_token_properties(b: u32, ) -> Weight;
 	fn token_owner() -> Weight;
 	fn set_allowance_for_all() -> Weight;
@@ -69,8 +70,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3530`
-		// Minimum execution time: 9_726_000 picoseconds.
-		Weight::from_parts(10_059_000, 3530)
+		// Minimum execution time: 4_990_000 picoseconds.
+		Weight::from_parts(5_170_000, 3530)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -87,10 +88,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3530`
-		// Minimum execution time: 3_270_000 picoseconds.
-		Weight::from_parts(3_693_659, 3530)
-			// Standard Error: 255
-			.saturating_add(Weight::from_parts(3_024_284, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_680_000 picoseconds.
+		Weight::from_parts(1_720_000, 3530)
+			// Standard Error: 674
+			.saturating_add(Weight::from_parts(2_406_591, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 			.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(b.into())))
@@ -108,10 +109,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 3_188_000 picoseconds.
-		Weight::from_parts(3_307_000, 3481)
-			// Standard Error: 567
-			.saturating_add(Weight::from_parts(4_320_449, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_680_000 picoseconds.
+		Weight::from_parts(1_720_000, 3481)
+			// Standard Error: 1_729
+			.saturating_add(Weight::from_parts(3_418_983, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
@@ -136,8 +137,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `3530`
-		// Minimum execution time: 18_062_000 picoseconds.
-		Weight::from_parts(18_433_000, 3530)
+		// Minimum execution time: 10_700_000 picoseconds.
+		Weight::from_parts(11_180_000, 3530)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -159,8 +160,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `3530`
-		// Minimum execution time: 22_942_000 picoseconds.
-		Weight::from_parts(23_527_000, 3530)
+		// Minimum execution time: 13_650_000 picoseconds.
+		Weight::from_parts(13_910_000, 3530)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -185,10 +186,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `1500 + b * (58 ±0)`
 		//  Estimated: `5874 + b * (5032 ±0)`
-		// Minimum execution time: 22_709_000 picoseconds.
-		Weight::from_parts(23_287_000, 5874)
-			// Standard Error: 89_471
-			.saturating_add(Weight::from_parts(63_285_201, 0).saturating_mul(b.into()))
+		// Minimum execution time: 13_500_000 picoseconds.
+		Weight::from_parts(13_830_000, 5874)
+			// Standard Error: 136_447
+			.saturating_add(Weight::from_parts(43_149_279, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(7_u64))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(b.into())))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
@@ -207,8 +208,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `6070`
-		// Minimum execution time: 13_652_000 picoseconds.
-		Weight::from_parts(13_981_000, 6070)
+		// Minimum execution time: 8_440_000 picoseconds.
+		Weight::from_parts(8_680_000, 6070)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -220,8 +221,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `3522`
-		// Minimum execution time: 7_837_000 picoseconds.
-		Weight::from_parts(8_113_000, 3522)
+		// Minimum execution time: 4_580_000 picoseconds.
+		Weight::from_parts(4_850_000, 3522)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -233,8 +234,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `313`
 		//  Estimated: `3522`
-		// Minimum execution time: 7_769_000 picoseconds.
-		Weight::from_parts(7_979_000, 3522)
+		// Minimum execution time: 4_650_000 picoseconds.
+		Weight::from_parts(4_890_000, 3522)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -244,8 +245,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `362`
 		//  Estimated: `3522`
-		// Minimum execution time: 4_194_000 picoseconds.
-		Weight::from_parts(4_353_000, 3522)
+		// Minimum execution time: 2_630_000 picoseconds.
+		Weight::from_parts(2_760_000, 3522)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible Allowance (r:1 w:1)
@@ -266,8 +267,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `463`
 		//  Estimated: `3530`
-		// Minimum execution time: 21_978_000 picoseconds.
-		Weight::from_parts(22_519_000, 3530)
+		// Minimum execution time: 13_300_000 picoseconds.
+		Weight::from_parts(13_650_000, 3530)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
@@ -278,10 +279,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `314`
 		//  Estimated: `20191`
-		// Minimum execution time: 1_457_000 picoseconds.
-		Weight::from_parts(1_563_000, 20191)
-			// Standard Error: 14_041
-			.saturating_add(Weight::from_parts(8_452_415, 0).saturating_mul(b.into()))
+		// Minimum execution time: 550_000 picoseconds.
+		Weight::from_parts(600_000, 20191)
+			// Standard Error: 23_117
+			.saturating_add(Weight::from_parts(6_048_092, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -296,24 +297,34 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `640 + b * (261 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 963_000 picoseconds.
-		Weight::from_parts(1_126_511, 36269)
-			// Standard Error: 9_175
-			.saturating_add(Weight::from_parts(5_096_011, 0).saturating_mul(b.into()))
+		// Minimum execution time: 340_000 picoseconds.
+		Weight::from_parts(7_359_078, 36269)
+			// Standard Error: 9_052
+			.saturating_add(Weight::from_parts(2_763_267, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
+	/// Storage: Nonfungible TokenProperties (r:1 w:0)
+	/// Proof: Nonfungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
+	fn load_token_properties() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `279`
+		//  Estimated: `36269`
+		// Minimum execution time: 1_610_000 picoseconds.
+		Weight::from_parts(1_690_000, 36269)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
 	/// Storage: Nonfungible TokenProperties (r:0 w:1)
 	/// Proof: Nonfungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
 	/// The range of component `b` is `[0, 64]`.
-	fn init_token_properties(b: u32, ) -> Weight {
+	fn write_token_properties(b: u32, ) -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 194_000 picoseconds.
-		Weight::from_parts(222_000, 0)
-			// Standard Error: 7_295
-			.saturating_add(Weight::from_parts(4_499_463, 0).saturating_mul(b.into()))
+		// Minimum execution time: 70_000 picoseconds.
+		Weight::from_parts(3_262_181, 0)
+			// Standard Error: 5_240
+			.saturating_add(Weight::from_parts(2_426_582, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
@@ -327,10 +338,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `699 + b * (33291 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 992_000 picoseconds.
-		Weight::from_parts(1_043_000, 36269)
-			// Standard Error: 37_370
-			.saturating_add(Weight::from_parts(23_672_870, 0).saturating_mul(b.into()))
+		// Minimum execution time: 350_000 picoseconds.
+		Weight::from_parts(370_000, 36269)
+			// Standard Error: 29_081
+			.saturating_add(Weight::from_parts(9_667_268, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -340,8 +351,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `3522`
-		// Minimum execution time: 3_743_000 picoseconds.
-		Weight::from_parts(3_908_000, 3522)
+		// Minimum execution time: 2_380_000 picoseconds.
+		Weight::from_parts(2_500_000, 3522)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible CollectionAllowance (r:0 w:1)
@@ -350,8 +361,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_106_000 picoseconds.
-		Weight::from_parts(4_293_000, 0)
+		// Minimum execution time: 2_060_000 picoseconds.
+		Weight::from_parts(2_150_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: Nonfungible CollectionAllowance (r:1 w:0)
@@ -360,8 +371,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3576`
-		// Minimum execution time: 2_775_000 picoseconds.
-		Weight::from_parts(2_923_000, 3576)
+		// Minimum execution time: 1_630_000 picoseconds.
+		Weight::from_parts(1_730_000, 3576)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible TokenProperties (r:1 w:1)
@@ -370,8 +381,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `279`
 		//  Estimated: `36269`
-		// Minimum execution time: 3_033_000 picoseconds.
-		Weight::from_parts(3_174_000, 36269)
+		// Minimum execution time: 1_700_000 picoseconds.
+		Weight::from_parts(1_780_000, 36269)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -391,8 +402,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3530`
-		// Minimum execution time: 9_726_000 picoseconds.
-		Weight::from_parts(10_059_000, 3530)
+		// Minimum execution time: 4_990_000 picoseconds.
+		Weight::from_parts(5_170_000, 3530)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -409,10 +420,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3530`
-		// Minimum execution time: 3_270_000 picoseconds.
-		Weight::from_parts(3_693_659, 3530)
-			// Standard Error: 255
-			.saturating_add(Weight::from_parts(3_024_284, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_680_000 picoseconds.
+		Weight::from_parts(1_720_000, 3530)
+			// Standard Error: 674
+			.saturating_add(Weight::from_parts(2_406_591, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 			.saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(b.into())))
@@ -430,10 +441,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 3_188_000 picoseconds.
-		Weight::from_parts(3_307_000, 3481)
-			// Standard Error: 567
-			.saturating_add(Weight::from_parts(4_320_449, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_680_000 picoseconds.
+		Weight::from_parts(1_720_000, 3481)
+			// Standard Error: 1_729
+			.saturating_add(Weight::from_parts(3_418_983, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
@@ -458,8 +469,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `3530`
-		// Minimum execution time: 18_062_000 picoseconds.
-		Weight::from_parts(18_433_000, 3530)
+		// Minimum execution time: 10_700_000 picoseconds.
+		Weight::from_parts(11_180_000, 3530)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -481,8 +492,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `3530`
-		// Minimum execution time: 22_942_000 picoseconds.
-		Weight::from_parts(23_527_000, 3530)
+		// Minimum execution time: 13_650_000 picoseconds.
+		Weight::from_parts(13_910_000, 3530)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -507,10 +518,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `1500 + b * (58 ±0)`
 		//  Estimated: `5874 + b * (5032 ±0)`
-		// Minimum execution time: 22_709_000 picoseconds.
-		Weight::from_parts(23_287_000, 5874)
-			// Standard Error: 89_471
-			.saturating_add(Weight::from_parts(63_285_201, 0).saturating_mul(b.into()))
+		// Minimum execution time: 13_500_000 picoseconds.
+		Weight::from_parts(13_830_000, 5874)
+			// Standard Error: 136_447
+			.saturating_add(Weight::from_parts(43_149_279, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(7_u64))
 			.saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(b.into())))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
@@ -529,8 +540,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `380`
 		//  Estimated: `6070`
-		// Minimum execution time: 13_652_000 picoseconds.
-		Weight::from_parts(13_981_000, 6070)
+		// Minimum execution time: 8_440_000 picoseconds.
+		Weight::from_parts(8_680_000, 6070)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -542,8 +553,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `3522`
-		// Minimum execution time: 7_837_000 picoseconds.
-		Weight::from_parts(8_113_000, 3522)
+		// Minimum execution time: 4_580_000 picoseconds.
+		Weight::from_parts(4_850_000, 3522)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -555,8 +566,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `313`
 		//  Estimated: `3522`
-		// Minimum execution time: 7_769_000 picoseconds.
-		Weight::from_parts(7_979_000, 3522)
+		// Minimum execution time: 4_650_000 picoseconds.
+		Weight::from_parts(4_890_000, 3522)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -566,8 +577,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `362`
 		//  Estimated: `3522`
-		// Minimum execution time: 4_194_000 picoseconds.
-		Weight::from_parts(4_353_000, 3522)
+		// Minimum execution time: 2_630_000 picoseconds.
+		Weight::from_parts(2_760_000, 3522)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible Allowance (r:1 w:1)
@@ -588,8 +599,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `463`
 		//  Estimated: `3530`
-		// Minimum execution time: 21_978_000 picoseconds.
-		Weight::from_parts(22_519_000, 3530)
+		// Minimum execution time: 13_300_000 picoseconds.
+		Weight::from_parts(13_650_000, 3530)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
@@ -600,10 +611,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `314`
 		//  Estimated: `20191`
-		// Minimum execution time: 1_457_000 picoseconds.
-		Weight::from_parts(1_563_000, 20191)
-			// Standard Error: 14_041
-			.saturating_add(Weight::from_parts(8_452_415, 0).saturating_mul(b.into()))
+		// Minimum execution time: 550_000 picoseconds.
+		Weight::from_parts(600_000, 20191)
+			// Standard Error: 23_117
+			.saturating_add(Weight::from_parts(6_048_092, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -618,24 +629,34 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `640 + b * (261 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 963_000 picoseconds.
-		Weight::from_parts(1_126_511, 36269)
-			// Standard Error: 9_175
-			.saturating_add(Weight::from_parts(5_096_011, 0).saturating_mul(b.into()))
+		// Minimum execution time: 340_000 picoseconds.
+		Weight::from_parts(7_359_078, 36269)
+			// Standard Error: 9_052
+			.saturating_add(Weight::from_parts(2_763_267, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
+	/// Storage: Nonfungible TokenProperties (r:1 w:0)
+	/// Proof: Nonfungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
+	fn load_token_properties() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `279`
+		//  Estimated: `36269`
+		// Minimum execution time: 1_610_000 picoseconds.
+		Weight::from_parts(1_690_000, 36269)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
 	/// Storage: Nonfungible TokenProperties (r:0 w:1)
 	/// Proof: Nonfungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
 	/// The range of component `b` is `[0, 64]`.
-	fn init_token_properties(b: u32, ) -> Weight {
+	fn write_token_properties(b: u32, ) -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 194_000 picoseconds.
-		Weight::from_parts(222_000, 0)
-			// Standard Error: 7_295
-			.saturating_add(Weight::from_parts(4_499_463, 0).saturating_mul(b.into()))
+		// Minimum execution time: 70_000 picoseconds.
+		Weight::from_parts(3_262_181, 0)
+			// Standard Error: 5_240
+			.saturating_add(Weight::from_parts(2_426_582, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
@@ -649,10 +670,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `699 + b * (33291 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 992_000 picoseconds.
-		Weight::from_parts(1_043_000, 36269)
-			// Standard Error: 37_370
-			.saturating_add(Weight::from_parts(23_672_870, 0).saturating_mul(b.into()))
+		// Minimum execution time: 350_000 picoseconds.
+		Weight::from_parts(370_000, 36269)
+			// Standard Error: 29_081
+			.saturating_add(Weight::from_parts(9_667_268, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -662,8 +683,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `326`
 		//  Estimated: `3522`
-		// Minimum execution time: 3_743_000 picoseconds.
-		Weight::from_parts(3_908_000, 3522)
+		// Minimum execution time: 2_380_000 picoseconds.
+		Weight::from_parts(2_500_000, 3522)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible CollectionAllowance (r:0 w:1)
@@ -672,8 +693,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_106_000 picoseconds.
-		Weight::from_parts(4_293_000, 0)
+		// Minimum execution time: 2_060_000 picoseconds.
+		Weight::from_parts(2_150_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: Nonfungible CollectionAllowance (r:1 w:0)
@@ -682,8 +703,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `142`
 		//  Estimated: `3576`
-		// Minimum execution time: 2_775_000 picoseconds.
-		Weight::from_parts(2_923_000, 3576)
+		// Minimum execution time: 1_630_000 picoseconds.
+		Weight::from_parts(1_730_000, 3576)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
 	/// Storage: Nonfungible TokenProperties (r:1 w:1)
@@ -692,8 +713,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `279`
 		//  Estimated: `36269`
-		// Minimum execution time: 3_033_000 picoseconds.
-		Weight::from_parts(3_174_000, 36269)
+		// Minimum execution time: 1_700_000 picoseconds.
+		Weight::from_parts(1_780_000, 36269)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -19,10 +19,7 @@
 use frame_benchmarking::v2::*;
 use pallet_common::{
 	bench_init,
-	benchmarking::{
-		create_collection_raw, /*load_is_admin_and_property_permissions,*/ property_key,
-		property_value,
-	},
+	benchmarking::{create_collection_raw, property_key, property_value},
 };
 use sp_std::prelude::*;
 use up_data_structs::{
@@ -424,6 +421,81 @@
 		Ok(())
 	}
 
+	// set_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 {
+	// 			mutable: false,
+	// 			collection_admin: true,
+	// 			token_owner: true,
+	// 		},
+	// 	}).collect::<Vec<_>>();
+	// 	<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+	// 	let props = (0..b).map(|k| Property {
+	// 		key: property_key(k as usize),
+	// 		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(), &Unlimited)?}
+
+	// load_token_properties {
+	// 	bench_init!{
+	// 		owner: sub; collection: collection(owner);
+	// 		owner: cross_from_sub;
+	// 	};
+
+	// 	let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+	// }: {
+	// 	pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(
+	// 		&collection,
+	// 		item,
+	// 	)
+	// }
+
+	// write_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 {
+	// 			mutable: false,
+	// 			collection_admin: true,
+	// 			token_owner: true,
+	// 		},
+	// 	}).collect::<Vec<_>>();
+	// 	<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+	// 	let props = (0..b).map(|k| Property {
+	// 		key: property_key(k as usize),
+	// 		value: property_value(),
+	// 	}).collect::<Vec<_>>();
+	// 	let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+
+	// 	let lazy_collection_info = pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(
+	// 		&collection,
+	// 		&owner,
+	// 	);
+	// }: {
+	// 	let mut property_writer = pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+	// 	property_writer.write_token_properties(
+	// 		item,
+	// 		props.into_iter(),
+	// 		crate::erc::ERC721TokenEvent::TokenChanged {
+	// 			token_id: item.into(),
+	// 		}
+	// 		.to_log(T::ContractAddress::get()),
+	// 	)?
+	// }
+
 	#[benchmark]
 	fn set_token_property_permissions(
 		b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -16,14 +16,12 @@
 
 use core::marker::PhantomData;
 
-use frame_support::{
-	dispatch::DispatchResultWithPostInfo, ensure, fail, traits::Get, weights::Weight,
-};
+use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};
 use pallet_common::{
-	init_token_properties_delta, weights::WeightInfo as _, with_weight, CommonCollectionOperations,
-	CommonWeightInfo, RefungibleExtensions,
+	weights::WeightInfo as _, with_weight, write_token_properties_total_weight,
+	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions,
 };
-use pallet_structure::{Error as StructureError, Pallet as PalletStructure};
+use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::DispatchError;
 use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
 use up_data_structs::{
@@ -50,14 +48,14 @@
 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(
-			init_token_properties_delta::<T, _>(
+			write_token_properties_total_weight::<T, _>(
 				data.iter().map(|data| match data {
 					up_data_structs::CreateItemData::ReFungible(rft_data) => {
 						rft_data.properties.len() as u32
 					}
 					_ => 0,
 				}),
-				<SelfWeightOf<T>>::init_token_properties,
+				<SelfWeightOf<T>>::write_token_properties,
 			),
 		)
 	}
@@ -66,16 +64,16 @@
 		match call {
 			CreateItemExData::RefungibleMultipleOwners(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
-					.saturating_add(init_token_properties_delta::<T, _>(
+					.saturating_add(write_token_properties_total_weight::<T, _>(
 						[i.properties.len() as u32].into_iter(),
-						<SelfWeightOf<T>>::init_token_properties,
+						<SelfWeightOf<T>>::write_token_properties,
 					))
 			}
 			CreateItemExData::RefungibleMultipleItems(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
-					.saturating_add(init_token_properties_delta::<T, _>(
+					.saturating_add(write_token_properties_total_weight::<T, _>(
 						i.iter().map(|d| d.properties.len() as u32),
-						<SelfWeightOf<T>>::init_token_properties,
+						<SelfWeightOf<T>>::write_token_properties,
 					))
 			}
 			_ => Weight::zero(),
@@ -88,18 +86,13 @@
 
 	fn set_collection_properties(amount: u32) -> Weight {
 		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
-	}
-
-	fn delete_collection_properties(amount: u32) -> Weight {
-		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
 	}
 
 	fn set_token_properties(amount: u32) -> Weight {
-		<SelfWeightOf<T>>::set_token_properties(amount)
-	}
-
-	fn delete_token_properties(amount: u32) -> Weight {
-		<SelfWeightOf<T>>::delete_token_properties(amount)
+		write_token_properties_total_weight::<T, _>([amount].into_iter(), |amount| {
+			<SelfWeightOf<T>>::load_token_properties()
+				+ <SelfWeightOf<T>>::write_token_properties(amount)
+		})
 	}
 
 	fn set_token_property_permissions(amount: u32) -> Weight {
@@ -136,19 +129,6 @@
 		<SelfWeightOf<T>>::burn_from()
 	}
 
-	fn burn_recursively_self_raw() -> Weight {
-		// Read to get total balance
-		Self::burn_item() + T::DbWeight::get().reads(1)
-	}
-	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
-		// Refungible token can't have children
-		Weight::zero()
-	}
-
-	fn token_owner() -> Weight {
-		<SelfWeightOf<T>>::token_owner()
-	}
-
 	fn set_allowance_for_all() -> Weight {
 		<SelfWeightOf<T>>::set_allowance_for_all()
 	}
@@ -262,25 +242,6 @@
 		with_weight(
 			<Pallet<T>>::burn(self, &sender, token, amount),
 			<CommonWeights<T>>::burn_item(),
-		)
-	}
-
-	fn burn_item_recursively(
-		&self,
-		sender: T::CrossAccountId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		_breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo {
-		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
-		with_weight(
-			<Pallet<T>>::burn(
-				self,
-				&sender,
-				token,
-				<Balance<T>>::get((self.id, token, &sender)),
-			),
-			<CommonWeights<T>>::burn_recursively_self_raw(),
 		)
 	}
 
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -32,26 +32,26 @@
 use pallet_common::{
 	erc::{static_property::key, CollectionCall, CommonEvmHandler},
 	eth::{self, TokenUri},
-	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
+	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,
 	Error as CommonError,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{
 	call, dispatch_to_evm,
 	execution::{Error, PreDispatch, Result},
-	frontier_contract,
+	frontier_contract, SubstrateRecorder,
 };
 use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};
 use sp_core::{Get, H160, U256};
 use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
 use up_data_structs::{
-	mapping::TokenAddressMapping, CollectionId, CollectionPropertiesVec, Property, PropertyKey,
-	PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,
+	budget::Budget, mapping::TokenAddressMapping, CollectionId, CollectionPropertiesVec, Property,
+	PropertyKey, PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,
 };
 
 use crate::{
-	weights::WeightInfo, AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle,
-	SelfWeightOf, TokenProperties, TokensMinted, TotalSupply,
+	common::CommonWeights, weights::WeightInfo, AccountBalance, Balance, Config, CreateItemData,
+	Pallet, RefungibleHandle, SelfWeightOf, TokenProperties, TokensMinted, TotalSupply,
 };
 
 frontier_contract! {
@@ -90,6 +90,10 @@
 	pub properties: Vec<eth::Property>,
 }
 
+pub fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {
+	recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())
+}
+
 /// @title A contract that allows to set and delete token properties and change token property permissions.
 #[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]
 impl<T: Config> RefungibleHandle<T> {
@@ -158,7 +162,7 @@
 	/// @param key Property key.
 	/// @param value Property value.
 	#[solidity(hide)]
-	#[weight(<SelfWeightOf<T>>::set_token_properties(1))]
+	#[weight(<CommonWeights<T>>::set_token_properties(1))]
 	fn set_property(
 		&mut self,
 		caller: Caller,
@@ -173,16 +177,12 @@
 			.map_err(|_| "key too long")?;
 		let value = value.0.try_into().map_err(|_| "value too long")?;
 
-		let nesting_budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-
 		<Pallet<T>>::set_token_property(
 			self,
 			&caller,
 			TokenId(token_id),
 			Property { key, value },
-			&nesting_budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)
 	}
@@ -191,7 +191,7 @@
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the token.
 	/// @param properties settable properties
-	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]
+	#[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]
 	fn set_properties(
 		&mut self,
 		caller: Caller,
@@ -201,10 +201,6 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
 
-		let nesting_budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-
 		let properties = properties
 			.into_iter()
 			.map(eth::Property::try_into)
@@ -215,7 +211,7 @@
 			&caller,
 			TokenId(token_id),
 			properties.into_iter(),
-			&nesting_budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)
 	}
@@ -225,7 +221,7 @@
 	/// @param tokenId ID of the token.
 	/// @param key Property key.
 	#[solidity(hide)]
-	#[weight(<SelfWeightOf<T>>::delete_token_properties(1))]
+	#[weight(<CommonWeights<T>>::delete_token_properties(1))]
 	fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
@@ -233,19 +229,21 @@
 			.try_into()
 			.map_err(|_| "key too long")?;
 
-		let nesting_budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-
-		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)
-			.map_err(dispatch_to_evm::<T>)
+		<Pallet<T>>::delete_token_property(
+			self,
+			&caller,
+			TokenId(token_id),
+			key,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)
 	}
 
 	/// @notice Delete token properties value.
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the token.
 	/// @param keys Properties key.
-	#[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]
+	#[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]
 	fn delete_properties(
 		&mut self,
 		token_id: U256,
@@ -258,17 +256,13 @@
 			.into_iter()
 			.map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))
 			.collect::<Result<Vec<_>>>()?;
-
-		let nesting_budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		<Pallet<T>>::delete_token_properties(
 			self,
 			&caller,
 			TokenId(token_id),
 			keys.into_iter(),
-			&nesting_budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)
 	}
@@ -497,15 +491,20 @@
 		let from = T::CrossAccountId::from_eth(from);
 		let to = T::CrossAccountId::from_eth(to);
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token, &from)?;
 		ensure_single_owner(self, token, balance)?;
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			token,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 
 		Ok(())
 	}
@@ -629,9 +628,6 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let token_id: u32 = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		if <TokensMinted<T>>::get(self.id)
 			.checked_add(1)
@@ -653,7 +649,7 @@
 				users,
 				properties: CollectionPropertiesVec::default(),
 			},
-			&budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)?;
 
@@ -704,9 +700,6 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		if <TokensMinted<T>>::get(self.id)
 			.checked_add(1)
@@ -736,7 +729,7 @@
 			self,
 			&caller,
 			CreateItemData::<T> { users, properties },
-			&budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
@@ -865,15 +858,19 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token, &caller)?;
 		ensure_single_owner(self, token, balance)?;
 
-		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer(
+			self,
+			&caller,
+			&to,
+			token,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
@@ -893,15 +890,19 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token, &caller)?;
 		ensure_single_owner(self, token, balance)?;
 
-		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer(
+			self,
+			&caller,
+			&to,
+			token,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
@@ -923,15 +924,20 @@
 		let from = from.into_sub_cross_account::<T>()?;
 		let to = to.into_sub_cross_account::<T>()?;
 		let token_id = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token_id, &from)?;
 		ensure_single_owner(self, token_id, balance)?;
 
-		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		Pallet::<T>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			token_id,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
@@ -948,15 +954,19 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = T::CrossAccountId::from_eth(from);
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token, &from)?;
 		ensure_single_owner(self, token, balance)?;
 
-		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			token,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
@@ -977,15 +987,19 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = from.into_sub_cross_account::<T>()?;
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let balance = balance(self, token, &from)?;
 		ensure_single_owner(self, token, balance)?;
 
-		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			token,
+			balance,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
@@ -1010,9 +1024,6 @@
 		let mut expected_index = <TokensMinted<T>>::get(self.id)
 			.checked_add(1)
 			.ok_or("item id overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let total_tokens = token_ids.len();
 		for id in token_ids.into_iter() {
@@ -1035,7 +1046,7 @@
 			.map(|_| create_item_data.clone())
 			.collect();
 
-		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
+		<Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
@@ -1053,9 +1064,6 @@
 		token_properties: Vec<MintTokenData>,
 	) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 		let has_multiple_tokens = token_properties.len() > 1;
 
 		let mut create_rft_data = Vec::with_capacity(token_properties.len());
@@ -1084,8 +1092,13 @@
 			});
 		}
 
-		<Pallet<T>>::create_multiple_items(self, &caller, create_rft_data, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::create_multiple_items(
+			self,
+			&caller,
+			create_rft_data,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -1108,9 +1121,6 @@
 		let mut expected_index = <TokensMinted<T>>::get(self.id)
 			.checked_add(1)
 			.ok_or("item id overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let mut data = Vec::with_capacity(tokens.len());
 		let users: BoundedBTreeMap<_, _, _> = [(to, 1)]
@@ -1143,7 +1153,7 @@
 			data.push(create_item_data);
 		}
 
-		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
+		<Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
@@ -1174,10 +1184,6 @@
 
 		let caller = T::CrossAccountId::from_eth(caller);
 
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
-
 		let users = [(to, 1)]
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
@@ -1187,7 +1193,7 @@
 			self,
 			&caller,
 			CreateItemData::<T> { users, properties },
-			&budget,
+			&nesting_budget(&self.recorder),
 		)
 		.map_err(dispatch_to_evm::<T>)?;
 
modifiedpallets/refungible/src/erc_token.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -37,14 +37,13 @@
 	execution::{PreDispatch, Result},
 	frontier_contract, WithRecorder,
 };
-use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};
 use sp_core::U256;
 use sp_std::vec::Vec;
 use up_data_structs::TokenId;
 
 use crate::{
-	common::CommonWeights, weights::WeightInfo, Allowance, Balance, Config, Pallet,
-	RefungibleHandle, SelfWeightOf, TotalSupply,
+	common::CommonWeights, erc::nesting_budget, weights::WeightInfo, Allowance, Balance, Config,
+	Pallet, RefungibleHandle, SelfWeightOf, TotalSupply,
 };
 
 /// Refungible token handle contains information about token's collection and id
@@ -140,12 +139,16 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer(
+			self,
+			&caller,
+			&to,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -165,12 +168,17 @@
 		let from = T::CrossAccountId::from_eth(from);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -231,12 +239,16 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = T::CrossAccountId::from_eth(from);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -254,12 +266,16 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let from = from.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::burn_from(
+			self,
+			&caller,
+			&from,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -315,12 +331,16 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer(
+			self,
+			&caller,
+			&to,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -340,12 +360,17 @@
 		let from = from.into_sub_cross_account::<T>()?;
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::transfer_from(
+			self,
+			&caller,
+			&from,
+			&to,
+			self.1,
+			amount,
+			&nesting_budget(&self.recorder),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 }
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -507,7 +507,7 @@
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		let mut property_writer =
-			pallet_common::property_writer_for_existing_token(collection, sender);
+			pallet_common::ExistingTokenPropertyWriter::new(collection, sender);
 
 		property_writer.write_token_properties(
 			sender,
@@ -858,7 +858,7 @@
 
 		// =========
 
-		let mut property_writer = pallet_common::property_writer_for_new_token(collection, sender);
+		let mut property_writer = pallet_common::NewTokenPropertyWriter::new(collection, sender);
 
 		with_transaction(|| {
 			for (i, data) in data.iter().enumerate() {
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,9 +3,9 @@
 //! Autogenerated weights for pallet_refungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-09-30, STEPS: `50`, REPEAT: `400`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-10-05, STEPS: `50`, REPEAT: `80`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `bench-host`, CPU: `Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz`
+//! HOSTNAME: `hearthstone`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -20,7 +20,7 @@
 // *
 // --template=.maintain/frame-weight-template.hbs
 // --steps=50
-// --repeat=400
+// --repeat=80
 // --heap-pages=4096
 // --output=./pallets/refungible/src/weights.rs
 
@@ -52,7 +52,8 @@
 	fn burn_from() -> Weight;
 	fn set_token_property_permissions(b: u32, ) -> Weight;
 	fn set_token_properties(b: u32, ) -> Weight;
-	fn init_token_properties(b: u32, ) -> Weight;
+	fn load_token_properties() -> Weight;
+	fn write_token_properties(b: u32, ) -> Weight;
 	fn delete_token_properties(b: u32, ) -> Weight;
 	fn repartition_item() -> Weight;
 	fn token_owner() -> Weight;
@@ -78,8 +79,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3530`
-		// Minimum execution time: 11_341_000 picoseconds.
-		Weight::from_parts(11_741_000, 3530)
+		// Minimum execution time: 5_710_000 picoseconds.
+		Weight::from_parts(5_980_000, 3530)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -98,10 +99,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3530`
-		// Minimum execution time: 2_665_000 picoseconds.
-		Weight::from_parts(2_791_000, 3530)
-			// Standard Error: 996
-			.saturating_add(Weight::from_parts(4_343_736, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_300_000 picoseconds.
+		Weight::from_parts(1_360_000, 3530)
+			// Standard Error: 2_783
+			.saturating_add(Weight::from_parts(3_456_531, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b.into())))
@@ -121,10 +122,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 2_616_000 picoseconds.
-		Weight::from_parts(2_726_000, 3481)
-			// Standard Error: 665
-			.saturating_add(Weight::from_parts(5_554_066, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_290_000 picoseconds.
+		Weight::from_parts(1_370_000, 3481)
+			// Standard Error: 3_198
+			.saturating_add(Weight::from_parts(4_435_305, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
@@ -146,10 +147,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 3_697_000 picoseconds.
-		Weight::from_parts(2_136_481, 3481)
-			// Standard Error: 567
-			.saturating_add(Weight::from_parts(4_390_621, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_730_000 picoseconds.
+		Weight::from_parts(1_810_000, 3481)
+			// Standard Error: 1_923
+			.saturating_add(Weight::from_parts(3_500_817, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
@@ -168,8 +169,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `456`
 		//  Estimated: `8682`
-		// Minimum execution time: 22_859_000 picoseconds.
-		Weight::from_parts(23_295_000, 8682)
+		// Minimum execution time: 14_010_000 picoseconds.
+		Weight::from_parts(16_300_000, 8682)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -189,8 +190,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `3554`
-		// Minimum execution time: 21_477_000 picoseconds.
-		Weight::from_parts(22_037_000, 3554)
+		// Minimum execution time: 13_700_000 picoseconds.
+		Weight::from_parts(14_180_000, 3554)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
@@ -202,8 +203,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `365`
 		//  Estimated: `6118`
-		// Minimum execution time: 13_714_000 picoseconds.
-		Weight::from_parts(14_050_000, 6118)
+		// Minimum execution time: 8_990_000 picoseconds.
+		Weight::from_parts(9_400_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -219,8 +220,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `6118`
-		// Minimum execution time: 15_879_000 picoseconds.
-		Weight::from_parts(16_266_000, 6118)
+		// Minimum execution time: 10_240_000 picoseconds.
+		Weight::from_parts(10_610_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -236,8 +237,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `456`
 		//  Estimated: `6118`
-		// Minimum execution time: 18_186_000 picoseconds.
-		Weight::from_parts(18_682_000, 6118)
+		// Minimum execution time: 12_040_000 picoseconds.
+		Weight::from_parts(12_390_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -253,8 +254,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `6118`
-		// Minimum execution time: 17_943_000 picoseconds.
-		Weight::from_parts(18_333_000, 6118)
+		// Minimum execution time: 11_940_000 picoseconds.
+		Weight::from_parts(12_240_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
@@ -266,8 +267,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `223`
 		//  Estimated: `3554`
-		// Minimum execution time: 8_391_000 picoseconds.
-		Weight::from_parts(8_637_000, 3554)
+		// Minimum execution time: 5_150_000 picoseconds.
+		Weight::from_parts(5_440_000, 3554)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -279,8 +280,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `211`
 		//  Estimated: `3554`
-		// Minimum execution time: 8_519_000 picoseconds.
-		Weight::from_parts(8_760_000, 3554)
+		// Minimum execution time: 5_170_000 picoseconds.
+		Weight::from_parts(5_400_000, 3554)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -294,8 +295,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `495`
 		//  Estimated: `6118`
-		// Minimum execution time: 19_554_000 picoseconds.
-		Weight::from_parts(20_031_000, 6118)
+		// Minimum execution time: 13_150_000 picoseconds.
+		Weight::from_parts(13_600_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(3_u64))
 	}
@@ -313,8 +314,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `6118`
-		// Minimum execution time: 21_338_000 picoseconds.
-		Weight::from_parts(21_803_000, 6118)
+		// Minimum execution time: 14_280_000 picoseconds.
+		Weight::from_parts(14_680_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -332,8 +333,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `586`
 		//  Estimated: `6118`
-		// Minimum execution time: 24_179_000 picoseconds.
-		Weight::from_parts(24_647_000, 6118)
+		// Minimum execution time: 16_110_000 picoseconds.
+		Weight::from_parts(16_710_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -351,8 +352,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `6118`
-		// Minimum execution time: 24_008_000 picoseconds.
-		Weight::from_parts(24_545_000, 6118)
+		// Minimum execution time: 16_130_000 picoseconds.
+		Weight::from_parts(16_680_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(6_u64))
 			.saturating_add(T::DbWeight::get().writes(7_u64))
 	}
@@ -374,8 +375,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `3570`
-		// Minimum execution time: 27_907_000 picoseconds.
-		Weight::from_parts(28_489_000, 3570)
+		// Minimum execution time: 18_380_000 picoseconds.
+		Weight::from_parts(18_870_000, 3570)
 			.saturating_add(T::DbWeight::get().reads(5_u64))
 			.saturating_add(T::DbWeight::get().writes(7_u64))
 	}
@@ -386,10 +387,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `314`
 		//  Estimated: `20191`
-		// Minimum execution time: 1_460_000 picoseconds.
-		Weight::from_parts(1_564_000, 20191)
-			// Standard Error: 14_117
-			.saturating_add(Weight::from_parts(8_196_214, 0).saturating_mul(b.into()))
+		// Minimum execution time: 580_000 picoseconds.
+		Weight::from_parts(660_000, 20191)
+			// Standard Error: 29_964
+			.saturating_add(Weight::from_parts(6_251_766, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -404,24 +405,34 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `502 + b * (261 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 1_012_000 picoseconds.
-		Weight::from_parts(1_081_000, 36269)
-			// Standard Error: 6_838
-			.saturating_add(Weight::from_parts(5_801_181, 0).saturating_mul(b.into()))
+		// Minimum execution time: 350_000 picoseconds.
+		Weight::from_parts(2_269_806, 36269)
+			// Standard Error: 7_751
+			.saturating_add(Weight::from_parts(3_068_126, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
+	/// Storage: Refungible TokenProperties (r:1 w:0)
+	/// Proof: Refungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
+	fn load_token_properties() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `120`
+		//  Estimated: `36269`
+		// Minimum execution time: 1_010_000 picoseconds.
+		Weight::from_parts(1_080_000, 36269)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
 	/// Storage: Refungible TokenProperties (r:0 w:1)
 	/// Proof: Refungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
 	/// The range of component `b` is `[0, 64]`.
-	fn init_token_properties(b: u32, ) -> Weight {
+	fn write_token_properties(b: u32, ) -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 229_000 picoseconds.
-		Weight::from_parts(253_000, 0)
-			// Standard Error: 100_218
-			.saturating_add(Weight::from_parts(12_632_221, 0).saturating_mul(b.into()))
+		// Minimum execution time: 70_000 picoseconds.
+		Weight::from_parts(1_363_449, 0)
+			// Standard Error: 8_964
+			.saturating_add(Weight::from_parts(2_665_759, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
@@ -435,10 +446,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `561 + b * (33291 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 1_014_000 picoseconds.
-		Weight::from_parts(1_065_000, 36269)
-			// Standard Error: 39_536
-			.saturating_add(Weight::from_parts(24_125_838, 0).saturating_mul(b.into()))
+		// Minimum execution time: 320_000 picoseconds.
+		Weight::from_parts(370_000, 36269)
+			// Standard Error: 28_541
+			.saturating_add(Weight::from_parts(9_863_065, 0).saturating_mul(b.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -450,8 +461,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `288`
 		//  Estimated: `3554`
-		// Minimum execution time: 10_315_000 picoseconds.
-		Weight::from_parts(10_601_000, 3554)
+		// Minimum execution time: 6_320_000 picoseconds.
+		Weight::from_parts(6_640_000, 3554)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -461,8 +472,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `288`
 		//  Estimated: `6118`
-		// Minimum execution time: 4_898_000 picoseconds.
-		Weight::from_parts(5_136_000, 6118)
+		// Minimum execution time: 2_520_000 picoseconds.
+		Weight::from_parts(2_680_000, 6118)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 	}
 	/// Storage: Refungible CollectionAllowance (r:0 w:1)
@@ -471,8 +482,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_146_000 picoseconds.
-		Weight::from_parts(4_337_000, 0)
+		// Minimum execution time: 2_070_000 picoseconds.
+		Weight::from_parts(2_230_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: Refungible CollectionAllowance (r:1 w:0)
@@ -481,8 +492,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3576`
-		// Minimum execution time: 2_170_000 picoseconds.
-		Weight::from_parts(2_301_000, 3576)
+		// Minimum execution time: 1_270_000 picoseconds.
+		Weight::from_parts(1_420_000, 3576)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
 	/// Storage: Refungible TokenProperties (r:1 w:1)
@@ -491,8 +502,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `120`
 		//  Estimated: `36269`
-		// Minimum execution time: 2_098_000 picoseconds.
-		Weight::from_parts(2_251_000, 36269)
+		// Minimum execution time: 1_010_000 picoseconds.
+		Weight::from_parts(1_160_000, 36269)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -514,8 +525,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3530`
-		// Minimum execution time: 11_341_000 picoseconds.
-		Weight::from_parts(11_741_000, 3530)
+		// Minimum execution time: 5_710_000 picoseconds.
+		Weight::from_parts(5_980_000, 3530)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -534,10 +545,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3530`
-		// Minimum execution time: 2_665_000 picoseconds.
-		Weight::from_parts(2_791_000, 3530)
-			// Standard Error: 996
-			.saturating_add(Weight::from_parts(4_343_736, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_300_000 picoseconds.
+		Weight::from_parts(1_360_000, 3530)
+			// Standard Error: 2_783
+			.saturating_add(Weight::from_parts(3_456_531, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 			.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(b.into())))
@@ -557,10 +568,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 2_616_000 picoseconds.
-		Weight::from_parts(2_726_000, 3481)
-			// Standard Error: 665
-			.saturating_add(Weight::from_parts(5_554_066, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_290_000 picoseconds.
+		Weight::from_parts(1_370_000, 3481)
+			// Standard Error: 3_198
+			.saturating_add(Weight::from_parts(4_435_305, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
@@ -582,10 +593,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3481 + b * (2540 ±0)`
-		// Minimum execution time: 3_697_000 picoseconds.
-		Weight::from_parts(2_136_481, 3481)
-			// Standard Error: 567
-			.saturating_add(Weight::from_parts(4_390_621, 0).saturating_mul(b.into()))
+		// Minimum execution time: 1_730_000 picoseconds.
+		Weight::from_parts(1_810_000, 3481)
+			// Standard Error: 1_923
+			.saturating_add(Weight::from_parts(3_500_817, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(b.into())))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
@@ -604,8 +615,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `456`
 		//  Estimated: `8682`
-		// Minimum execution time: 22_859_000 picoseconds.
-		Weight::from_parts(23_295_000, 8682)
+		// Minimum execution time: 14_010_000 picoseconds.
+		Weight::from_parts(16_300_000, 8682)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -625,8 +636,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `3554`
-		// Minimum execution time: 21_477_000 picoseconds.
-		Weight::from_parts(22_037_000, 3554)
+		// Minimum execution time: 13_700_000 picoseconds.
+		Weight::from_parts(14_180_000, 3554)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
@@ -638,8 +649,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `365`
 		//  Estimated: `6118`
-		// Minimum execution time: 13_714_000 picoseconds.
-		Weight::from_parts(14_050_000, 6118)
+		// Minimum execution time: 8_990_000 picoseconds.
+		Weight::from_parts(9_400_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -655,8 +666,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `6118`
-		// Minimum execution time: 15_879_000 picoseconds.
-		Weight::from_parts(16_266_000, 6118)
+		// Minimum execution time: 10_240_000 picoseconds.
+		Weight::from_parts(10_610_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -672,8 +683,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `456`
 		//  Estimated: `6118`
-		// Minimum execution time: 18_186_000 picoseconds.
-		Weight::from_parts(18_682_000, 6118)
+		// Minimum execution time: 12_040_000 picoseconds.
+		Weight::from_parts(12_390_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -689,8 +700,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `341`
 		//  Estimated: `6118`
-		// Minimum execution time: 17_943_000 picoseconds.
-		Weight::from_parts(18_333_000, 6118)
+		// Minimum execution time: 11_940_000 picoseconds.
+		Weight::from_parts(12_240_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
@@ -702,8 +713,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `223`
 		//  Estimated: `3554`
-		// Minimum execution time: 8_391_000 picoseconds.
-		Weight::from_parts(8_637_000, 3554)
+		// Minimum execution time: 5_150_000 picoseconds.
+		Weight::from_parts(5_440_000, 3554)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -715,8 +726,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `211`
 		//  Estimated: `3554`
-		// Minimum execution time: 8_519_000 picoseconds.
-		Weight::from_parts(8_760_000, 3554)
+		// Minimum execution time: 5_170_000 picoseconds.
+		Weight::from_parts(5_400_000, 3554)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -730,8 +741,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `495`
 		//  Estimated: `6118`
-		// Minimum execution time: 19_554_000 picoseconds.
-		Weight::from_parts(20_031_000, 6118)
+		// Minimum execution time: 13_150_000 picoseconds.
+		Weight::from_parts(13_600_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(3_u64))
 	}
@@ -749,8 +760,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `6118`
-		// Minimum execution time: 21_338_000 picoseconds.
-		Weight::from_parts(21_803_000, 6118)
+		// Minimum execution time: 14_280_000 picoseconds.
+		Weight::from_parts(14_680_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -768,8 +779,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `586`
 		//  Estimated: `6118`
-		// Minimum execution time: 24_179_000 picoseconds.
-		Weight::from_parts(24_647_000, 6118)
+		// Minimum execution time: 16_110_000 picoseconds.
+		Weight::from_parts(16_710_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -787,8 +798,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `6118`
-		// Minimum execution time: 24_008_000 picoseconds.
-		Weight::from_parts(24_545_000, 6118)
+		// Minimum execution time: 16_130_000 picoseconds.
+		Weight::from_parts(16_680_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(6_u64))
 			.saturating_add(RocksDbWeight::get().writes(7_u64))
 	}
@@ -810,8 +821,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `471`
 		//  Estimated: `3570`
-		// Minimum execution time: 27_907_000 picoseconds.
-		Weight::from_parts(28_489_000, 3570)
+		// Minimum execution time: 18_380_000 picoseconds.
+		Weight::from_parts(18_870_000, 3570)
 			.saturating_add(RocksDbWeight::get().reads(5_u64))
 			.saturating_add(RocksDbWeight::get().writes(7_u64))
 	}
@@ -822,10 +833,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `314`
 		//  Estimated: `20191`
-		// Minimum execution time: 1_460_000 picoseconds.
-		Weight::from_parts(1_564_000, 20191)
-			// Standard Error: 14_117
-			.saturating_add(Weight::from_parts(8_196_214, 0).saturating_mul(b.into()))
+		// Minimum execution time: 580_000 picoseconds.
+		Weight::from_parts(660_000, 20191)
+			// Standard Error: 29_964
+			.saturating_add(Weight::from_parts(6_251_766, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -840,24 +851,34 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `502 + b * (261 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 1_012_000 picoseconds.
-		Weight::from_parts(1_081_000, 36269)
-			// Standard Error: 6_838
-			.saturating_add(Weight::from_parts(5_801_181, 0).saturating_mul(b.into()))
+		// Minimum execution time: 350_000 picoseconds.
+		Weight::from_parts(2_269_806, 36269)
+			// Standard Error: 7_751
+			.saturating_add(Weight::from_parts(3_068_126, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
+	/// Storage: Refungible TokenProperties (r:1 w:0)
+	/// Proof: Refungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
+	fn load_token_properties() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `120`
+		//  Estimated: `36269`
+		// Minimum execution time: 1_010_000 picoseconds.
+		Weight::from_parts(1_080_000, 36269)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
 	/// Storage: Refungible TokenProperties (r:0 w:1)
 	/// Proof: Refungible TokenProperties (max_values: None, max_size: Some(32804), added: 35279, mode: MaxEncodedLen)
 	/// The range of component `b` is `[0, 64]`.
-	fn init_token_properties(b: u32, ) -> Weight {
+	fn write_token_properties(b: u32, ) -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 229_000 picoseconds.
-		Weight::from_parts(253_000, 0)
-			// Standard Error: 100_218
-			.saturating_add(Weight::from_parts(12_632_221, 0).saturating_mul(b.into()))
+		// Minimum execution time: 70_000 picoseconds.
+		Weight::from_parts(1_363_449, 0)
+			// Standard Error: 8_964
+			.saturating_add(Weight::from_parts(2_665_759, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: Common CollectionPropertyPermissions (r:1 w:0)
@@ -871,10 +892,10 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `561 + b * (33291 ±0)`
 		//  Estimated: `36269`
-		// Minimum execution time: 1_014_000 picoseconds.
-		Weight::from_parts(1_065_000, 36269)
-			// Standard Error: 39_536
-			.saturating_add(Weight::from_parts(24_125_838, 0).saturating_mul(b.into()))
+		// Minimum execution time: 320_000 picoseconds.
+		Weight::from_parts(370_000, 36269)
+			// Standard Error: 28_541
+			.saturating_add(Weight::from_parts(9_863_065, 0).saturating_mul(b.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -886,8 +907,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `288`
 		//  Estimated: `3554`
-		// Minimum execution time: 10_315_000 picoseconds.
-		Weight::from_parts(10_601_000, 3554)
+		// Minimum execution time: 6_320_000 picoseconds.
+		Weight::from_parts(6_640_000, 3554)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -897,8 +918,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `288`
 		//  Estimated: `6118`
-		// Minimum execution time: 4_898_000 picoseconds.
-		Weight::from_parts(5_136_000, 6118)
+		// Minimum execution time: 2_520_000 picoseconds.
+		Weight::from_parts(2_680_000, 6118)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 	}
 	/// Storage: Refungible CollectionAllowance (r:0 w:1)
@@ -907,8 +928,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_146_000 picoseconds.
-		Weight::from_parts(4_337_000, 0)
+		// Minimum execution time: 2_070_000 picoseconds.
+		Weight::from_parts(2_230_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: Refungible CollectionAllowance (r:1 w:0)
@@ -917,8 +938,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `4`
 		//  Estimated: `3576`
-		// Minimum execution time: 2_170_000 picoseconds.
-		Weight::from_parts(2_301_000, 3576)
+		// Minimum execution time: 1_270_000 picoseconds.
+		Weight::from_parts(1_420_000, 3576)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
 	/// Storage: Refungible TokenProperties (r:1 w:1)
@@ -927,8 +948,8 @@
 		// Proof Size summary in bytes:
 		//  Measured:  `120`
 		//  Estimated: `36269`
-		// Minimum execution time: 2_098_000 picoseconds.
-		Weight::from_parts(2_251_000, 36269)
+		// Minimum execution time: 1_010_000 picoseconds.
+		Weight::from_parts(1_160_000, 36269)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -53,11 +53,7 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use frame_support::{
-	dispatch::{DispatchResult, DispatchResultWithPostInfo},
-	fail,
-	pallet_prelude::*,
-};
+use frame_support::{dispatch::DispatchResult, fail, pallet_prelude::*};
 use pallet_common::{
 	dispatch::CollectionDispatch, erc::CrossAccountId, eth::is_collection,
 	CommonCollectionOperations,
@@ -267,22 +263,6 @@
 		}
 
 		Err(<Error<T>>::DepthLimit.into())
-	}
-
-	/// Burn token and all of it's nested tokens
-	///
-	/// - `self_budget`: Limit for searching children in depth.
-	/// - `breadth_budget`: Limit of breadth of searching children.
-	pub fn burn_item_recursively(
-		from: T::CrossAccountId,
-		collection: CollectionId,
-		token: TokenId,
-		self_budget: &dyn Budget,
-		breadth_budget: &dyn Budget,
-	) -> DispatchResultWithPostInfo {
-		let dispatch = T::CollectionDispatch::dispatch(collection)?;
-		let dispatch = dispatch.as_dyn();
-		dispatch.burn_item_recursively(from, token, self_budget, breadth_budget)
 	}
 
 	/// Check if `token` indirectly owned by `user`
modifiedpallets/unique/Cargo.tomldiffbeforeafterboth
--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -31,7 +31,9 @@
 	'parity-scale-codec/std',
 	'sp-runtime/std',
 	'sp-std/std',
+	'up-common/std',
 	'up-data-structs/std',
+	'pallet-structure/std',
 ]
 stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"]
 try-runtime = ["frame-support/try-runtime"]
@@ -53,9 +55,11 @@
 pallet-evm-coder-substrate = { workspace = true }
 pallet-nonfungible = { workspace = true }
 pallet-refungible = { workspace = true }
+pallet-structure = { workspace = true }
 scale-info = { workspace = true }
 sp-core = { workspace = true }
 sp-io = { workspace = true }
 sp-runtime = { workspace = true }
 sp-std = { workspace = true }
+up-common = { workspace = true }
 up-data-structs = { workspace = true }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -84,13 +84,20 @@
 
 #[frame_support::pallet]
 pub mod pallet {
-	use frame_support::{dispatch::DispatchResult, ensure, fail, storage::Key, BoundedVec};
+	use frame_support::{
+		dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},
+		ensure, fail,
+		storage::Key,
+		BoundedVec,
+	};
 	use frame_system::{ensure_root, ensure_signed};
 	use pallet_common::{
 		dispatch::{dispatch_tx, CollectionDispatch},
-		CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,
+		CollectionHandle, CommonCollectionOperations, CommonWeightInfo, Pallet as PalletCommon,
+		RefungibleExtensionsWeightInfo,
 	};
 	use pallet_evm::account::CrossAccountId;
+	use pallet_structure::weights::WeightInfo as StructureWeightInfo;
 	use scale_info::TypeInfo;
 	use sp_std::{vec, vec::Vec};
 	use up_data_structs::{
@@ -104,9 +111,6 @@
 	use weights::WeightInfo;
 
 	use super::*;
-
-	/// A maximum number of levels of depth in the token nesting tree.
-	pub const NESTING_BUDGET: u32 = 5;
 
 	/// Errors for the common Unique transactions.
 	#[pallet::error]
@@ -128,6 +132,8 @@
 		/// Weight information for common pallet operations.
 		type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;
 
+		type StructureWeightInfo: StructureWeightInfo;
+
 		/// Weight info information for extra refungible pallet operations.
 		type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;
 	}
@@ -264,7 +270,7 @@
 	impl<T: Config> Pallet<T> {
 		/// A maximum number of levels of depth in the token nesting tree.
 		fn nesting_budget() -> u32 {
-			NESTING_BUDGET
+			5
 		}
 
 		/// Maximal length of a collection name.
@@ -666,7 +672,7 @@
 		/// * `owner`: Address of the initial owner of the item.
 		/// * `data`: Token data describing the item to store on chain.
 		#[pallet::call_index(11)]
-		#[pallet::weight(T::CommonWeightInfo::create_item(data))]
+		#[pallet::weight(T::CommonWeightInfo::create_item(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn create_item(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -674,9 +680,9 @@
 			data: CreateItemData,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.create_item(sender, owner, data, &budget)
 			})
 		}
@@ -700,7 +706,7 @@
 		/// * `owner`: Address of the initial owner of the tokens.
 		/// * `items_data`: Vector of data describing each item to be created.
 		#[pallet::call_index(12)]
-		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data))]
+		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data) + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn create_multiple_items(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -709,9 +715,9 @@
 		) -> DispatchResultWithPostInfo {
 			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.create_multiple_items(sender, owner, items_data, &budget)
 			})
 		}
@@ -791,7 +797,7 @@
 		/// * `properties`: Vector of key-value pairs stored as the token's metadata.
 		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.
 		#[pallet::call_index(15)]
-		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32))]
+		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn set_token_properties(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -801,9 +807,9 @@
 			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.set_token_properties(sender, token_id, properties, &budget)
 			})
 		}
@@ -824,7 +830,7 @@
 		/// * `property_keys`: Vector of keys of the properties to be deleted.
 		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.
 		#[pallet::call_index(16)]
-		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32))]
+		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn delete_token_properties(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -834,9 +840,9 @@
 			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.delete_token_properties(sender, token_id, property_keys, &budget)
 			})
 		}
@@ -888,16 +894,16 @@
 		/// * `collection_id`: ID of the collection to which the tokens would belong.
 		/// * `data`: Explicit item creation data.
 		#[pallet::call_index(18)]
-		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data))]
+		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn create_multiple_items_ex(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
 			data: CreateItemExData<T::CrossAccountId>,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.create_multiple_items_ex(sender, data, &budget)
 			})
 		}
@@ -995,7 +1001,7 @@
 		///     * Fungible Mode: The desired number of pieces to burn.
 		///     * Re-Fungible Mode: The desired number of pieces to burn.
 		#[pallet::call_index(21)]
-		#[pallet::weight(T::CommonWeightInfo::burn_from())]
+		#[pallet::weight(T::CommonWeightInfo::burn_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn burn_from(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -1004,9 +1010,9 @@
 			value: u128,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.burn_from(sender, from, item_id, value, &budget)
 			})
 		}
@@ -1033,7 +1039,7 @@
 		///     * Fungible Mode: The desired number of pieces to transfer.
 		///     * Re-Fungible Mode: The desired number of pieces to transfer.
 		#[pallet::call_index(22)]
-		#[pallet::weight(T::CommonWeightInfo::transfer())]
+		#[pallet::weight(T::CommonWeightInfo::transfer() + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn transfer(
 			origin: OriginFor<T>,
 			recipient: T::CrossAccountId,
@@ -1042,9 +1048,9 @@
 			value: u128,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.transfer(sender, recipient, item_id, value, &budget)
 			})
 		}
@@ -1138,7 +1144,7 @@
 		///     * Fungible Mode: The desired number of pieces to transfer.
 		///     * Re-Fungible Mode: The desired number of pieces to transfer.
 		#[pallet::call_index(25)]
-		#[pallet::weight(T::CommonWeightInfo::transfer_from())]
+		#[pallet::weight(T::CommonWeightInfo::transfer_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]
 		pub fn transfer_from(
 			origin: OriginFor<T>,
 			from: T::CrossAccountId,
@@ -1148,9 +1154,9 @@
 			value: u128,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
+			let budget = Self::structure_nesting_budget();
 
-			dispatch_tx::<T, _>(collection_id, |d| {
+			Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {
 				d.transfer_from(sender, from, recipient, item_id, value, &budget)
 			})
 		}
@@ -1348,5 +1354,44 @@
 
 			Ok(())
 		}
+
+		fn structure_nesting_budget() -> budget::Value {
+			budget::Value::new(Self::nesting_budget())
+		}
+
+		fn nesting_budget_weight(value: &budget::Value) -> Weight {
+			T::StructureWeightInfo::find_parent().saturating_mul(value.remaining() as u64)
+		}
+
+		fn nesting_budget_predispatch_weight() -> Weight {
+			Self::nesting_budget_weight(&Self::structure_nesting_budget())
+		}
+
+		pub fn dispatch_tx_with_nesting_budget<
+			C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,
+		>(
+			collection: CollectionId,
+			budget: &budget::Value,
+			call: C,
+		) -> DispatchResultWithPostInfo {
+			let mut result = dispatch_tx::<T, _>(collection, call);
+
+			match &mut result {
+				Ok(PostDispatchInfo {
+					actual_weight: Some(weight),
+					..
+				})
+				| Err(DispatchErrorWithPostInfo {
+					post_info: PostDispatchInfo {
+						actual_weight: Some(weight),
+						..
+					},
+					..
+				}) => *weight += Self::nesting_budget_weight(budget),
+				_ => {}
+			}
+
+			result
+		}
 	}
 }
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -45,6 +45,7 @@
 
 /// Minimum balance required to create or keep an account open.
 pub const EXISTENTIAL_DEPOSIT: u128 = 0;
+
 /// Amount of Balance reserved for candidate registration.
 pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE;
 /// Amount of maximum collators for Collator Selection.
modifiedprimitives/data-structs/src/budget.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -1,4 +1,4 @@
-use core::cell::Cell;
+use sp_std::cell::Cell;
 
 pub trait Budget {
 	/// Returns true while not exceeded
@@ -22,7 +22,7 @@
 	pub fn new(v: u32) -> Self {
 		Self(Cell::new(v))
 	}
-	pub fn refund(self) -> u32 {
+	pub fn remaining(&self) -> u32 {
 		self.0.get()
 	}
 }
modifiedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -116,6 +116,7 @@
 impl pallet_unique::Config for Runtime {
 	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
 	type CommonWeightInfo = CommonWeights<Self>;
+	type StructureWeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
 	type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
 }
 
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -84,7 +84,7 @@
 				}
 
 				fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
-					let budget = up_data_structs::budget::Value::new(10);
+					let budget = budget::Value::new(10);
 
 					<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)
 				}
modifiedruntime/common/weights/mod.rsdiffbeforeafterboth
--- a/runtime/common/weights/mod.rs
+++ b/runtime/common/weights/mod.rs
@@ -98,10 +98,6 @@
 		dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
 	}
 
-	fn delete_token_properties(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
-	}
-
 	fn set_token_property_permissions(amount: u32) -> Weight {
 		dispatch_weight::<T>() + max_weight_of!(set_token_property_permissions(amount))
 	}
@@ -124,26 +120,14 @@
 
 	fn burn_from() -> Weight {
 		dispatch_weight::<T>() + max_weight_of!(burn_from())
-	}
-
-	fn burn_recursively_self_raw() -> Weight {
-		max_weight_of!(burn_recursively_self_raw())
-	}
-
-	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
-		max_weight_of!(burn_recursively_breadth_raw(amount))
-	}
-
-	fn token_owner() -> Weight {
-		max_weight_of!(token_owner())
 	}
 
 	fn set_allowance_for_all() -> Weight {
-		max_weight_of!(set_allowance_for_all())
+		dispatch_weight::<T>() + max_weight_of!(set_allowance_for_all())
 	}
 
 	fn force_repair_item() -> Weight {
-		max_weight_of!(force_repair_item())
+		dispatch_weight::<T>() + max_weight_of!(force_repair_item())
 	}
 }