git.delta.rocks / unique-network / refs/commits / 742862849b4b

difftreelog

fix EVM mint with properties, minor improvements

Daniel Shiposha2023-10-13parent: #aae21d3.patch.diff
in: master

9 files changed

modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -29,9 +29,8 @@
 use sp_std::{vec, vec::Vec};
 use up_data_structs::{
 	AccessMode, CollectionId, CollectionMode, CollectionPermissions, CreateCollectionData,
-	NestingPermissions, PropertiesPermissionMap, Property, PropertyKey, PropertyValue,
-	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM,
-	MAX_TOKEN_PREFIX_LENGTH,
+	NestingPermissions, Property, PropertyKey, PropertyValue, MAX_COLLECTION_DESCRIPTION_LENGTH,
+	MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM, MAX_TOKEN_PREFIX_LENGTH,
 };
 
 use crate::{BenchmarkPropertyWriter, CollectionHandle, Config, Pallet};
@@ -190,31 +189,6 @@
 		#[block]
 		{
 			<Pallet<T>>::set_collection_properties(&collection, &owner, props.into_iter())?;
-		}
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn delete_collection_properties(
-		b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
-	) -> Result<(), BenchmarkError> {
-		bench_init! {
-			owner: sub; collection: collection(owner);
-			owner: cross_from_sub;
-		};
-		let props = (0..b)
-			.map(|p| Property {
-				key: property_key(p as usize),
-				value: property_value(),
-			})
-			.collect::<Vec<_>>();
-		<Pallet<T>>::set_collection_properties(&collection, &owner, props.into_iter())?;
-		let to_delete = (0..b).map(|p| property_key(p as usize)).collect::<Vec<_>>();
-
-		#[block]
-		{
-			<Pallet<T>>::delete_collection_properties(&collection, &owner, to_delete.into_iter())?;
 		}
 
 		Ok(())
@@ -253,7 +227,7 @@
 	}
 
 	#[benchmark]
-	fn init_token_properties_common() -> Result<(), BenchmarkError> {
+	fn property_writer_load_collection_info() -> Result<(), BenchmarkError> {
 		bench_init! {
 			owner: sub; collection: collection(owner);
 			sender: sub;
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -126,7 +126,7 @@
 	///
 	/// @param key Property key.
 	#[solidity(hide)]
-	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]
+	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]
 	fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let key = <Vec<u8>>::from(key)
@@ -139,7 +139,7 @@
 	/// Delete collection properties.
 	///
 	/// @param keys Properties keys.
-	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]
+	#[weight(<SelfWeightOf<T>>::set_collection_properties(keys.len() as u32))]
 	fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let keys = keys
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -2626,8 +2626,8 @@
 impl<T: Config> BenchmarkPropertyWriter<T> {
 	/// Creates a [`PropertyWriter`] for benchmarking tokens properties writing.
 	pub fn new<'a, Handle>(
-		collection: &Handle,
-		collection_lazy_info: PropertyWriterLazyCollectionInfo,
+		collection: &'a Handle,
+		collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,
 	) -> PropertyWriter<'a, Self, T, Handle>
 	where
 		Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,7 +18,6 @@
 use pallet_common::{
 	bench_init,
 	benchmarking::{create_collection_raw, property_key, property_value},
-	CommonCollectionOperations,
 };
 use sp_std::prelude::*;
 use up_data_structs::{
@@ -131,53 +130,12 @@
 		#[block]
 		{
 			<Pallet<T>>::burn(&collection, &burner, item)?;
-		}
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn burn_recursively_self_raw() -> Result<(), BenchmarkError> {
-		bench_init! {
-			owner: sub; collection: collection(owner);
-			sender: cross_from_sub(owner); burner: cross_sub;
-		};
-		let item = create_max_item(&collection, &sender, burner.clone())?;
-
-		#[block]
-		{
-			<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?;
 		}
 
 		Ok(())
 	}
 
 	#[benchmark]
-	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(
-		b: Linear<0, 200>,
-	) -> Result<(), BenchmarkError> {
-		bench_init! {
-			owner: sub; collection: collection(owner);
-			sender: cross_from_sub(owner); burner: cross_sub;
-		};
-		let item = create_max_item(&collection, &sender, burner.clone())?;
-		for _ in 0..b {
-			create_max_item(
-				&collection,
-				&sender,
-				T::CrossTokenAddressMapping::token_to_address(collection.id, item),
-			)?;
-		}
-
-		#[block]
-		{
-			<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?;
-		}
-
-		Ok(())
-	}
-
-	#[benchmark]
 	fn transfer_raw() -> Result<(), BenchmarkError> {
 		bench_init! {
 			owner: sub; collection: collection(owner);
@@ -262,116 +220,34 @@
 		{
 			<Pallet<T>>::burn_from(&collection, &burner, &sender, item, &Unlimited)?;
 		}
-	}
 
-	// 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;
-	// 	};
+		Ok(())
+	}
 
-	// 	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(
-		b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
-	) -> Result<(), BenchmarkError> {
+	fn load_token_properties() -> Result<(), BenchmarkError> {
 		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: false,
-					token_owner: false,
-				},
-			})
-			.collect::<Vec<_>>();
 
