git.delta.rocks / unique-network / refs/commits / 7bb598f2cac3

difftreelog

CORE-325 Fix metadata name and symbol

Trubnikov Sergey2022-04-07parent: #2694afe.patch.diff
in: master

2 files changed

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/>.1617extern crate alloc;18use core::{19	char::{REPLACEMENT_CHARACTER, decode_utf16},20	convert::TryInto,21};22use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};23use frame_support::BoundedVec;24use up_data_structs::{TokenId, SchemaVersion};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_core::{H160, U256};27use sp_std::{vec::Vec, vec};28use pallet_common::{29	account::CrossAccountId,30	erc::{CommonEvmHandler, PrecompileResult},31};32use pallet_evm_coder_substrate::call;3334use crate::{35	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,36	SelfWeightOf, weights::WeightInfo,37};3839fn error_unsupported_shema_version() -> Error {40	alloc::format!("Unsupported shema version! Support only {:?}", SchemaVersion::ImageURL).as_str().into()41}4243#[derive(ToLog)]44pub enum ERC721Events {45	Transfer {46		#[indexed]47		from: address,48		#[indexed]49		to: address,50		#[indexed]51		token_id: uint256,52	},53	Approval {54		#[indexed]55		owner: address,56		#[indexed]57		approved: address,58		#[indexed]59		token_id: uint256,60	},61	#[allow(dead_code)]62	ApprovalForAll {63		#[indexed]64		owner: address,65		#[indexed]66		operator: address,67		approved: bool,68	},69}7071#[derive(ToLog)]72pub enum ERC721MintableEvents {73	#[allow(dead_code)]74	MintingFinished {},75}7677#[solidity_interface(name = "ERC721Metadata")]78impl<T: Config> NonfungibleHandle<T> {79	fn name(&self) -> Result<string> {80		if let SchemaVersion::ImageURL = self.schema_version {81			Ok(decode_utf16(self.name.iter().copied())82				.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))83				.collect::<string>())84        } else {85            Err(error_unsupported_shema_version())86        }87	}88	fn symbol(&self) -> Result<string> {89		if let SchemaVersion::ImageURL = self.schema_version {90			Ok(string::from_utf8_lossy(&self.token_prefix).into())91        } else {92            Err(error_unsupported_shema_version())93        }94	}9596	/// Returns token's const_metadata97	#[solidity(rename_selector = "tokenURI")]98	fn token_uri(&self, token_id: uint256) -> Result<string> {99		if let SchemaVersion::ImageURL = self.schema_version {100			self.consume_store_reads(1)?;101			let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102			Ok(string::from_utf8_lossy(103				&<TokenData<T>>::get((self.id, token_id))104					.ok_or("token not found")?105					.const_data,106			)107			.into())108        } else {109            Err(error_unsupported_shema_version())110        }111	}112}113114#[solidity_interface(name = "ERC721Enumerable")]115impl<T: Config> NonfungibleHandle<T> {116	fn token_by_index(&self, index: uint256) -> Result<uint256> {117		Ok(index)118	}119120	/// Not implemented121	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {122		// TODO: Not implemetable123		Err("not implemented".into())124	}125126	fn total_supply(&self) -> Result<uint256> {127		self.consume_store_reads(1)?;128		Ok(<Pallet<T>>::total_supply(self).into())129	}130}131132#[solidity_interface(name = "ERC721", events(ERC721Events))]133impl<T: Config> NonfungibleHandle<T> {134	fn balance_of(&self, owner: address) -> Result<uint256> {135		self.consume_store_reads(1)?;136		let owner = T::CrossAccountId::from_eth(owner);137		let balance = <AccountBalance<T>>::get((self.id, owner));138		Ok(balance.into())139	}140	fn owner_of(&self, token_id: uint256) -> Result<address> {141		self.consume_store_reads(1)?;142		let token: TokenId = token_id.try_into()?;143		Ok(*<TokenData<T>>::get((self.id, token))144			.ok_or("token not found")?145			.owner146			.as_eth())147	}148	/// Not implemented149	fn safe_transfer_from_with_data(150		&mut self,151		_from: address,152		_to: address,153		_token_id: uint256,154		_data: bytes,155		_value: value,156	) -> Result<void> {157		// TODO: Not implemetable158		Err("not implemented".into())159	}160	/// Not implemented161	fn safe_transfer_from(162		&mut self,163		_from: address,164		_to: address,165		_token_id: uint256,166		_value: value,167	) -> Result<void> {168		// TODO: Not implemetable169		Err("not implemented".into())170	}171172	#[weight(<SelfWeightOf<T>>::transfer_from())]173	fn transfer_from(174		&mut self,175		caller: caller,176		from: address,177		to: address,178		token_id: uint256,179		_value: value,180	) -> Result<void> {181		let caller = T::CrossAccountId::from_eth(caller);182		let from = T::CrossAccountId::from_eth(from);183		let to = T::CrossAccountId::from_eth(to);184		let token = token_id.try_into()?;185186		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)187			.map_err(dispatch_to_evm::<T>)?;188		Ok(())189	}190191	#[weight(<SelfWeightOf<T>>::approve())]192	fn approve(193		&mut self,194		caller: caller,195		approved: address,196		token_id: uint256,197		_value: value,198	) -> Result<void> {199		let caller = T::CrossAccountId::from_eth(caller);200		let approved = T::CrossAccountId::from_eth(approved);201		let token = token_id.try_into()?;202203		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))204			.map_err(dispatch_to_evm::<T>)?;205		Ok(())206	}207208	/// Not implemented209	fn set_approval_for_all(210		&mut self,211		_caller: caller,212		_operator: address,213		_approved: bool,214	) -> Result<void> {215		// TODO: Not implemetable216		Err("not implemented".into())217	}218219	/// Not implemented220	fn get_approved(&self, _token_id: uint256) -> Result<address> {221		// TODO: Not implemetable222		Err("not implemented".into())223	}224225	/// Not implemented226	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {227		// TODO: Not implemetable228		Err("not implemented".into())229	}230}231232#[solidity_interface(name = "ERC721Burnable")]233impl<T: Config> NonfungibleHandle<T> {234	#[weight(<SelfWeightOf<T>>::burn_item())]235	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {236		let caller = T::CrossAccountId::from_eth(caller);237		let token = token_id.try_into()?;238239		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;240		Ok(())241	}242}243244#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]245impl<T: Config> NonfungibleHandle<T> {246	fn minting_finished(&self) -> Result<bool> {247		Ok(false)248	}249250	/// `token_id` should be obtained with `next_token_id` method,251	/// unlike standard, you can't specify it manually252	#[weight(<SelfWeightOf<T>>::create_item())]253	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {254		let caller = T::CrossAccountId::from_eth(caller);255		let to = T::CrossAccountId::from_eth(to);256		let token_id: u32 = token_id.try_into()?;257		if <TokensMinted<T>>::get(self.id)258			.checked_add(1)259			.ok_or("item id overflow")?260			!= token_id261		{262			return Err("item id should be next".into());263		}264265		<Pallet<T>>::create_item(266			self,267			&caller,268			CreateItemData::<T> {269				const_data: BoundedVec::default(),270				variable_data: BoundedVec::default(),271				owner: to,272			},273		)274		.map_err(dispatch_to_evm::<T>)?;275276		Ok(true)277	}278279	/// `token_id` should be obtained with `next_token_id` method,280	/// unlike standard, you can't specify it manually281	#[solidity(rename_selector = "mintWithTokenURI")]282	#[weight(<SelfWeightOf<T>>::create_item())]283	fn mint_with_token_uri(284		&mut self,285		caller: caller,286		to: address,287		token_id: uint256,288		token_uri: string,289	) -> Result<bool> {290		if let SchemaVersion::ImageURL = self.schema_version {291			let caller = T::CrossAccountId::from_eth(caller);292			let to = T::CrossAccountId::from_eth(to);293			let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;294			if <TokensMinted<T>>::get(self.id)295				.checked_add(1)296				.ok_or("item id overflow")?297				!= token_id298			{299				return Err("item id should be next".into());300			}301	302			<Pallet<T>>::create_item(303				self,304				&caller,305				CreateItemData::<T> {306					const_data: Vec::<u8>::from(token_uri)307						.try_into()308						.map_err(|_| "token uri is too long")?,309					variable_data: BoundedVec::default(),310					owner: to,311				},312			)313			.map_err(dispatch_to_evm::<T>)?;314			Ok(true)315        } else {316            Err(error_unsupported_shema_version())317        }318	}319320	/// Not implemented321	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {322		Err("not implementable".into())323	}324}325326#[solidity_interface(name = "ERC721UniqueExtensions")]327impl<T: Config> NonfungibleHandle<T> {328	#[weight(<SelfWeightOf<T>>::transfer())]329	fn transfer(330		&mut self,331		caller: caller,332		to: address,333		token_id: uint256,334		_value: value,335	) -> Result<void> {336		let caller = T::CrossAccountId::from_eth(caller);337		let to = T::CrossAccountId::from_eth(to);338		let token = token_id.try_into()?;339340		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;341		Ok(())342	}343344	#[weight(<SelfWeightOf<T>>::burn_from())]345	fn burn_from(346		&mut self,347		caller: caller,348		from: address,349		token_id: uint256,350		_value: value,351	) -> Result<void> {352		let caller = T::CrossAccountId::from_eth(caller);353		let from = T::CrossAccountId::from_eth(from);354		let token = token_id.try_into()?;355356		<Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;357		Ok(())358	}359360	fn next_token_id(&self) -> Result<uint256> {361		self.consume_store_reads(1)?;362		Ok(<TokensMinted<T>>::get(self.id)363			.checked_add(1)364			.ok_or("item id overflow")?365			.into())366	}367368	#[weight(<SelfWeightOf<T>>::set_variable_metadata(data.len() as u32))]369	fn set_variable_metadata(370		&mut self,371		caller: caller,372		token_id: uint256,373		data: bytes,374	) -> Result<void> {375		let caller = T::CrossAccountId::from_eth(caller);376		let token = token_id.try_into()?;377378		<Pallet<T>>::set_variable_metadata(379			self,380			&caller,381			token,382			data.try_into()383				.map_err(|_| "metadata size exceeded limit")?,384		)385		.map_err(dispatch_to_evm::<T>)?;386		Ok(())387	}388389	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {390		self.consume_store_reads(1)?;391		let token: TokenId = token_id.try_into()?;392393		Ok(<TokenData<T>>::get((self.id, token))394			.ok_or("token not found")?395			.variable_data396			.into_inner())397	}398399	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]400	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {401		let caller = T::CrossAccountId::from_eth(caller);402		let to = T::CrossAccountId::from_eth(to);403		let mut expected_index = <TokensMinted<T>>::get(self.id)404			.checked_add(1)405			.ok_or("item id overflow")?;406407		let total_tokens = token_ids.len();408		for id in token_ids.into_iter() {409			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;410			if id != expected_index {411				return Err("item id should be next".into());412			}413			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;414		}415		let data = (0..total_tokens)416			.map(|_| CreateItemData::<T> {417				const_data: BoundedVec::default(),418				variable_data: BoundedVec::default(),419				owner: to.clone(),420			})421			.collect();422423		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;424		Ok(true)425	}426427	#[solidity(rename_selector = "mintBulkWithTokenURI")]428	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]429	fn mint_bulk_with_token_uri(430		&mut self,431		caller: caller,432		to: address,433		tokens: Vec<(uint256, string)>,434	) -> Result<bool> {435		if let SchemaVersion::ImageURL = self.schema_version {436			let caller = T::CrossAccountId::from_eth(caller);437			let to = T::CrossAccountId::from_eth(to);438			let mut expected_index = <TokensMinted<T>>::get(self.id)439				.checked_add(1)440				.ok_or("item id overflow")?;441	442			let mut data = Vec::with_capacity(tokens.len());443			for (id, token_uri) in tokens {444				let id: u32 = id.try_into().map_err(|_| "token id overflow")?;445				if id != expected_index {446					panic!("item id should be next ({}) but got {}", expected_index, id);447				}448				expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;449	450				data.push(CreateItemData::<T> {451					const_data: Vec::<u8>::from(token_uri)452						.try_into()453						.map_err(|_| "token uri is too long")?,454					variable_data: vec![].try_into().unwrap(),455					owner: to.clone(),456				});457			}458	459			<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;460			Ok(true)461        } else {462            Err(error_unsupported_shema_version())463        }464	}465}466467#[solidity_interface(468	name = "UniqueNFT",469	is(470		ERC721,471		ERC721Metadata,472		ERC721Enumerable,473		ERC721UniqueExtensions,474		ERC721Mintable,475		ERC721Burnable,476	)477)]478impl<T: Config> NonfungibleHandle<T> {}479480// Not a tests, but code generators481generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);482generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);483484impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {485	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");486487	fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult> {488		call::<T, UniqueNFTCall<T>, _>(*source, self, value, input)489	}490}
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/>.1617extern crate alloc;18use core::{19	char::{REPLACEMENT_CHARACTER, decode_utf16},20	convert::TryInto,21};22use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};23use frame_support::BoundedVec;24use up_data_structs::{TokenId, SchemaVersion};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_core::{H160, U256};27use sp_std::{vec::Vec, vec};28use pallet_common::{29	account::CrossAccountId,30	erc::{CommonEvmHandler, PrecompileResult},31};32use pallet_evm_coder_substrate::call;3334use crate::{35	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,36	SelfWeightOf, weights::WeightInfo,37};3839fn error_unsupported_shema_version() -> Error {40	alloc::format!("Unsupported shema version! Support only {:?}", SchemaVersion::ImageURL).as_str().into()41}4243#[derive(ToLog)]44pub enum ERC721Events {45	Transfer {46		#[indexed]47		from: address,48		#[indexed]49		to: address,50		#[indexed]51		token_id: uint256,52	},53	Approval {54		#[indexed]55		owner: address,56		#[indexed]57		approved: address,58		#[indexed]59		token_id: uint256,60	},61	#[allow(dead_code)]62	ApprovalForAll {63		#[indexed]64		owner: address,65		#[indexed]66		operator: address,67		approved: bool,68	},69}7071#[derive(ToLog)]72pub enum ERC721MintableEvents {73	#[allow(dead_code)]74	MintingFinished {},75}7677#[solidity_interface(name = "ERC721Metadata")]78impl<T: Config> NonfungibleHandle<T> {79	fn name(&self) -> Result<string> {80		Ok(decode_utf16(self.name.iter().copied())81			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))82			.collect::<string>())8384	}85	86	fn symbol(&self) -> Result<string> {87		Ok(string::from_utf8_lossy(&self.token_prefix).into())88	}8990	/// Returns token's const_metadata91	#[solidity(rename_selector = "tokenURI")]92	fn token_uri(&self, token_id: uint256) -> Result<string> {93		if let SchemaVersion::ImageURL = self.schema_version {94			self.consume_store_reads(1)?;95			let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;96			Ok(string::from_utf8_lossy(97				&<TokenData<T>>::get((self.id, token_id))98					.ok_or("token not found")?99					.const_data,100			)101			.into())102        } else {103            Err(error_unsupported_shema_version())104        }105	}106}107108#[solidity_interface(name = "ERC721Enumerable")]109impl<T: Config> NonfungibleHandle<T> {110	fn token_by_index(&self, index: uint256) -> Result<uint256> {111		Ok(index)112	}113114	/// Not implemented115	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {116		// TODO: Not implemetable117		Err("not implemented".into())118	}119120	fn total_supply(&self) -> Result<uint256> {121		self.consume_store_reads(1)?;122		Ok(<Pallet<T>>::total_supply(self).into())123	}124}125126#[solidity_interface(name = "ERC721", events(ERC721Events))]127impl<T: Config> NonfungibleHandle<T> {128	fn balance_of(&self, owner: address) -> Result<uint256> {129		self.consume_store_reads(1)?;130		let owner = T::CrossAccountId::from_eth(owner);131		let balance = <AccountBalance<T>>::get((self.id, owner));132		Ok(balance.into())133	}134	fn owner_of(&self, token_id: uint256) -> Result<address> {135		self.consume_store_reads(1)?;136		let token: TokenId = token_id.try_into()?;137		Ok(*<TokenData<T>>::get((self.id, token))138			.ok_or("token not found")?139			.owner140			.as_eth())141	}142	/// Not implemented143	fn safe_transfer_from_with_data(144		&mut self,145		_from: address,146		_to: address,147		_token_id: uint256,148		_data: bytes,149		_value: value,150	) -> Result<void> {151		// TODO: Not implemetable152		Err("not implemented".into())153	}154	/// Not implemented155	fn safe_transfer_from(156		&mut self,157		_from: address,158		_to: address,159		_token_id: uint256,160		_value: value,161	) -> Result<void> {162		// TODO: Not implemetable163		Err("not implemented".into())164	}165166	#[weight(<SelfWeightOf<T>>::transfer_from())]167	fn transfer_from(168		&mut self,169		caller: caller,170		from: address,171		to: address,172		token_id: uint256,173		_value: value,174	) -> Result<void> {175		let caller = T::CrossAccountId::from_eth(caller);176		let from = T::CrossAccountId::from_eth(from);177		let to = T::CrossAccountId::from_eth(to);178		let token = token_id.try_into()?;179180		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)181			.map_err(dispatch_to_evm::<T>)?;182		Ok(())183	}184185	#[weight(<SelfWeightOf<T>>::approve())]186	fn approve(187		&mut self,188		caller: caller,189		approved: address,190		token_id: uint256,191		_value: value,192	) -> Result<void> {193		let caller = T::CrossAccountId::from_eth(caller);194		let approved = T::CrossAccountId::from_eth(approved);195		let token = token_id.try_into()?;196197		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))198			.map_err(dispatch_to_evm::<T>)?;199		Ok(())200	}201202	/// Not implemented203	fn set_approval_for_all(204		&mut self,205		_caller: caller,206		_operator: address,207		_approved: bool,208	) -> Result<void> {209		// TODO: Not implemetable210		Err("not implemented".into())211	}212213	/// Not implemented214	fn get_approved(&self, _token_id: uint256) -> Result<address> {215		// TODO: Not implemetable216		Err("not implemented".into())217	}218219	/// Not implemented220	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {221		// TODO: Not implemetable222		Err("not implemented".into())223	}224}225226#[solidity_interface(name = "ERC721Burnable")]227impl<T: Config> NonfungibleHandle<T> {228	#[weight(<SelfWeightOf<T>>::burn_item())]229	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {230		let caller = T::CrossAccountId::from_eth(caller);231		let token = token_id.try_into()?;232233		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;234		Ok(())235	}236}237238#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]239impl<T: Config> NonfungibleHandle<T> {240	fn minting_finished(&self) -> Result<bool> {241		Ok(false)242	}243244	/// `token_id` should be obtained with `next_token_id` method,245	/// unlike standard, you can't specify it manually246	#[weight(<SelfWeightOf<T>>::create_item())]247	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {248		let caller = T::CrossAccountId::from_eth(caller);249		let to = T::CrossAccountId::from_eth(to);250		let token_id: u32 = token_id.try_into()?;251		if <TokensMinted<T>>::get(self.id)252			.checked_add(1)253			.ok_or("item id overflow")?254			!= token_id255		{256			return Err("item id should be next".into());257		}258259		<Pallet<T>>::create_item(260			self,261			&caller,262			CreateItemData::<T> {263				const_data: BoundedVec::default(),264				variable_data: BoundedVec::default(),265				owner: to,266			},267		)268		.map_err(dispatch_to_evm::<T>)?;269270		Ok(true)271	}272273	/// `token_id` should be obtained with `next_token_id` method,274	/// unlike standard, you can't specify it manually275	#[solidity(rename_selector = "mintWithTokenURI")]276	#[weight(<SelfWeightOf<T>>::create_item())]277	fn mint_with_token_uri(278		&mut self,279		caller: caller,280		to: address,281		token_id: uint256,282		token_uri: string,283	) -> Result<bool> {284		if let SchemaVersion::ImageURL = self.schema_version {285			let caller = T::CrossAccountId::from_eth(caller);286			let to = T::CrossAccountId::from_eth(to);287			let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;288			if <TokensMinted<T>>::get(self.id)289				.checked_add(1)290				.ok_or("item id overflow")?291				!= token_id292			{293				return Err("item id should be next".into());294			}295	296			<Pallet<T>>::create_item(297				self,298				&caller,299				CreateItemData::<T> {300					const_data: Vec::<u8>::from(token_uri)301						.try_into()302						.map_err(|_| "token uri is too long")?,303					variable_data: BoundedVec::default(),304					owner: to,305				},306			)307			.map_err(dispatch_to_evm::<T>)?;308			Ok(true)309        } else {310            Err(error_unsupported_shema_version())311        }312	}313314	/// Not implemented315	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {316		Err("not implementable".into())317	}318}319320#[solidity_interface(name = "ERC721UniqueExtensions")]321impl<T: Config> NonfungibleHandle<T> {322	#[weight(<SelfWeightOf<T>>::transfer())]323	fn transfer(324		&mut self,325		caller: caller,326		to: address,327		token_id: uint256,328		_value: value,329	) -> Result<void> {330		let caller = T::CrossAccountId::from_eth(caller);331		let to = T::CrossAccountId::from_eth(to);332		let token = token_id.try_into()?;333334		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;335		Ok(())336	}337338	#[weight(<SelfWeightOf<T>>::burn_from())]339	fn burn_from(340		&mut self,341		caller: caller,342		from: address,343		token_id: uint256,344		_value: value,345	) -> Result<void> {346		let caller = T::CrossAccountId::from_eth(caller);347		let from = T::CrossAccountId::from_eth(from);348		let token = token_id.try_into()?;349350		<Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;351		Ok(())352	}353354	fn next_token_id(&self) -> Result<uint256> {355		self.consume_store_reads(1)?;356		Ok(<TokensMinted<T>>::get(self.id)357			.checked_add(1)358			.ok_or("item id overflow")?359			.into())360	}361362	#[weight(<SelfWeightOf<T>>::set_variable_metadata(data.len() as u32))]363	fn set_variable_metadata(364		&mut self,365		caller: caller,366		token_id: uint256,367		data: bytes,368	) -> Result<void> {369		let caller = T::CrossAccountId::from_eth(caller);370		let token = token_id.try_into()?;371372		<Pallet<T>>::set_variable_metadata(373			self,374			&caller,375			token,376			data.try_into()377				.map_err(|_| "metadata size exceeded limit")?,378		)379		.map_err(dispatch_to_evm::<T>)?;380		Ok(())381	}382383	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {384		self.consume_store_reads(1)?;385		let token: TokenId = token_id.try_into()?;386387		Ok(<TokenData<T>>::get((self.id, token))388			.ok_or("token not found")?389			.variable_data390			.into_inner())391	}392393	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]394	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {395		let caller = T::CrossAccountId::from_eth(caller);396		let to = T::CrossAccountId::from_eth(to);397		let mut expected_index = <TokensMinted<T>>::get(self.id)398			.checked_add(1)399			.ok_or("item id overflow")?;400401		let total_tokens = token_ids.len();402		for id in token_ids.into_iter() {403			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;404			if id != expected_index {405				return Err("item id should be next".into());406			}407			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;408		}409		let data = (0..total_tokens)410			.map(|_| CreateItemData::<T> {411				const_data: BoundedVec::default(),412				variable_data: BoundedVec::default(),413				owner: to.clone(),414			})415			.collect();416417		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;418		Ok(true)419	}420421	#[solidity(rename_selector = "mintBulkWithTokenURI")]422	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]423	fn mint_bulk_with_token_uri(424		&mut self,425		caller: caller,426		to: address,427		tokens: Vec<(uint256, string)>,428	) -> Result<bool> {429		if let SchemaVersion::ImageURL = self.schema_version {430			let caller = T::CrossAccountId::from_eth(caller);431			let to = T::CrossAccountId::from_eth(to);432			let mut expected_index = <TokensMinted<T>>::get(self.id)433				.checked_add(1)434				.ok_or("item id overflow")?;435	436			let mut data = Vec::with_capacity(tokens.len());437			for (id, token_uri) in tokens {438				let id: u32 = id.try_into().map_err(|_| "token id overflow")?;439				if id != expected_index {440					panic!("item id should be next ({}) but got {}", expected_index, id);441				}442				expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;443	444				data.push(CreateItemData::<T> {445					const_data: Vec::<u8>::from(token_uri)446						.try_into()447						.map_err(|_| "token uri is too long")?,448					variable_data: vec![].try_into().unwrap(),449					owner: to.clone(),450				});451			}452	453			<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;454			Ok(true)455        } else {456            Err(error_unsupported_shema_version())457        }458	}459}460461#[solidity_interface(462	name = "UniqueNFT",463	is(464		ERC721,465		ERC721Metadata,466		ERC721Enumerable,467		ERC721UniqueExtensions,468		ERC721Mintable,469		ERC721Burnable,470	)471)]472impl<T: Config> NonfungibleHandle<T> {}473474// Not a tests, but code generators475generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);476generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);477478impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {479	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");480481	fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult> {482		call::<T, UniqueNFTCall<T>, _>(*source, self, value, input)483	}484}
modifiedtests/src/eth/metadata.test.tsdiffbeforeafterboth
--- a/tests/src/eth/metadata.test.ts
+++ b/tests/src/eth/metadata.test.ts
@@ -72,6 +72,8 @@
     const collectionId = await createCollectionExpectSuccess({
       mode: {type: 'NFT'},
       shemaVersion: 'Unique',
+      name: 'some_name',
+      tokenPrefix: 'some_prefix',
     });
     const collection = await api.rpc.unique.collectionById(collectionId);
     expect(collection.isSome).to.be.true;
