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

difftreelog

feat calculate signature in compile time

Trubnikov Sergey2022-10-11parent: #4d4a023.patch.diff
in: master

11 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1096,6 +1096,26 @@
 checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
 
 [[package]]
+name = "const_format"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
 name = "constant_time_eq"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2353,6 +2373,7 @@
 version = "0.1.3"
 dependencies = [
  "concat-idents",
+ "const_format",
  "ethereum",
  "evm-coder-procedural",
  "evm-core 0.35.0 (git+https://github.com/uniquenetwork/evm?branch=unique-polkadot-v0.9.30)",
@@ -2362,6 +2383,7 @@
  "impl-trait-for-tuples",
  "pallet-evm 6.0.0-dev (git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit)",
  "primitive-types",
+ "sha3-const",
  "similar-asserts",
  "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30)",
 ]
@@ -11085,6 +11107,12 @@
 ]
 
 [[package]]
+name = "sha3-const"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9625d558d174dbdc711248b479271c0fdca39e9d3d67f6e890b3d4251fbf8e"
+
+[[package]]
 name = "sharded-slab"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedcrates/evm-coder/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -5,6 +5,10 @@
 edition = "2021"
 
 [dependencies]
+const_format = { version = "0.2.26", default-features = false }
+sha3-const = { version = "0.1.0", default-features = false }
+# Ethereum uses keccak (=sha3) for selectors
+# sha3 = "0.10.1"
 # evm-coder reexports those proc-macro
 evm-coder-procedural = { path = "./procedural" }
 # Evm uses primitive-types for H160, H256 and others
modifiedcrates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -20,6 +20,7 @@
 // about Procedural Macros in Rust book:
 // https://doc.rust-lang.org/reference/procedural-macros.html
 
+use proc_macro2::{TokenStream, Group};
 use quote::{quote, ToTokens};
 use inflector::cases;
 use std::fmt::Write;
@@ -328,6 +329,7 @@
 	}
 }
 
+#[derive(Debug)]
 enum AbiType {
 	// type
 	Plain(Ident),
@@ -473,6 +475,7 @@
 	}
 }
 
+#[derive(Debug)]
 struct MethodArg {
 	name: Ident,
 	camel_name: String,
@@ -722,13 +725,25 @@
 		}
 	}
 
+	fn expand_selector(&self) -> proc_macro2::TokenStream {
+		let custom_signature = self.expand_custom_signature();
+		quote! {
+			 {
+				let a = ::evm_coder::sha3_const::Keccak256::new()
+					.update(#custom_signature.as_bytes())
+					.finalize();
+				[a[0], a[1], a[2], a[3]]
+			}
+		}
+	}
+
 	fn expand_const(&self) -> proc_macro2::TokenStream {
 		let screaming_name = &self.screaming_name;
-		let selector = u32::to_be_bytes(self.selector);
 		let selector_str = &self.selector_str;
+		let selector = &self.expand_selector();
 		quote! {
 			#[doc = #selector_str]
-			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
+			const #screaming_name: ::evm_coder::types::bytes4 = #selector;
 		}
 	}
 
@@ -831,6 +846,59 @@
 		}
 	}
 