+		let item = create_max_item(&collection, &owner, owner.clone())?;
+
 		#[block]
 		{
-			<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+			pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(&collection, item);
 		}
 
 		Ok(())
 	}
 
 	#[benchmark]
-	fn set_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
+	fn write_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
 		bench_init! {
 			owner: sub; collection: collection(owner);
 			owner: cross_from_sub;
 		};
+
 		let perms = (0..b)
 			.map(|k| PropertyKeyPermission {
 				key: property_key(k as usize),
@@ -391,71 +267,29 @@
 			.collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
 
+		let lazy_collection_info =
+			pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(&collection, &owner);
+
 		#[block]
 		{
-			<Pallet<T>>::set_token_properties(
-				&collection,
-				&owner,
+			let mut property_writer =
+				pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+			property_writer.write_token_properties(
 				item,
 				props.into_iter(),
-				&Unlimited,
+				crate::erc::ERC721TokenEvent::TokenChanged {
+					token_id: item.into(),
+				}
+				.to_log(T::ContractAddress::get()),
 			)?;
 		}
 
 		Ok(())
 	}
 
-	// TODO:
 	#[benchmark]
-	fn init_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
-		// 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)?;
-		#[block]
-		{}
-		// 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 (is_collection_admin, property_permissions) =
-		// 	load_is_admin_and_property_permissions(&collection, &owner);
-		// #[block]
-		// {
-		// 	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()),
-		// 	)?;
-		// }
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn delete_token_properties(
+	fn set_token_property_permissions(
 		b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
 	) -> Result<(), BenchmarkError> {
 		bench_init! {
@@ -466,54 +300,16 @@
 			.map(|k| PropertyKeyPermission {
 				key: property_key(k as usize),
 				permission: PropertyPermission {
-					mutable: true,
-					collection_admin: true,
-					token_owner: true,
+					mutable: false,
+					collection_admin: false,
+					token_owner: false,
 				},
-			})
-			.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,
-		)?;
-		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 
 		#[block]
 		{
-			<Pallet<T>>::delete_token_properties(
-				&collection,
-				&owner,
-				item,
-				to_delete.into_iter(),
-				&Unlimited,
-			)?;
-		}
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn token_owner() -> Result<(), BenchmarkError> {
-		bench_init! {
-			owner: sub; collection: collection(owner);
-			owner: cross_from_sub;
-		};
-		let item = create_max_item(&collection, &owner, owner.clone())?;
-
-		#[block]
-		{
-			collection.token_owner(item).unwrap();
+			<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
 		}
 
 		Ok(())
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -39,24 +39,21 @@
 impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
 		match data {
-			CreateItemExData::NFT(t) => <SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32)
-				.saturating_add(write_token_properties_total_weight::<T, _>(
-					t.iter().map(|t| t.properties.len() as u32),
-					<SelfWeightOf<T>>::write_token_properties,
-				)),
+			CreateItemExData::NFT(t) => mint_with_props_weight::<T>(
+				<SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32),
+				t.iter().map(|t| t.properties.len() as u32),
+			),
 			_ => Weight::zero(),
 		}
 	}
 
 	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
-		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
-			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>>::write_token_properties,
-			),
+		mint_with_props_weight::<T>(
+			<SelfWeightOf<T>>::create_multiple_items(data.len() as u32),
+			data.iter().map(|t| match t {
+				up_data_structs::CreateItemData::NFT(n) => n.properties.len() as u32,
+				_ => 0,
+			}),
 		)
 	}
 
@@ -113,6 +110,16 @@
 	}
 }
 
+pub(crate) fn mint_with_props_weight<T: Config>(
+	create_no_data_weight: Weight,
+	tokens: impl Iterator<Item = u32> + Clone,
+) -> Weight {
+	create_no_data_weight.saturating_add(write_token_properties_total_weight::<T, _>(
+		tokens,
+		<SelfWeightOf<T>>::write_token_properties,
+	))
+}
+
 fn map_create_data<T: Config>(
 	data: up_data_structs::CreateItemData,
 	to: &T::CrossAccountId,
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, 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/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -421,114 +421,30 @@
 		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>,
-	) -> Result<(), BenchmarkError> {
+	fn load_token_properties() -> Result<(), BenchmarkError> {
 		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: false,
-					token_owner: false,
-				},
-			})
-			.collect::<Vec<_>>();
 