@@ -86,8 +88,8 @@
     const address = collectionIdToAddress(collectionId);
     const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
 
-    await expect(contract.methods.name().call()).to.be.rejectedWith('Unsupported shema version! Support only ImageURL');
-    await expect(contract.methods.symbol().call()).to.be.rejectedWith('Unsupported shema version! Support only ImageURL');
+    expect(await contract.methods.name().call()).to.be.eq('some_name');
+    expect(await contract.methods.symbol().call()).to.be.eq('some_prefix');
 
     const receiver = createEthAccount(web3);
     const nextTokenId = await contract.methods.nextTokenId().call();
@@ -131,28 +133,78 @@
     expect(await contract.methods.symbol().call()).to.be.eq('some_prefix');
 
     const receiver = createEthAccount(web3);
-    const nextTokenId = await contract.methods.nextTokenId().call();
-    expect(nextTokenId).to.be.equal('1');
-    const result = await contract.methods.mintWithTokenURI(
-      receiver,
-      nextTokenId,
-      'Test URI',
-    ).send({from: caller});
-    const events = normalizeEvents(result.events);
+    { // mintWithTokenURI
+      const nextTokenId = await contract.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await contract.methods.mintWithTokenURI(
+        receiver,
+        nextTokenId,
+        'Test URI',
+      ).send({from: caller});
+      const events = normalizeEvents(result.events);
+  
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
+        },
+      ]);
+  
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+    }
+
+    { // mintBulkWithTokenURI
+      const nextTokenId = await contract.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('2');
+      const result = await contract.methods.mintBulkWithTokenURI(
+        receiver,
+        [
+          [nextTokenId, 'Test URI 0'],
+          [+nextTokenId + 1, 'Test URI 1'],
+          [+nextTokenId + 2, 'Test URI 2'],
+        ],
+      ).send({from: caller});
+      const events = normalizeEvents(result.events);
 
-    expect(events).to.be.deep.equal([
-      {
-        address,
-        event: 'Transfer',
-        args: {
-          from: '0x0000000000000000000000000000000000000000',
-          to: receiver,
-          tokenId: nextTokenId,
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
         },
-      },
-    ]);
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 1),
+          },
+        },
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 2),
+          },
+        },
+      ]);
 
-    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');
+      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');
+      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
+    }
   });
 });