+	fn expand_custom_signature(&self) -> proc_macro2::TokenStream {
+		let mut first_comma = true;
+		let mut custom_signature = TokenStream::new();
+		let mut template = self.camel_name.clone() + "(";
+		self.args
+			.iter()
+			.filter_map(|a| {
+				if a.is_special() {
+					return None;
+				};
+
+				match a.ty {
+					AbiType::Plain(ref ident) => Some(ident),
+					_ => None,
+				}
+			})
+			.for_each(|ident| {
+				if !first_comma {
+					custom_signature.extend(quote!(,));
+					template.push(',');
+				} else {
+					first_comma = false;
+				};
+				template.push_str("{}");
+				let ident_str = ident.to_string();
+				match ident_str.as_str() {
+					"address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128"
+					| "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller"
+					| "bool" | "" => {
+						custom_signature.extend(quote!(#ident_str));
+					}
+					_ => {
+						custom_signature.extend(quote! {
+							#ident::SIGNATURE_STRING
+						});
+					}
+				}
+			});
+
+		template.push(')');
+		let mut template = quote!(#template);
+		template.extend(quote!(,));
+		template.extend(custom_signature);
+		let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template);
+		let mut custom_signature = quote! {
+			::evm_coder::const_format::formatcp!
+		};
+		custom_signature.extend(custom_signature_group.to_token_stream());
+
+		// println!("!!!!! {}", custom_signature);
+		custom_signature
+	}
+
 	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
 		let camel_name = &self.camel_name;
 		let mutability = match self.mutability {
@@ -847,15 +915,17 @@
 			.map(MethodArg::expand_solidity_argument);
 		let docs = &self.docs;
 		let selector_str = &self.selector_str;
-		let selector = self.selector;
+		let selector = &self.expand_selector();
 		let hide = self.hide;
+		let custom_signature = self.expand_custom_signature();
 		let is_payable = self.has_value_args;
 		quote! {
 			SolidityFunction {
 				docs: &[#(#docs),*],
 				selector_str: #selector_str,
-				selector: #selector,
 				hide: #hide,
+				selector: u32::from_be_bytes(#selector),
+				custom_signature: #custom_signature,
 				name: #camel_name,
 				mutability: #mutability,
 				is_payable: #is_payable,
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -90,6 +90,8 @@
 pub use evm_coder_procedural::solidity;
 /// See [`solidity_interface`]
 pub use evm_coder_procedural::weight;
+pub use const_format;
+pub use sha3_const;
 
 /// Derives [`ToLog`] for enum
 ///
@@ -227,6 +229,14 @@
 		}
 	}
 
+	impl SignatureString for EthCrossAccount {
+		const SIGNATURE_STRING: &'static str = "(address,uint256)";
+	}
+
+	pub trait SignatureString {
+		const SIGNATURE_STRING: &'static str;
+	}
+
 	/// Convert `CrossAccountId` to `uint256`.
 	pub fn convert_cross_account_to_uint256<T: pallet_evm::account::Config>(
 		from: &T::CrossAccountId,
@@ -348,6 +358,18 @@
 		assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
 	}
 
+	// #[test]
+	// fn function_selector_generation_1() {
+	// 	assert_eq!(
+	// 		fn_selector!(transferFromCrossAccountToCrossAccount(
+	// 			EthCrossAccount,
+	// 			EthCrossAccount,
+	// 			uint256
+	// 		)),
+	// 		2543295963
+	// 	);
+	// }
+
 	#[test]
 	fn event_topic_generation() {
 		assert_eq!(
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -35,6 +35,12 @@
 use crate::types::*;
 
 #[derive(Default)]
+pub struct FunctionSelectorMaker {
+	pub name: string,
+	pub args: Vec<fn() -> string>,
+}
+
+#[derive(Default)]
 pub struct TypeCollector {
 	/// Code => id
 	/// id ordering is required to perform topo-sort on the resulting data
@@ -482,11 +488,25 @@
 	View,
 	Mutable,
 }
+
+// fn fn_selector_str(input: &str) -> u32 {
+// 	use sha3::Digest;
+// 	let mut hasher = sha3::Keccak256::new();
+// 	hasher.update(input.as_bytes());
+// 	let result = hasher.finalize();
+
+// 	let mut selector_bytes = [0; 4];
+// 	selector_bytes.copy_from_slice(&result[0..4]);
+
+// 	u32::from_be_bytes(selector_bytes)
+// }
+
 pub struct SolidityFunction<A, R> {
 	pub docs: &'static [&'static str],
 	pub selector_str: &'static str,
 	pub selector: u32,
 	pub hide: bool,
+	pub custom_signature: &'static str,
 	pub name: &'static str,
 	pub args: A,
 	pub result: R,
@@ -512,7 +532,7 @@
 		writeln!(
 			writer,
 			"\t{hide_comment}///  or in textual repr: {}",
-			self.selector_str
+			self.custom_signature
 		)?;
 		write!(writer, "\t{hide_comment}function {}(", self.name)?;
 		self.args.solidity_name(writer, tc)?;
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -27,7 +27,7 @@
 evm-coder = { default-features = false, path = '../../crates/evm-coder' }
 pallet-common = { default-features = false, path = '../../pallets/common' }
 pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
-pallet-evm-transaction-payment = {  default-features = false, path = '../../pallets/evm-transaction-payment' }
+pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [
     'serde1',
 ] }
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,6 +16,8 @@
 
 //! Implementation of magic contract
 
+extern crate alloc;
+use alloc::{format, string::ToString};
 use core::marker::PhantomData;
 use evm_coder::{
 	abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -16,6 +16,8 @@
 
 //! ERC-20 standart support implementation.
 
+extern crate alloc;
+use alloc::{format, string::ToString};
 use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
 use core::convert::TryInto;
 use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
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;23use core::{24	char::{REPLACEMENT_CHARACTER, decode_utf16},25	convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31	CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37	CollectionHandle, CollectionPropertyPermissions,38	eth::convert_tuple_to_cross_account,39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::call;42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4344use crate::{45	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,46	SelfWeightOf, weights::WeightInfo, TokenProperties,47};4849/// @title A contract that allows to set and delete token properties and change token property permissions.50#[solidity_interface(name = TokenProperties)]51impl<T: Config> NonfungibleHandle<T> {52	/// @notice Set permissions for token property.53	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.54	/// @param key Property key.55	/// @param isMutable Permission to mutate property.56	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.57	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.58	fn set_token_property_permission(59		&mut self,60		caller: caller,61		key: string,62		is_mutable: bool,63		collection_admin: bool,64		token_owner: bool,65	) -> Result<()> {66		let caller = T::CrossAccountId::from_eth(caller);67		<Pallet<T>>::set_property_permission(68			self,69			&caller,70			PropertyKeyPermission {71				key: <Vec<u8>>::from(key)72					.try_into()73					.map_err(|_| "too long key")?,74				permission: PropertyPermission {75					mutable: is_mutable,76					collection_admin,77					token_owner,78				},79			},80		)81		.map_err(dispatch_to_evm::<T>)82	}8384	/// @notice Set token property value.85	/// @dev Throws error if `msg.sender` has no permission to edit the property.86	/// @param tokenId ID of the token.87	/// @param key Property key.88	/// @param value Property value.89	fn set_property(90		&mut self,91		caller: caller,92		token_id: uint256,93		key: string,94		value: bytes,95	) -> Result<()> {96		let caller = T::CrossAccountId::from_eth(caller);97		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;98		let key = <Vec<u8>>::from(key)99			.try_into()100			.map_err(|_| "key too long")?;101		let value = value.0.try_into().map_err(|_| "value too long")?;102103		let nesting_budget = self104			.recorder105			.weight_calls_budget(<StructureWeight<T>>::find_parent());106107		<Pallet<T>>::set_token_property(108			self,109			&caller,110			TokenId(token_id),111			Property { key, value },112			&nesting_budget,113		)114		.map_err(dispatch_to_evm::<T>)115	}116117	/// @notice Set token properties value.118	/// @dev Throws error if `msg.sender` has no permission to edit the property.119	/// @param tokenId ID of the token.120	/// @param properties settable properties121	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]122	fn set_properties(123		&mut self,124		caller: caller,125		token_id: uint256,126		properties: Vec<(string, bytes)>,127	) -> Result<()> {128		let caller = T::CrossAccountId::from_eth(caller);129		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130131		let nesting_budget = self132			.recorder133			.weight_calls_budget(<StructureWeight<T>>::find_parent());134135		let properties = properties136			.into_iter()137			.map(|(key, value)| {138				let key = <Vec<u8>>::from(key)139					.try_into()140					.map_err(|_| "key too large")?;141142				let value = value.0.try_into().map_err(|_| "value too large")?;143144				Ok(Property { key, value })145			})146			.collect::<Result<Vec<_>>>()?;147148		<Pallet<T>>::set_token_properties(149			self,150			&caller,151			TokenId(token_id),152			properties.into_iter(),153			<Pallet<T>>::token_exists(&self, TokenId(token_id)),154			&nesting_budget,155		)156		.map_err(dispatch_to_evm::<T>)157	}158159	/// @notice Delete token property value.160	/// @dev Throws error if `msg.sender` has no permission to edit the property.161	/// @param tokenId ID of the token.162	/// @param key Property key.163	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {164		let caller = T::CrossAccountId::from_eth(caller);165		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;166		let key = <Vec<u8>>::from(key)167			.try_into()168			.map_err(|_| "key too long")?;169170		let nesting_budget = self171			.recorder172			.weight_calls_budget(<StructureWeight<T>>::find_parent());173174		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)175			.map_err(dispatch_to_evm::<T>)176	}177178	/// @notice Get token property value.179	/// @dev Throws error if key not found180	/// @param tokenId ID of the token.181	/// @param key Property key.182	/// @return Property value bytes183	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {184		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;185		let key = <Vec<u8>>::from(key)186			.try_into()187			.map_err(|_| "key too long")?;188189		let props = <TokenProperties<T>>::get((self.id, token_id));190		let prop = props.get(&key).ok_or("key not found")?;191192		Ok(prop.to_vec().into())193	}194}195196#[derive(ToLog)]197pub enum ERC721Events {198	/// @dev This emits when ownership of any NFT changes by any mechanism.199	///  This event emits when NFTs are created (`from` == 0) and destroyed200	///  (`to` == 0). Exception: during contract creation, any number of NFTs201	///  may be created and assigned without emitting Transfer. At the time of202	///  any transfer, the approved address for that NFT (if any) is reset to none.203	Transfer {204		#[indexed]205		from: address,206		#[indexed]207		to: address,208		#[indexed]209		token_id: uint256,210	},211	/// @dev This emits when the approved address for an NFT is changed or212	///  reaffirmed. The zero address indicates there is no approved address.213	///  When a Transfer event emits, this also indicates that the approved214	///  address for that NFT (if any) is reset to none.215	Approval {216		#[indexed]217		owner: address,218		#[indexed]219		approved: address,220		#[indexed]221		token_id: uint256,222	},223	/// @dev This emits when an operator is enabled or disabled for an owner.224	///  The operator can manage all NFTs of the owner.225	#[allow(dead_code)]226	ApprovalForAll {227		#[indexed]228		owner: address,229		#[indexed]230		operator: address,231		approved: bool,232	},233}234235#[derive(ToLog)]236pub enum ERC721UniqueMintableEvents {237	#[allow(dead_code)]238	MintingFinished {},239}240241/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension242/// @dev See https://eips.ethereum.org/EIPS/eip-721243#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]244impl<T: Config> NonfungibleHandle<T>245where246	T::AccountId: From<[u8; 32]>,247{248	/// @notice A descriptive name for a collection of NFTs in this contract249	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`250	#[solidity(hide, rename_selector = "name")]251	fn name_proxy(&self) -> Result<string> {252		self.name()253	}254255	/// @notice An abbreviated name for NFTs in this contract256	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`257	#[solidity(hide, rename_selector = "symbol")]258	fn symbol_proxy(&self) -> Result<string> {259		self.symbol()260	}261262	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.263	///264	/// @dev If the token has a `url` property and it is not empty, it is returned.265	///  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`.266	///  If the collection property `baseURI` is empty or absent, return "" (empty string)267	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix268	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).269	///270	/// @return token's const_metadata271	#[solidity(rename_selector = "tokenURI")]272	fn token_uri(&self, token_id: uint256) -> Result<string> {273		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;274275		match get_token_property(self, token_id_u32, &key::url()).as_deref() {276			Err(_) | Ok("") => (),277			Ok(url) => {278				return Ok(url.into());279			}280		};281282		let base_uri =283			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())284				.map(BoundedVec::into_inner)285				.map(string::from_utf8)286				.transpose()287				.map_err(|e| {288					Error::Revert(alloc::format!(289						"Can not convert value \"baseURI\" to string with error \"{}\"",290						e291					))292				})?;293294		let base_uri = match base_uri.as_deref() {295			None | Some("") => {296				return Ok("".into());297			}298			Some(base_uri) => base_uri.into(),299		};300301		Ok(302			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {303				Err(_) | Ok("") => base_uri,304				Ok(suffix) => base_uri + suffix,305			},306		)307	}308}309310/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension311/// @dev See https://eips.ethereum.org/EIPS/eip-721312#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]313impl<T: Config> NonfungibleHandle<T> {314	/// @notice Enumerate valid NFTs315	/// @param index A counter less than `totalSupply()`316	/// @return The token identifier for the `index`th NFT,317	///  (sort order not specified)318	fn token_by_index(&self, index: uint256) -> Result<uint256> {319		Ok(index)320	}321322	/// @dev Not implemented323	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {324		// TODO: Not implemetable325		Err("not implemented".into())326	}327328	/// @notice Count NFTs tracked by this contract329	/// @return A count of valid NFTs tracked by this contract, where each one of330	///  them has an assigned and queryable owner not equal to the zero address331	fn total_supply(&self) -> Result<uint256> {332		self.consume_store_reads(1)?;333		Ok(<Pallet<T>>::total_supply(self).into())334	}335}336337/// @title ERC-721 Non-Fungible Token Standard338/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md339#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]340impl<T: Config> NonfungibleHandle<T> {341	/// @notice Count all NFTs assigned to an owner342	/// @dev NFTs assigned to the zero address are considered invalid, and this343	///  function throws for queries about the zero address.344	/// @param owner An address for whom to query the balance345	/// @return The number of NFTs owned by `owner`, possibly zero346	fn balance_of(&self, owner: address) -> Result<uint256> {347		self.consume_store_reads(1)?;348		let owner = T::CrossAccountId::from_eth(owner);349		let balance = <AccountBalance<T>>::get((self.id, owner));350		Ok(balance.into())351	}352	/// @notice Find the owner of an NFT353	/// @dev NFTs assigned to zero address are considered invalid, and queries354	///  about them do throw.355	/// @param tokenId The identifier for an NFT356	/// @return The address of the owner of the NFT357	fn owner_of(&self, token_id: uint256) -> Result<address> {358		self.consume_store_reads(1)?;359		let token: TokenId = token_id.try_into()?;360		Ok(*<TokenData<T>>::get((self.id, token))361			.ok_or("token not found")?362			.owner363			.as_eth())364	}365	/// @dev Not implemented366	#[solidity(rename_selector = "safeTransferFrom")]367	fn safe_transfer_from_with_data(368		&mut self,369		_from: address,370		_to: address,371		_token_id: uint256,372		_data: bytes,373	) -> Result<void> {374		// TODO: Not implemetable375		Err("not implemented".into())376	}377	/// @dev Not implemented378	fn safe_transfer_from(379		&mut self,380		_from: address,381		_to: address,382		_token_id: uint256,383	) -> Result<void> {384		// TODO: Not implemetable385		Err("not implemented".into())386	}387388	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE389	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE390	///  THEY MAY BE PERMANENTLY LOST391	/// @dev Throws unless `msg.sender` is the current owner or an authorized392	///  operator for this NFT. Throws if `from` is not the current owner. Throws393	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.394	/// @param from The current owner of the NFT395	/// @param to The new owner396	/// @param tokenId The NFT to transfer397	#[weight(<SelfWeightOf<T>>::transfer_from())]398	fn transfer_from(399		&mut self,400		caller: caller,401		from: address,402		to: address,403		token_id: uint256,404	) -> Result<void> {405		let caller = T::CrossAccountId::from_eth(caller);406		let from = T::CrossAccountId::from_eth(from);407		let to = T::CrossAccountId::from_eth(to);408		let token = token_id.try_into()?;409		let budget = self410			.recorder411			.weight_calls_budget(<StructureWeight<T>>::find_parent());412413		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)414			.map_err(dispatch_to_evm::<T>)?;415		Ok(())416	}417418	/// @notice Set or reaffirm the approved address for an NFT419	/// @dev The zero address indicates there is no approved address.420	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized421	///  operator of the current owner.422	/// @param approved The new approved NFT controller423	/// @param tokenId The NFT to approve424	#[weight(<SelfWeightOf<T>>::approve())]425	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {426		let caller = T::CrossAccountId::from_eth(caller);427		let approved = T::CrossAccountId::from_eth(approved);428		let token = token_id.try_into()?;429430		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))431			.map_err(dispatch_to_evm::<T>)?;432		Ok(())433	}434435	/// @dev Not implemented436	fn set_approval_for_all(437		&mut self,438		_caller: caller,439		_operator: address,440		_approved: bool,441	) -> Result<void> {442		// TODO: Not implemetable443		Err("not implemented".into())444	}445446	/// @dev Not implemented447	fn get_approved(&self, _token_id: uint256) -> Result<address> {448		// TODO: Not implemetable449		Err("not implemented".into())450	}451452	/// @dev Not implemented453	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {454		// TODO: Not implemetable455		Err("not implemented".into())456	}457}458459/// @title ERC721 Token that can be irreversibly burned (destroyed).460#[solidity_interface(name = ERC721Burnable)]461impl<T: Config> NonfungibleHandle<T> {462	/// @notice Burns a specific ERC721 token.463	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized464	///  operator of the current owner.465	/// @param tokenId The NFT to approve466	#[weight(<SelfWeightOf<T>>::burn_item())]467	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {468		let caller = T::CrossAccountId::from_eth(caller);469		let token = token_id.try_into()?;470471		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;472		Ok(())473	}474}475476/// @title ERC721 minting logic.477#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]478impl<T: Config> NonfungibleHandle<T> {479	fn minting_finished(&self) -> Result<bool> {480		Ok(false)481	}482483	/// @notice Function to mint token.484	/// @param to The new owner485	/// @return uint256 The id of the newly minted token486	#[weight(<SelfWeightOf<T>>::create_item())]487	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {488		let token_id: uint256 = <TokensMinted<T>>::get(self.id)489			.checked_add(1)490			.ok_or("item id overflow")?491			.into();492		self.mint_check_id(caller, to, token_id)?;493		Ok(token_id)494	}495496	/// @notice Function to mint token.497	/// @dev `tokenId` should be obtained with `nextTokenId` method,498	///  unlike standard, you can't specify it manually499	/// @param to The new owner500	/// @param tokenId ID of the minted NFT501	#[solidity(hide, rename_selector = "mint")]502	#[weight(<SelfWeightOf<T>>::create_item())]503	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {504		let caller = T::CrossAccountId::from_eth(caller);505		let to = T::CrossAccountId::from_eth(to);506		let token_id: u32 = token_id.try_into()?;507		let budget = self508			.recorder509			.weight_calls_budget(<StructureWeight<T>>::find_parent());510511		if <TokensMinted<T>>::get(self.id)512			.checked_add(1)513			.ok_or("item id overflow")?514			!= token_id515		{516			return Err("item id should be next".into());517		}518519		<Pallet<T>>::create_item(520			self,521			&caller,522			CreateItemData::<T> {523				properties: BoundedVec::default(),524				owner: to,525			},526			&budget,527		)528		.map_err(dispatch_to_evm::<T>)?;529530		Ok(true)531	}532533	/// @notice Function to mint token with the given tokenUri.534	/// @param to The new owner535	/// @param tokenUri Token URI that would be stored in the NFT properties536	/// @return uint256 The id of the newly minted token537	#[solidity(rename_selector = "mintWithTokenURI")]538	#[weight(<SelfWeightOf<T>>::create_item())]539	fn mint_with_token_uri(540		&mut self,541		caller: caller,542		to: address,543		token_uri: string,544	) -> Result<uint256> {545		let token_id: uint256 = <TokensMinted<T>>::get(self.id)546			.checked_add(1)547			.ok_or("item id overflow")?548			.into();549		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;550		Ok(token_id)551	}552553	/// @notice Function to mint token with the given tokenUri.554	/// @dev `tokenId` should be obtained with `nextTokenId` method,555	///  unlike standard, you can't specify it manually556	/// @param to The new owner557	/// @param tokenId ID of the minted NFT558	/// @param tokenUri Token URI that would be stored in the NFT properties559	#[solidity(hide, rename_selector = "mintWithTokenURI")]560	#[weight(<SelfWeightOf<T>>::create_item())]561	fn mint_with_token_uri_check_id(562		&mut self,563		caller: caller,564		to: address,565		token_id: uint256,566		token_uri: string,567	) -> Result<bool> {568		let key = key::url();569		let permission = get_token_permission::<T>(self.id, &key)?;570		if !permission.collection_admin {571			return Err("Operation is not allowed".into());572		}573574		let caller = T::CrossAccountId::from_eth(caller);575		let to = T::CrossAccountId::from_eth(to);576		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;577		let budget = self578			.recorder579			.weight_calls_budget(<StructureWeight<T>>::find_parent());580581		if <TokensMinted<T>>::get(self.id)582			.checked_add(1)583			.ok_or("item id overflow")?584			!= token_id585		{586			return Err("item id should be next".into());587		}588589		let mut properties = CollectionPropertiesVec::default();590		properties591			.try_push(Property {592				key,593				value: token_uri594					.into_bytes()595					.try_into()596					.map_err(|_| "token uri is too long")?,597			})598			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;599600		<Pallet<T>>::create_item(601			self,602			&caller,603			CreateItemData::<T> {604				properties,605				owner: to,606			},607			&budget,608		)609		.map_err(dispatch_to_evm::<T>)?;610		Ok(true)611	}612613	/// @dev Not implemented614	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {615		Err("not implementable".into())616	}617}618619fn get_token_property<T: Config>(620	collection: &CollectionHandle<T>,621	token_id: u32,622	key: &up_data_structs::PropertyKey,623) -> Result<string> {624	collection.consume_store_reads(1)?;625	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))626		.map_err(|_| Error::Revert("Token properties not found".into()))?;627	if let Some(property) = properties.get(key) {628		return Ok(string::from_utf8_lossy(property).into());629	}630631	Err("Property tokenURI not found".into())632}633634fn get_token_permission<T: Config>(635	collection_id: CollectionId,636	key: &PropertyKey,637) -> Result<PropertyPermission> {638	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)639		.map_err(|_| Error::Revert("No permissions for collection".into()))?;640	let a = token_property_permissions641		.get(key)642		.map(Clone::clone)643		.ok_or_else(|| {644			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();645			Error::Revert(alloc::format!("No permission for key {}", key))646		})?;647	Ok(a)648}649650/// @title Unique extensions for ERC721.651#[solidity_interface(name = ERC721UniqueExtensions)]652impl<T: Config> NonfungibleHandle<T>653where654	T::AccountId: From<[u8; 32]>,655{656	/// @notice A descriptive name for a collection of NFTs in this contract657	fn name(&self) -> Result<string> {658		Ok(decode_utf16(self.name.iter().copied())659			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))660			.collect::<string>())661	}662663	/// @notice An abbreviated name for NFTs in this contract664	fn symbol(&self) -> Result<string> {665		Ok(string::from_utf8_lossy(&self.token_prefix).into())666	}667668	/// @notice Set or reaffirm the approved address for an NFT669	/// @dev The zero address indicates there is no approved address.670	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized671	///  operator of the current owner.672	/// @param approved The new substrate address approved NFT controller673	/// @param tokenId The NFT to approve674	#[weight(<SelfWeightOf<T>>::approve())]675	fn approve_cross(676		&mut self,677		caller: caller,678		approved: (address, uint256),679		token_id: uint256,680	) -> Result<void> {681		let caller = T::CrossAccountId::from_eth(caller);682		let approved = convert_tuple_to_cross_account::<T>(approved)?;683		let token = token_id.try_into()?;684685		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))686			.map_err(dispatch_to_evm::<T>)?;687		Ok(())688	}689690	/// @notice Transfer ownership of an NFT691	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`692	///  is the zero address. Throws if `tokenId` is not a valid NFT.693	/// @param to The new owner694	/// @param tokenId The NFT to transfer695	#[weight(<SelfWeightOf<T>>::transfer())]696	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {697		let caller = T::CrossAccountId::from_eth(caller);698		let to = T::CrossAccountId::from_eth(to);699		let token = token_id.try_into()?;700		let budget = self701			.recorder702			.weight_calls_budget(<StructureWeight<T>>::find_parent());703704		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;705		Ok(())706	}707708	/// @notice Transfer ownership of an NFT from cross account address to cross account address709	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`710	///  is the zero address. Throws if `tokenId` is not a valid NFT.711	/// @param from Cross acccount address of current owner712	/// @param to Cross acccount address of new owner713	/// @param tokenId The NFT to transfer714	#[weight(<SelfWeightOf<T>>::transfer())]715	fn transfer_from_cross(716		&mut self,717		caller: caller,718		from: EthCrossAccount,719		to: EthCrossAccount,720		token_id: uint256,721	) -> Result<void> {722		let caller = T::CrossAccountId::from_eth(caller);723		let from = from.into_sub_cross_account::<T>()?;724		let to = to.into_sub_cross_account::<T>()?;725		let token_id = token_id.try_into()?;726		let budget = self727			.recorder728			.weight_calls_budget(<StructureWeight<T>>::find_parent());729		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)730			.map_err(dispatch_to_evm::<T>)?;731		Ok(())732	}733734	/// @notice Burns a specific ERC721 token.735	/// @dev Throws unless `msg.sender` is the current owner or an authorized736	///  operator for this NFT. Throws if `from` is not the current owner. Throws737	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.738	/// @param from The current owner of the NFT739	/// @param tokenId The NFT to transfer740	#[weight(<SelfWeightOf<T>>::burn_from())]741	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {742		let caller = T::CrossAccountId::from_eth(caller);743		let from = T::CrossAccountId::from_eth(from);744		let token = token_id.try_into()?;745		let budget = self746			.recorder747			.weight_calls_budget(<StructureWeight<T>>::find_parent());748749		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)750			.map_err(dispatch_to_evm::<T>)?;751		Ok(())752	}753754	/// @notice Burns a specific ERC721 token.755	/// @dev Throws unless `msg.sender` is the current owner or an authorized756	///  operator for this NFT. Throws if `from` is not the current owner. Throws757	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.758	/// @param from The current owner of the NFT759	/// @param tokenId The NFT to transfer760	#[weight(<SelfWeightOf<T>>::burn_from())]761	fn burn_from_cross(762		&mut self,763		caller: caller,764		from: (address, uint256),765		token_id: uint256,766	) -> Result<void> {767		let caller = T::CrossAccountId::from_eth(caller);768		let from = convert_tuple_to_cross_account::<T>(from)?;769		let token = token_id.try_into()?;770		let budget = self771			.recorder772			.weight_calls_budget(<StructureWeight<T>>::find_parent());773774		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)775			.map_err(dispatch_to_evm::<T>)?;776		Ok(())777	}778779	/// @notice Returns next free NFT ID.780	fn next_token_id(&self) -> Result<uint256> {781		self.consume_store_reads(1)?;782		Ok(<TokensMinted<T>>::get(self.id)783			.checked_add(1)784			.ok_or("item id overflow")?785			.into())786	}787788	/// @notice Function to mint multiple tokens.789	/// @dev `tokenIds` should be an array of consecutive numbers and first number790	///  should be obtained with `nextTokenId` method791	/// @param to The new owner792	/// @param tokenIds IDs of the minted NFTs793	#[solidity(hide)]794	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]795	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {796		let caller = T::CrossAccountId::from_eth(caller);797		let to = T::CrossAccountId::from_eth(to);798		let mut expected_index = <TokensMinted<T>>::get(self.id)799			.checked_add(1)800			.ok_or("item id overflow")?;801		let budget = self802			.recorder803			.weight_calls_budget(<StructureWeight<T>>::find_parent());804805		let total_tokens = token_ids.len();806		for id in token_ids.into_iter() {807			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;808			if id != expected_index {809				return Err("item id should be next".into());810			}811			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;812		}813		let data = (0..total_tokens)814			.map(|_| CreateItemData::<T> {815				properties: BoundedVec::default(),816				owner: to.clone(),817			})818			.collect();819820		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)821			.map_err(dispatch_to_evm::<T>)?;822		Ok(true)823	}824825	/// @notice Function to mint multiple tokens with the given tokenUris.826	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive827	///  numbers and first number should be obtained with `nextTokenId` method828	/// @param to The new owner829	/// @param tokens array of pairs of token ID and token URI for minted tokens830	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]831	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]832	fn mint_bulk_with_token_uri(833		&mut self,834		caller: caller,835		to: address,836		tokens: Vec<(uint256, string)>,837	) -> Result<bool> {838		let key = key::url();839		let caller = T::CrossAccountId::from_eth(caller);840		let to = T::CrossAccountId::from_eth(to);841		let mut expected_index = <TokensMinted<T>>::get(self.id)842			.checked_add(1)843			.ok_or("item id overflow")?;844		let budget = self845			.recorder846			.weight_calls_budget(<StructureWeight<T>>::find_parent());847848		let mut data = Vec::with_capacity(tokens.len());849		for (id, token_uri) in tokens {850			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;851			if id != expected_index {852				return Err("item id should be next".into());853			}854			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;855856			let mut properties = CollectionPropertiesVec::default();857			properties858				.try_push(Property {859					key: key.clone(),860					value: token_uri861						.into_bytes()862						.try_into()863						.map_err(|_| "token uri is too long")?,864				})865				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;866867			data.push(CreateItemData::<T> {868				properties,869				owner: to.clone(),870			});871		}872873		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)874			.map_err(dispatch_to_evm::<T>)?;875		Ok(true)876	}877}878879#[solidity_interface(880	name = UniqueNFT,881	is(882		ERC721,883		ERC721Enumerable,884		ERC721UniqueExtensions,885		ERC721UniqueMintable,886		ERC721Burnable,887		ERC721Metadata(if(this.flags.erc721metadata)),888		Collection(via(common_mut returns CollectionHandle<T>)),889		TokenProperties,890	)891)]892impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}893894// Not a tests, but code generators895generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);896generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);897898impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>899where900	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,901{902	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");903904	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {905		call::<T, UniqueNFTCall<T>, _, _>(handle, self)906	}907}
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;23use alloc::{format, string::ToString};24use core::{25	char::{REPLACEMENT_CHARACTER, decode_utf16},26	convert::TryInto,27};28use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};29use frame_support::BoundedVec;30use up_data_structs::{31	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,32	CollectionPropertiesVec,33};34use pallet_evm_coder_substrate::dispatch_to_evm;35use sp_std::vec::Vec;36use pallet_common::{37	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},38	CollectionHandle, CollectionPropertyPermissions,39	eth::convert_tuple_to_cross_account,40};41use pallet_evm::{account::CrossAccountId, PrecompileHandle};42use pallet_evm_coder_substrate::call;43use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4445use crate::{46	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,47	SelfWeightOf, weights::WeightInfo, TokenProperties,48};4950/// @title A contract that allows to set and delete token properties and change token property permissions.51#[solidity_interface(name = TokenProperties)]52impl<T: Config> NonfungibleHandle<T> {53	/// @notice Set permissions for token property.54	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.55	/// @param key Property key.56	/// @param isMutable Permission to mutate property.57	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.58	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.59	fn set_token_property_permission(60		&mut self,61		caller: caller,62		key: string,63		is_mutable: bool,64		collection_admin: bool,65		token_owner: bool,66	) -> Result<()> {67		let caller = T::CrossAccountId::from_eth(caller);68		<Pallet<T>>::set_property_permission(69			self,70			&caller,71			PropertyKeyPermission {72				key: <Vec<u8>>::from(key)73					.try_into()74					.map_err(|_| "too long key")?,75				permission: PropertyPermission {76					mutable: is_mutable,77					collection_admin,78					token_owner,79				},80			},81		)82		.map_err(dispatch_to_evm::<T>)83	}8485	/// @notice Set token property value.86	/// @dev Throws error if `msg.sender` has no permission to edit the property.87	/// @param tokenId ID of the token.88	/// @param key Property key.89	/// @param value Property value.90	fn set_property(91		&mut self,92		caller: caller,93		token_id: uint256,94		key: string,95		value: bytes,96	) -> Result<()> {97		let caller = T::CrossAccountId::from_eth(caller);98		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;99		let key = <Vec<u8>>::from(key)100			.try_into()101			.map_err(|_| "key too long")?;102		let value = value.0.try_into().map_err(|_| "value too long")?;103104		let nesting_budget = self105			.recorder106			.weight_calls_budget(<StructureWeight<T>>::find_parent());107108		<Pallet<T>>::set_token_property(109			self,110			&caller,111			TokenId(token_id),112			Property { key, value },113			&nesting_budget,114		)115		.map_err(dispatch_to_evm::<T>)116	}117118	/// @notice Set token properties value.119	/// @dev Throws error if `msg.sender` has no permission to edit the property.120	/// @param tokenId ID of the token.121	/// @param properties settable properties122	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]123	fn set_properties(124		&mut self,125		caller: caller,126		token_id: uint256,127		properties: Vec<(string, bytes)>,128	) -> Result<()> {129		let caller = T::CrossAccountId::from_eth(caller);130		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;131132		let nesting_budget = self133			.recorder134			.weight_calls_budget(<StructureWeight<T>>::find_parent());135136		let properties = properties137			.into_iter()138			.map(|(key, value)| {139				let key = <Vec<u8>>::from(key)140					.try_into()141					.map_err(|_| "key too large")?;142143				let value = value.0.try_into().map_err(|_| "value too large")?;144145				Ok(Property { key, value })146			})147			.collect::<Result<Vec<_>>>()?;148149		<Pallet<T>>::set_token_properties(150			self,151			&caller,152			TokenId(token_id),153			properties.into_iter(),154			<Pallet<T>>::token_exists(&self, TokenId(token_id)),155			&nesting_budget,156		)157		.map_err(dispatch_to_evm::<T>)158	}159160	/// @notice Delete token property value.161	/// @dev Throws error if `msg.sender` has no permission to edit the property.162	/// @param tokenId ID of the token.163	/// @param key Property key.164	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {165		let caller = T::CrossAccountId::from_eth(caller);166		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;167		let key = <Vec<u8>>::from(key)168			.try_into()169			.map_err(|_| "key too long")?;170171		let nesting_budget = self172			.recorder173			.weight_calls_budget(<StructureWeight<T>>::find_parent());174175		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)176			.map_err(dispatch_to_evm::<T>)177	}178179	/// @notice Get token property value.180	/// @dev Throws error if key not found181	/// @param tokenId ID of the token.182	/// @param key Property key.183	/// @return Property value bytes184	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {185		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;186		let key = <Vec<u8>>::from(key)187			.try_into()188			.map_err(|_| "key too long")?;189190		let props = <TokenProperties<T>>::get((self.id, token_id));191		let prop = props.get(&key).ok_or("key not found")?;192193		Ok(prop.to_vec().into())194	}195}196197#[derive(ToLog)]198pub enum ERC721Events {199	/// @dev This emits when ownership of any NFT changes by any mechanism.200	///  This event emits when NFTs are created (`from` == 0) and destroyed201	///  (`to` == 0). Exception: during contract creation, any number of NFTs202	///  may be created and assigned without emitting Transfer. At the time of203	///  any transfer, the approved address for that NFT (if any) is reset to none.204	Transfer {205		#[indexed]206		from: address,207		#[indexed]208		to: address,209		#[indexed]210		token_id: uint256,211	},212	/// @dev This emits when the approved address for an NFT is changed or213	///  reaffirmed. The zero address indicates there is no approved address.214	///  When a Transfer event emits, this also indicates that the approved215	///  address for that NFT (if any) is reset to none.216	Approval {217		#[indexed]218		owner: address,219		#[indexed]220		approved: address,221		#[indexed]222		token_id: uint256,223	},224	/// @dev This emits when an operator is enabled or disabled for an owner.225	///  The operator can manage all NFTs of the owner.226	#[allow(dead_code)]227	ApprovalForAll {228		#[indexed]229		owner: address,230		#[indexed]231		operator: address,232		approved: bool,233	},234}235236#[derive(ToLog)]237pub enum ERC721UniqueMintableEvents {238	#[allow(dead_code)]239	MintingFinished {},240}241242/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension243/// @dev See https://eips.ethereum.org/EIPS/eip-721244#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]245impl<T: Config> NonfungibleHandle<T>246where247	T::AccountId: From<[u8; 32]>,248{249	/// @notice A descriptive name for a collection of NFTs in this contract250	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`251	#[solidity(hide, rename_selector = "name")]252	fn name_proxy(&self) -> Result<string> {253		self.name()254	}255256	/// @notice An abbreviated name for NFTs in this contract257	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`258	#[solidity(hide, rename_selector = "symbol")]259	fn symbol_proxy(&self) -> Result<string> {260		self.symbol()261	}262263	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.264	///265	/// @dev If the token has a `url` property and it is not empty, it is returned.266	///  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`.267	///  If the collection property `baseURI` is empty or absent, return "" (empty string)268	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix269	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).270	///271	/// @return token's const_metadata272	#[solidity(rename_selector = "tokenURI")]273	fn token_uri(&self, token_id: uint256) -> Result<string> {274		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;275276		match get_token_property(self, token_id_u32, &key::url()).as_deref() {277			Err(_) | Ok("") => (),278			Ok(url) => {279				return Ok(url.into());280			}281		};282283		let base_uri =284			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())285				.map(BoundedVec::into_inner)286				.map(string::from_utf8)287				.transpose()288				.map_err(|e| {289					Error::Revert(alloc::format!(290						"Can not convert value \"baseURI\" to string with error \"{}\"",291						e292					))293				})?;294295		let base_uri = match base_uri.as_deref() {296			None | Some("") => {297				return Ok("".into());298			}299			Some(base_uri) => base_uri.into(),300		};301302		Ok(303			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {304				Err(_) | Ok("") => base_uri,305				Ok(suffix) => base_uri + suffix,306			},307		)308	}309}310311/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension312/// @dev See https://eips.ethereum.org/EIPS/eip-721313#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]314impl<T: Config> NonfungibleHandle<T> {315	/// @notice Enumerate valid NFTs316	/// @param index A counter less than `totalSupply()`317	/// @return The token identifier for the `index`th NFT,318	///  (sort order not specified)319	fn token_by_index(&self, index: uint256) -> Result<uint256> {320		Ok(index)321	}322323	/// @dev Not implemented324	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {325		// TODO: Not implemetable326		Err("not implemented".into())327	}328329	/// @notice Count NFTs tracked by this contract330	/// @return A count of valid NFTs tracked by this contract, where each one of331	///  them has an assigned and queryable owner not equal to the zero address332	fn total_supply(&self) -> Result<uint256> {333		self.consume_store_reads(1)?;334		Ok(<Pallet<T>>::total_supply(self).into())335	}336}337338/// @title ERC-721 Non-Fungible Token Standard339/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md340#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]341impl<T: Config> NonfungibleHandle<T> {342	/// @notice Count all NFTs assigned to an owner343	/// @dev NFTs assigned to the zero address are considered invalid, and this344	///  function throws for queries about the zero address.345	/// @param owner An address for whom to query the balance346	/// @return The number of NFTs owned by `owner`, possibly zero347	fn balance_of(&self, owner: address) -> Result<uint256> {348		self.consume_store_reads(1)?;349		let owner = T::CrossAccountId::from_eth(owner);350		let balance = <AccountBalance<T>>::get((self.id, owner));351		Ok(balance.into())352	}353	/// @notice Find the owner of an NFT354	/// @dev NFTs assigned to zero address are considered invalid, and queries355	///  about them do throw.356	/// @param tokenId The identifier for an NFT357	/// @return The address of the owner of the NFT358	fn owner_of(&self, token_id: uint256) -> Result<address> {359		self.consume_store_reads(1)?;360		let token: TokenId = token_id.try_into()?;361		Ok(*<TokenData<T>>::get((self.id, token))362			.ok_or("token not found")?363			.owner364			.as_eth())365	}366	/// @dev Not implemented367	#[solidity(rename_selector = "safeTransferFrom")]368	fn safe_transfer_from_with_data(369		&mut self,370		_from: address,371		_to: address,372		_token_id: uint256,373		_data: bytes,374	) -> Result<void> {375		// TODO: Not implemetable376		Err("not implemented".into())377	}378	/// @dev Not implemented379	fn safe_transfer_from(380		&mut self,381		_from: address,382		_to: address,383		_token_id: uint256,384	) -> Result<void> {385		// TODO: Not implemetable386		Err("not implemented".into())387	}388389	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE390	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE391	///  THEY MAY BE PERMANENTLY LOST392	/// @dev Throws unless `msg.sender` is the current owner or an authorized393	///  operator for this NFT. Throws if `from` is not the current owner. Throws394	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.395	/// @param from The current owner of the NFT396	/// @param to The new owner397	/// @param tokenId The NFT to transfer398	#[weight(<SelfWeightOf<T>>::transfer_from())]399	fn transfer_from(400		&mut self,401		caller: caller,402		from: address,403		to: address,404		token_id: uint256,405	) -> Result<void> {406		let caller = T::CrossAccountId::from_eth(caller);407		let from = T::CrossAccountId::from_eth(from);408		let to = T::CrossAccountId::from_eth(to);409		let token = token_id.try_into()?;410		let budget = self411			.recorder412			.weight_calls_budget(<StructureWeight<T>>::find_parent());413414		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)415			.map_err(dispatch_to_evm::<T>)?;416		Ok(())417	}418419	/// @notice Set or reaffirm the approved address for an NFT420	/// @dev The zero address indicates there is no approved address.421	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized422	///  operator of the current owner.423	/// @param approved The new approved NFT controller424	/// @param tokenId The NFT to approve425	#[weight(<SelfWeightOf<T>>::approve())]426	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {427		let caller = T::CrossAccountId::from_eth(caller);428		let approved = T::CrossAccountId::from_eth(approved);429		let token = token_id.try_into()?;430431		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))432			.map_err(dispatch_to_evm::<T>)?;433		Ok(())434	}435436	/// @dev Not implemented437	fn set_approval_for_all(438		&mut self,439		_caller: caller,440		_operator: address,441		_approved: bool,442	) -> Result<void> {443		// TODO: Not implemetable444		Err("not implemented".into())445	}446447	/// @dev Not implemented448	fn get_approved(&self, _token_id: uint256) -> Result<address> {449		// TODO: Not implemetable450		Err("not implemented".into())451	}452453	/// @dev Not implemented454	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455		// TODO: Not implemetable456		Err("not implemented".into())457	}458}459460/// @title ERC721 Token that can be irreversibly burned (destroyed).461#[solidity_interface(name = ERC721Burnable)]462impl<T: Config> NonfungibleHandle<T> {463	/// @notice Burns a specific ERC721 token.464	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized465	///  operator of the current owner.466	/// @param tokenId The NFT to approve467	#[weight(<SelfWeightOf<T>>::burn_item())]468	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {469		let caller = T::CrossAccountId::from_eth(caller);470		let token = token_id.try_into()?;471472		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;473		Ok(())474	}475}476477/// @title ERC721 minting logic.478#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]479impl<T: Config> NonfungibleHandle<T> {480	fn minting_finished(&self) -> Result<bool> {481		Ok(false)482	}483484	/// @notice Function to mint token.485	/// @param to The new owner486	/// @return uint256 The id of the newly minted token487	#[weight(<SelfWeightOf<T>>::create_item())]488	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {489		let token_id: uint256 = <TokensMinted<T>>::get(self.id)490			.checked_add(1)491			.ok_or("item id overflow")?492			.into();493		self.mint_check_id(caller, to, token_id)?;494		Ok(token_id)495	}496497	/// @notice Function to mint token.498	/// @dev `tokenId` should be obtained with `nextTokenId` method,499	///  unlike standard, you can't specify it manually500	/// @param to The new owner501	/// @param tokenId ID of the minted NFT502	#[solidity(hide, rename_selector = "mint")]503	#[weight(<SelfWeightOf<T>>::create_item())]504	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {505		let caller = T::CrossAccountId::from_eth(caller);506		let to = T::CrossAccountId::from_eth(to);507		let token_id: u32 = token_id.try_into()?;508		let budget = self509			.recorder510			.weight_calls_budget(<StructureWeight<T>>::find_parent());511512		if <TokensMinted<T>>::get(self.id)513			.checked_add(1)514			.ok_or("item id overflow")?515			!= token_id516		{517			return Err("item id should be next".into());518		}519520		<Pallet<T>>::create_item(521			self,522			&caller,523			CreateItemData::<T> {524				properties: BoundedVec::default(),525				owner: to,526			},527			&budget,528		)529		.map_err(dispatch_to_evm::<T>)?;530531		Ok(true)532	}533534	/// @notice Function to mint token with the given tokenUri.535	/// @param to The new owner536	/// @param tokenUri Token URI that would be stored in the NFT properties537	/// @return uint256 The id of the newly minted token538	#[solidity(rename_selector = "mintWithTokenURI")]539	#[weight(<SelfWeightOf<T>>::create_item())]540	fn mint_with_token_uri(541		&mut self,542		caller: caller,543		to: address,544		token_uri: string,545	) -> Result<uint256> {546		let token_id: uint256 = <TokensMinted<T>>::get(self.id)547			.checked_add(1)548			.ok_or("item id overflow")?549			.into();550		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;551		Ok(token_id)552	}553554	/// @notice Function to mint token with the given tokenUri.555	/// @dev `tokenId` should be obtained with `nextTokenId` method,556	///  unlike standard, you can't specify it manually557	/// @param to The new owner558	/// @param tokenId ID of the minted NFT559	/// @param tokenUri Token URI that would be stored in the NFT properties560	#[solidity(hide, rename_selector = "mintWithTokenURI")]561	#[weight(<SelfWeightOf<T>>::create_item())]562	fn mint_with_token_uri_check_id(563		&mut self,564		caller: caller,565		to: address,566		token_id: uint256,567		token_uri: string,568	) -> Result<bool> {569		let key = key::url();570		let permission = get_token_permission::<T>(self.id, &key)?;571		if !permission.collection_admin {572			return Err("Operation is not allowed".into());573		}574575		let caller = T::CrossAccountId::from_eth(caller);576		let to = T::CrossAccountId::from_eth(to);577		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;578		let budget = self579			.recorder580			.weight_calls_budget(<StructureWeight<T>>::find_parent());581582		if <TokensMinted<T>>::get(self.id)583			.checked_add(1)584			.ok_or("item id overflow")?585			!= token_id586		{587			return Err("item id should be next".into());588		}589590		let mut properties = CollectionPropertiesVec::default();591		properties592			.try_push(Property {593				key,594				value: token_uri595					.into_bytes()596					.try_into()597					.map_err(|_| "token uri is too long")?,598			})599			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;600601		<Pallet<T>>::create_item(602			self,603			&caller,604			CreateItemData::<T> {605				properties,606				owner: to,607			},608			&budget,609		)610		.map_err(dispatch_to_evm::<T>)?;611		Ok(true)612	}613614	/// @dev Not implemented615	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {616		Err("not implementable".into())617	}618}619620fn get_token_property<T: Config>(621	collection: &CollectionHandle<T>,622	token_id: u32,623	key: &up_data_structs::PropertyKey,624) -> Result<string> {625	collection.consume_store_reads(1)?;626	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))627		.map_err(|_| Error::Revert("Token properties not found".into()))?;628	if let Some(property) = properties.get(key) {629		return Ok(string::from_utf8_lossy(property).into());630	}631632	Err("Property tokenURI not found".into())633}634635fn get_token_permission<T: Config>(636	collection_id: CollectionId,637	key: &PropertyKey,638) -> Result<PropertyPermission> {639	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)640		.map_err(|_| Error::Revert("No permissions for collection".into()))?;641	let a = token_property_permissions642		.get(key)643		.map(Clone::clone)644		.ok_or_else(|| {645			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();646			Error::Revert(alloc::format!("No permission for key {}", key))647		})?;648	Ok(a)649}650651/// @title Unique extensions for ERC721.652#[solidity_interface(name = ERC721UniqueExtensions)]653impl<T: Config> NonfungibleHandle<T>654where655	T::AccountId: From<[u8; 32]>,656{657	/// @notice A descriptive name for a collection of NFTs in this contract658	fn name(&self) -> Result<string> {659		Ok(decode_utf16(self.name.iter().copied())660			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))661			.collect::<string>())662	}663664	/// @notice An abbreviated name for NFTs in this contract665	fn symbol(&self) -> Result<string> {666		Ok(string::from_utf8_lossy(&self.token_prefix).into())667	}668669	/// @notice Set or reaffirm the approved address for an NFT670	/// @dev The zero address indicates there is no approved address.671	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized672	///  operator of the current owner.673	/// @param approved The new substrate address approved NFT controller674	/// @param tokenId The NFT to approve675	#[weight(<SelfWeightOf<T>>::approve())]676	fn approve_cross(677		&mut self,678		caller: caller,679		approved: (address, uint256),680		token_id: uint256,681	) -> Result<void> {682		let caller = T::CrossAccountId::from_eth(caller);683		let approved = convert_tuple_to_cross_account::<T>(approved)?;684		let token = token_id.try_into()?;685686		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))687			.map_err(dispatch_to_evm::<T>)?;688		Ok(())689	}690691	/// @notice Transfer ownership of an NFT692	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`693	///  is the zero address. Throws if `tokenId` is not a valid NFT.694	/// @param to The new owner695	/// @param tokenId The NFT to transfer696	#[weight(<SelfWeightOf<T>>::transfer())]697	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {698		let caller = T::CrossAccountId::from_eth(caller);699		let to = T::CrossAccountId::from_eth(to);700		let token = token_id.try_into()?;701		let budget = self702			.recorder703			.weight_calls_budget(<StructureWeight<T>>::find_parent());704705		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;706		Ok(())707	}708709	/// @notice Transfer ownership of an NFT from cross account address to cross account address710	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`711	///  is the zero address. Throws if `tokenId` is not a valid NFT.712	/// @param from Cross acccount address of current owner713	/// @param to Cross acccount address of new owner714	/// @param tokenId The NFT to transfer715	#[weight(<SelfWeightOf<T>>::transfer())]716	fn transfer_from_cross(717		&mut self,718		caller: caller,719		from: EthCrossAccount,720		to: EthCrossAccount,721		token_id: uint256,722	) -> Result<void> {723		let caller = T::CrossAccountId::from_eth(caller);724		let from = from.into_sub_cross_account::<T>()?;725		let to = to.into_sub_cross_account::<T>()?;726		let token_id = token_id.try_into()?;727		let budget = self728			.recorder729			.weight_calls_budget(<StructureWeight<T>>::find_parent());730		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)731			.map_err(dispatch_to_evm::<T>)?;732		Ok(())733	}734735	/// @notice Burns a specific ERC721 token.736	/// @dev Throws unless `msg.sender` is the current owner or an authorized737	///  operator for this NFT. Throws if `from` is not the current owner. Throws738	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.739	/// @param from The current owner of the NFT740	/// @param tokenId The NFT to transfer741	#[weight(<SelfWeightOf<T>>::burn_from())]742	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {743		let caller = T::CrossAccountId::from_eth(caller);744		let from = T::CrossAccountId::from_eth(from);745		let token = token_id.try_into()?;746		let budget = self747			.recorder748			.weight_calls_budget(<StructureWeight<T>>::find_parent());749750		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)751			.map_err(dispatch_to_evm::<T>)?;752		Ok(())753	}754755	/// @notice Burns a specific ERC721 token.756	/// @dev Throws unless `msg.sender` is the current owner or an authorized757	///  operator for this NFT. Throws if `from` is not the current owner. Throws758	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.759	/// @param from The current owner of the NFT760	/// @param tokenId The NFT to transfer761	#[weight(<SelfWeightOf<T>>::burn_from())]762	fn burn_from_cross(763		&mut self,764		caller: caller,765		from: (address, uint256),766		token_id: uint256,767	) -> Result<void> {768		let caller = T::CrossAccountId::from_eth(caller);769		let from = convert_tuple_to_cross_account::<T>(from)?;770		let token = token_id.try_into()?;771		let budget = self772			.recorder773			.weight_calls_budget(<StructureWeight<T>>::find_parent());774775		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)776			.map_err(dispatch_to_evm::<T>)?;777		Ok(())778	}779780	/// @notice Returns next free NFT ID.781	fn next_token_id(&self) -> Result<uint256> {782		self.consume_store_reads(1)?;783		Ok(<TokensMinted<T>>::get(self.id)784			.checked_add(1)785			.ok_or("item id overflow")?786			.into())787	}788789	/// @notice Function to mint multiple tokens.790	/// @dev `tokenIds` should be an array of consecutive numbers and first number791	///  should be obtained with `nextTokenId` method792	/// @param to The new owner793	/// @param tokenIds IDs of the minted NFTs794	#[solidity(hide)]795	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]796	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {797		let caller = T::CrossAccountId::from_eth(caller);798		let to = T::CrossAccountId::from_eth(to);799		let mut expected_index = <TokensMinted<T>>::get(self.id)800			.checked_add(1)801			.ok_or("item id overflow")?;802		let budget = self803			.recorder804			.weight_calls_budget(<StructureWeight<T>>::find_parent());805806		let total_tokens = token_ids.len();807		for id in token_ids.into_iter() {808			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;809			if id != expected_index {810				return Err("item id should be next".into());811			}812			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;813		}814		let data = (0..total_tokens)815			.map(|_| CreateItemData::<T> {816				properties: BoundedVec::default(),817				owner: to.clone(),818			})819			.collect();820821		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)822			.map_err(dispatch_to_evm::<T>)?;823		Ok(true)824	}825826	/// @notice Function to mint multiple tokens with the given tokenUris.827	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive828	///  numbers and first number should be obtained with `nextTokenId` method829	/// @param to The new owner830	/// @param tokens array of pairs of token ID and token URI for minted tokens831	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]832	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]833	fn mint_bulk_with_token_uri(834		&mut self,835		caller: caller,836		to: address,837		tokens: Vec<(uint256, string)>,838	) -> Result<bool> {839		let key = key::url();840		let caller = T::CrossAccountId::from_eth(caller);841		let to = T::CrossAccountId::from_eth(to);842		let mut expected_index = <TokensMinted<T>>::get(self.id)843			.checked_add(1)844			.ok_or("item id overflow")?;845		let budget = self846			.recorder847			.weight_calls_budget(<StructureWeight<T>>::find_parent());848849		let mut data = Vec::with_capacity(tokens.len());850		for (id, token_uri) in tokens {851			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;852			if id != expected_index {853				return Err("item id should be next".into());854			}855			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;856857			let mut properties = CollectionPropertiesVec::default();858			properties859				.try_push(Property {860					key: key.clone(),861					value: token_uri862						.into_bytes()863						.try_into()864						.map_err(|_| "token uri is too long")?,865				})866				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;867868			data.push(CreateItemData::<T> {869				properties,870				owner: to.clone(),871			});872		}873874		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)875			.map_err(dispatch_to_evm::<T>)?;876		Ok(true)877	}878}879880#[solidity_interface(881	name = UniqueNFT,882	is(883		ERC721,884		ERC721Enumerable,885		ERC721UniqueExtensions,886		ERC721UniqueMintable,887		ERC721Burnable,888		ERC721Metadata(if(this.flags.erc721metadata)),889		Collection(via(common_mut returns CollectionHandle<T>)),890		TokenProperties,891	)892)]893impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}894895// Not a tests, but code generators896generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);897generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);898899impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>900where901	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,902{903	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");904905	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {906		call::<T, UniqueNFTCall<T>, _, _>(handle, self)907	}908}
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -26,7 +26,9 @@
 struct-versioning = { path = "../../crates/struct-versioning" }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 ethereum = { version = "0.12.0", default-features = false }
-scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+scale-info = { version = "2.0.1", default-features = false, features = [
+    "derive",
+] }
 derivative = { version = "2.2.0", features = ["use_core"] }
 
 [features]
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -21,6 +21,7 @@
 
 extern crate alloc;
 
+use alloc::{format, string::ToString};
 use core::{
 	char::{REPLACEMENT_CHARACTER, decode_utf16},
 	convert::TryInto,