+		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+
 		#[block]
 		{
-			<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+			pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(&collection, item);
 		}
 
 		Ok(())
 	}
 
 	#[benchmark]
-	fn set_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
+	fn write_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
 		bench_init! {
 			owner: sub; collection: collection(owner);
 			owner: cross_from_sub;
 		};
+
 		let perms = (0..b)
 			.map(|k| PropertyKeyPermission {
 				key: property_key(k as usize),
@@ -548,73 +464,29 @@
 			.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);
+
 		#[block]
 		{
-			<Pallet<T>>::set_token_properties(
-				&collection,
-				&owner,
+			let mut property_writer =
+				pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+			property_writer.write_token_properties(
 				item,
 				props.into_iter(),
-				&Unlimited,
+				crate::erc::ERC721TokenEvent::TokenChanged {
+					token_id: item.into(),
+				}
+				.to_log(T::ContractAddress::get()),
 			)?;
 		}
 
 		Ok(())
 	}
 
-	// TODO:
 	#[benchmark]
-	fn init_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
-		// 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)?;
-
-		#[block]
-		{}
-		// 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 (is_collection_admin, property_permissions) = load_is_admin_and_property_permissions(&collection, &owner)
-		// let mut property_writer = pallet_common::collection_info_loaded_property_writer(
-		// 	&collection,
-		// 	is_collection_admin,
-		// 	property_permissions,
-		// );
-
-		// #[block]
-		// {
-		// 	property_writer.write_token_properties(
-		// 		true,
-		// 		item,
-		// 		props.into_iter(),
-		// 		crate::erc::ERC721TokenEvent::TokenChanged {
-		// 			token_id: item.into(),
-		// 		}
-		// 		.to_log(T::ContractAddress::get()),
-		// 	)?;
-		// }
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn delete_token_properties(
+	fn set_token_property_permissions(
 		b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
 	) -> Result<(), BenchmarkError> {
 		bench_init! {
@@ -625,38 +497,16 @@
 			.map(|k| PropertyKeyPermission {
 				key: property_key(k as usize),
 				permission: PropertyPermission {
-					mutable: true,
-					collection_admin: true,
-					token_owner: true,
+					mutable: false,
+					collection_admin: false,
+					token_owner: false,
 				},
-			})
-			.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,
-		)?;
-		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 
 		#[block]
 		{
-			<Pallet<T>>::delete_token_properties(
-				&collection,
-				&owner,
-				item,
-				to_delete.into_iter(),
-				&Unlimited,
-			)?;
+			<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
 		}
 
 		Ok(())
@@ -673,22 +523,6 @@
 		#[block]
 		{
 			<Pallet<T>>::repartition(&collection, &owner, item, 200)?;
-		}
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn token_owner() -> Result<(), BenchmarkError> {
-		bench_init! {
-			owner: sub; collection: collection(owner);
-			sender: cross_from_sub(owner); owner: cross_sub;
-		};
-		let item = create_max_item(&collection, &sender, [(owner, 100)])?;
-
-		#[block]
-		{
-			<Pallet<T>>::token_owner(collection.id, item).unwrap();
 		}
 
 		Ok(())
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -47,35 +47,27 @@
 pub struct CommonWeights<T: Config>(PhantomData<T>);
 impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
-		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
-			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>>::write_token_properties,
-			),
+		mint_with_props_weight::<T>(
+			<SelfWeightOf<T>>::create_multiple_items(data.len() as u32),
+			data.iter().map(|data| match data {
+				up_data_structs::CreateItemData::ReFungible(rft_data) => {
+					rft_data.properties.len() as u32
+				}
+				_ => 0,
+			}),
 		)
 	}
 
 	fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {
 		match call {
-			CreateItemExData::RefungibleMultipleOwners(i) => {
-				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
-					.saturating_add(write_token_properties_total_weight::<T, _>(
-						[i.properties.len() as u32].into_iter(),
-						<SelfWeightOf<T>>::write_token_properties,
-					))
-			}
-			CreateItemExData::RefungibleMultipleItems(i) => {
-				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
-					.saturating_add(write_token_properties_total_weight::<T, _>(
-						i.iter().map(|d| d.properties.len() as u32),
-						<SelfWeightOf<T>>::write_token_properties,
-					))
-			}
+			CreateItemExData::RefungibleMultipleOwners(i) => mint_with_props_weight::<T>(
+				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32),
+				[i.properties.len() as u32].into_iter(),
+			),
+			CreateItemExData::RefungibleMultipleItems(i) => mint_with_props_weight::<T>(
+				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32),
+				i.iter().map(|d| d.properties.len() as u32),
+			),
 			_ => Weight::zero(),
 		}
 	}
