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

difftreelog

CORE-325 Return error for non ShemaVersion::ImageURL

Trubnikov Sergey2022-04-06parent: #219f5f0.patch.diff
in: master

1 file 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/>.1617use core::{18	char::{REPLACEMENT_CHARACTER, decode_utf16},19	convert::TryInto,20};21use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};22use frame_support::BoundedVec;23use up_data_structs::TokenId;24use pallet_evm_coder_substrate::dispatch_to_evm;25use sp_core::{H160, U256};26use sp_std::{vec::Vec, vec};27use pallet_common::{28	account::CrossAccountId,29	erc::{CommonEvmHandler, PrecompileResult},30};31use pallet_evm_coder_substrate::call;3233use crate::{34	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,35	SelfWeightOf, weights::WeightInfo,36};3738#[derive(ToLog)]39pub enum ERC721Events {40	Transfer {41		#[indexed]42		from: address,43		#[indexed]44		to: address,45		#[indexed]46		token_id: uint256,47	},48	Approval {49		#[indexed]50		owner: address,51		#[indexed]52		approved: address,53		#[indexed]54		token_id: uint256,55	},56	#[allow(dead_code)]57	ApprovalForAll {58		#[indexed]59		owner: address,60		#[indexed]61		operator: address,62		approved: bool,63	},64}6566#[derive(ToLog)]67pub enum ERC721MintableEvents {68	#[allow(dead_code)]69	MintingFinished {},70}7172#[solidity_interface(name = "ERC721Metadata")]73impl<T: Config> NonfungibleHandle<T> {74	fn name(&self) -> Result<string> {75		Ok(decode_utf16(self.name.iter().copied())76			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))77			.collect::<string>())78	}79	fn symbol(&self) -> Result<string> {80		Ok(string::from_utf8_lossy(&self.token_prefix).into())81	}8283	/// Returns token's const_metadata84	#[solidity(rename_selector = "tokenURI")]85	fn token_uri(&self, token_id: uint256) -> Result<string> {86		self.consume_store_reads(1)?;87		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;88		Ok(string::from_utf8_lossy(89			&<TokenData<T>>::get((self.id, token_id))90				.ok_or("token not found")?91				.const_data,92		)93		.into())94	}95}9697#[solidity_interface(name = "ERC721Enumerable")]98impl<T: Config> NonfungibleHandle<T> {99	fn token_by_index(&self, index: uint256) -> Result<uint256> {100		Ok(index)101	}102103	/// Not implemented104	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {105		// TODO: Not implemetable106		Err("not implemented".into())107	}108109	fn total_supply(&self) -> Result<uint256> {110		self.consume_store_reads(1)?;111		Ok(<Pallet<T>>::total_supply(self).into())112	}113}114115#[solidity_interface(name = "ERC721", events(ERC721Events))]116impl<T: Config> NonfungibleHandle<T> {117	fn balance_of(&self, owner: address) -> Result<uint256> {118		self.consume_store_reads(1)?;119		let owner = T::CrossAccountId::from_eth(owner);120		let balance = <AccountBalance<T>>::get((self.id, owner));121		Ok(balance.into())122	}123	fn owner_of(&self, token_id: uint256) -> Result<address> {124		self.consume_store_reads(1)?;125		let token: TokenId = token_id.try_into()?;126		Ok(*<TokenData<T>>::get((self.id, token))127			.ok_or("token not found")?128			.owner129			.as_eth())130	}131	/// Not implemented132	fn safe_transfer_from_with_data(133		&mut self,134		_from: address,135		_to: address,136		_token_id: uint256,137		_data: bytes,138		_value: value,139	) -> Result<void> {140		// TODO: Not implemetable141		Err("not implemented".into())142	}143	/// Not implemented144	fn safe_transfer_from(145		&mut self,146		_from: address,147		_to: address,148		_token_id: uint256,149		_value: value,150	) -> Result<void> {151		// TODO: Not implemetable152		Err("not implemented".into())153	}154155	#[weight(<SelfWeightOf<T>>::transfer_from())]156	fn transfer_from(157		&mut self,158		caller: caller,159		from: address,160		to: address,161		token_id: uint256,162		_value: value,163	) -> Result<void> {164		let caller = T::CrossAccountId::from_eth(caller);165		let from = T::CrossAccountId::from_eth(from);166		let to = T::CrossAccountId::from_eth(to);167		let token = token_id.try_into()?;168169		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)170			.map_err(dispatch_to_evm::<T>)?;171		Ok(())172	}173174	#[weight(<SelfWeightOf<T>>::approve())]175	fn approve(176		&mut self,177		caller: caller,178		approved: address,179		token_id: uint256,180		_value: value,181	) -> Result<void> {182		let caller = T::CrossAccountId::from_eth(caller);183		let approved = T::CrossAccountId::from_eth(approved);184		let token = token_id.try_into()?;185186		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))187			.map_err(dispatch_to_evm::<T>)?;188		Ok(())189	}190191	/// Not implemented192	fn set_approval_for_all(193		&mut self,194		_caller: caller,195		_operator: address,196		_approved: bool,197	) -> Result<void> {198		// TODO: Not implemetable199		Err("not implemented".into())200	}201202	/// Not implemented203	fn get_approved(&self, _token_id: uint256) -> Result<address> {204		// TODO: Not implemetable205		Err("not implemented".into())206	}207208	/// Not implemented209	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {210		// TODO: Not implemetable211		Err("not implemented".into())212	}213}214215#[solidity_interface(name = "ERC721Burnable")]216impl<T: Config> NonfungibleHandle<T> {217	#[weight(<SelfWeightOf<T>>::burn_item())]218	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {219		let caller = T::CrossAccountId::from_eth(caller);220		let token = token_id.try_into()?;221222		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;223		Ok(())224	}225}226227#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]228impl<T: Config> NonfungibleHandle<T> {229	fn minting_finished(&self) -> Result<bool> {230		Ok(false)231	}232233	/// `token_id` should be obtained with `next_token_id` method,234	/// unlike standard, you can't specify it manually235	#[weight(<SelfWeightOf<T>>::create_item())]236	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {237		let caller = T::CrossAccountId::from_eth(caller);238		let to = T::CrossAccountId::from_eth(to);239		let token_id: u32 = token_id.try_into()?;240		if <TokensMinted<T>>::get(self.id)241			.checked_add(1)242			.ok_or("item id overflow")?243			!= token_id244		{245			return Err("item id should be next".into());246		}247248		<Pallet<T>>::create_item(249			self,250			&caller,251			CreateItemData::<T> {252				const_data: BoundedVec::default(),253				variable_data: BoundedVec::default(),254				owner: to,255			},256		)257		.map_err(dispatch_to_evm::<T>)?;258259		Ok(true)260	}261262	/// `token_id` should be obtained with `next_token_id` method,263	/// unlike standard, you can't specify it manually264	#[solidity(rename_selector = "mintWithTokenURI")]265	#[weight(<SelfWeightOf<T>>::create_item())]266	fn mint_with_token_uri(267		&mut self,268		caller: caller,269		to: address,270		token_id: uint256,271		token_uri: string,272	) -> Result<bool> {273		let caller = T::CrossAccountId::from_eth(caller);274		let to = T::CrossAccountId::from_eth(to);275		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;276		if <TokensMinted<T>>::get(self.id)277			.checked_add(1)278			.ok_or("item id overflow")?279			!= token_id280		{281			return Err("item id should be next".into());282		}283284		<Pallet<T>>::create_item(285			self,286			&caller,287			CreateItemData::<T> {288				const_data: Vec::<u8>::from(token_uri)289					.try_into()290					.map_err(|_| "token uri is too long")?,291				variable_data: BoundedVec::default(),292				owner: to,293			},294		)295		.map_err(dispatch_to_evm::<T>)?;296		Ok(true)297	}298299	/// Not implemented300	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {301		Err("not implementable".into())302	}303}304305#[solidity_interface(name = "ERC721UniqueExtensions")]306impl<T: Config> NonfungibleHandle<T> {307	#[weight(<SelfWeightOf<T>>::transfer())]308	fn transfer(309		&mut self,310		caller: caller,311		to: address,312		token_id: uint256,313		_value: value,314	) -> Result<void> {315		let caller = T::CrossAccountId::from_eth(caller);316		let to = T::CrossAccountId::from_eth(to);317		let token = token_id.try_into()?;318319		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;320		Ok(())321	}322323	#[weight(<SelfWeightOf<T>>::burn_from())]324	fn burn_from(325		&mut self,326		caller: caller,327		from: address,328		token_id: uint256,329		_value: value,330	) -> Result<void> {331		let caller = T::CrossAccountId::from_eth(caller);332		let from = T::CrossAccountId::from_eth(from);333		let token = token_id.try_into()?;334335		<Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;336		Ok(())337	}338339	fn next_token_id(&self) -> Result<uint256> {340		self.consume_store_reads(1)?;341		Ok(<TokensMinted<T>>::get(self.id)342			.checked_add(1)343			.ok_or("item id overflow")?344			.into())345	}346347	#[weight(<SelfWeightOf<T>>::set_variable_metadata(data.len() as u32))]348	fn set_variable_metadata(349		&mut self,350		caller: caller,351		token_id: uint256,352		data: bytes,353	) -> Result<void> {354		let caller = T::CrossAccountId::from_eth(caller);355		let token = token_id.try_into()?;356357		<Pallet<T>>::set_variable_metadata(358			self,359			&caller,360			token,361			data.try_into()362				.map_err(|_| "metadata size exceeded limit")?,363		)364		.map_err(dispatch_to_evm::<T>)?;365		Ok(())366	}367368	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {369		self.consume_store_reads(1)?;370		let token: TokenId = token_id.try_into()?;371372		Ok(<TokenData<T>>::get((self.id, token))373			.ok_or("token not found")?374			.variable_data375			.into_inner())376	}377378	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]379	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {380		let caller = T::CrossAccountId::from_eth(caller);381		let to = T::CrossAccountId::from_eth(to);382		let mut expected_index = <TokensMinted<T>>::get(self.id)383			.checked_add(1)384			.ok_or("item id overflow")?;385386		let total_tokens = token_ids.len();387		for id in token_ids.into_iter() {388			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;389			if id != expected_index {390				return Err("item id should be next".into());391			}392			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;393		}394		let data = (0..total_tokens)395			.map(|_| CreateItemData::<T> {396				const_data: BoundedVec::default(),397				variable_data: BoundedVec::default(),398				owner: to.clone(),399			})400			.collect();401402		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;403		Ok(true)404	}405406	#[solidity(rename_selector = "mintBulkWithTokenURI")]407	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]408	fn mint_bulk_with_token_uri(409		&mut self,410		caller: caller,411		to: address,412		tokens: Vec<(uint256, string)>,413	) -> Result<bool> {414		let caller = T::CrossAccountId::from_eth(caller);415		let to = T::CrossAccountId::from_eth(to);416		let mut expected_index = <TokensMinted<T>>::get(self.id)417			.checked_add(1)418			.ok_or("item id overflow")?;419420		let mut data = Vec::with_capacity(tokens.len());421		for (id, token_uri) in tokens {422			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;423			if id != expected_index {424				panic!("item id should be next ({}) but got {}", expected_index, id);425			}426			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;427428			data.push(CreateItemData::<T> {429				const_data: Vec::<u8>::from(token_uri)430					.try_into()431					.map_err(|_| "token uri is too long")?,432				variable_data: vec![].try_into().unwrap(),433				owner: to.clone(),434			});435		}436437		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;438		Ok(true)439	}440}441442#[solidity_interface(443	name = "UniqueNFT",444	is(445		ERC721,446		ERC721Metadata,447		ERC721Enumerable,448		ERC721UniqueExtensions,449		ERC721Mintable,450		ERC721Burnable,451	)452)]453impl<T: Config> NonfungibleHandle<T> {}454455// Not a tests, but code generators456generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);457generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);458459impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {460	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");461462	fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult> {463		call::<T, UniqueNFTCall<T>, _>(*source, self, value, input)464	}465}
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		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}