@@ -138,6 +130,16 @@
 	}
 }
 
+pub(crate) fn mint_with_props_weight<T: Config>(
+	create_no_data_weight: Weight,
+	tokens: impl Iterator<Item = u32> + Clone,
+) -> Weight {
+	create_no_data_weight.saturating_add(write_token_properties_total_weight::<T, _>(
+		tokens,
+		<SelfWeightOf<T>>::write_token_properties,
+	))
+}
+
 fn map_create_data<T: Config>(
 	data: up_data_structs::CreateItemData,
 	to: &T::CrossAccountId,
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -50,8 +50,10 @@
 };
 
 use crate::{
-	common::CommonWeights, weights::WeightInfo, AccountBalance, Balance, Config, CreateItemData,
-	Pallet, RefungibleHandle, SelfWeightOf, TokenProperties, TokensMinted, TotalSupply,
+	common::{mint_with_props_weight, CommonWeights},
+	weights::WeightInfo,
+	AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,
+	TokenProperties, TokensMinted, TotalSupply,
 };
 
 frontier_contract! {
@@ -661,7 +663,7 @@
 	/// @param tokenUri Token URI that would be stored in the NFT properties
 	/// @return uint256 The id of the newly minted token
 	#[solidity(rename_selector = "mintWithTokenURI")]
-	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]
+	#[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]
 	fn mint_with_token_uri(
 		&mut self,
 		caller: Caller,
@@ -683,7 +685,7 @@
 	/// @param tokenId ID of the minted RFT
 	/// @param tokenUri Token URI that would be stored in the RFT properties
 	#[solidity(hide, rename_selector = "mintWithTokenURI")]
-	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]
+	#[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]
 	fn mint_with_token_uri_check_id(
 		&mut self,
 		caller: Caller,
@@ -1052,22 +1054,26 @@
 	}
 
 	/// @notice Function to mint a token.
-	/// @param tokenProperties Properties of minted token
-	#[weight(if token_properties.len() == 1 {
-		<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32)
+	/// @param tokensData Data of minted token(s)
+	#[weight(if tokens_data.len() == 1 {
+		let token_data = tokens_data.first().unwrap();
+
+		mint_with_props_weight::<T>(
+			<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_data.owners.len() as u32),
+			[token_data.properties.len() as u32].into_iter(),
+		)
 	} else {
-		<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(token_properties.len() as u32)
-	} + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
-	fn mint_bulk_cross(
-		&mut self,
-		caller: Caller,
-		token_properties: Vec<MintTokenData>,
-	) -> Result<bool> {
+		mint_with_props_weight::<T>(
+			<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(tokens_data.len() as u32),
+			tokens_data.iter().map(|d| d.properties.len() as u32),
+		)
+	})]
+	fn mint_bulk_cross(&mut self, caller: Caller, tokens_data: Vec<MintTokenData>) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
-		let has_multiple_tokens = token_properties.len() > 1;
+		let has_multiple_tokens = tokens_data.len() > 1;
 
-		let mut create_rft_data = Vec::with_capacity(token_properties.len());
-		for MintTokenData { owners, properties } in token_properties {
+		let mut create_rft_data = Vec::with_capacity(tokens_data.len());
+		for MintTokenData { owners, properties } in tokens_data {
 			let has_multiple_owners = owners.len() > 1;
 			if has_multiple_tokens & has_multiple_owners {
 				return Err(
@@ -1108,7 +1114,12 @@
 	/// @param to The new owner
 	/// @param tokens array of pairs of token ID and token URI for minted tokens
 	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]
-	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]
+	#[weight(
+		mint_with_props_weight::<T>(
+			<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32),
+			tokens.iter().map(|_| 1),
+		)
+	)]
 	fn mint_bulk_with_token_uri(
 		&mut self,
 		caller: Caller,
@@ -1162,7 +1173,7 @@
 	/// @param to The new owner crossAccountId
 	/// @param properties Properties of minted token
 	/// @return uint256 The id of the newly minted token
-	#[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]
+	#[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [properties.len() as u32].into_iter()))]
 	fn mint_cross(
 		&mut self,
 		caller: Caller,