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

difftreelog

refactor do not receive token id as mint argument

Yaroslav Bolyukin2022-10-13parent: #1c71f62.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/>.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::{37		CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key,38		static_property::value,39	},40	CollectionHandle, CollectionPropertyPermissions,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4546use crate::{47	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,48	SelfWeightOf, weights::WeightInfo, TokenProperties,49};5051/// @title A contract that allows to set and delete token properties and change token property permissions.52#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54	/// @notice Set permissions for token property.55	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.56	/// @param key Property key.57	/// @param isMutable Permission to mutate property.58	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.59	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.60	fn set_token_property_permission(61		&mut self,62		caller: caller,63		key: string,64		is_mutable: bool,65		collection_admin: bool,66		token_owner: bool,67	) -> Result<()> {68		let caller = T::CrossAccountId::from_eth(caller);69		<Pallet<T>>::set_property_permission(70			self,71			&caller,72			PropertyKeyPermission {73				key: <Vec<u8>>::from(key)74					.try_into()75					.map_err(|_| "too long key")?,76				permission: PropertyPermission {77					mutable: is_mutable,78					collection_admin,79					token_owner,80				},81			},82		)83		.map_err(dispatch_to_evm::<T>)84	}8586	/// @notice Set token property value.87	/// @dev Throws error if `msg.sender` has no permission to edit the property.88	/// @param tokenId ID of the token.89	/// @param key Property key.90	/// @param value Property value.91	fn set_property(92		&mut self,93		caller: caller,94		token_id: uint256,95		key: string,96		value: bytes,97	) -> Result<()> {98		let caller = T::CrossAccountId::from_eth(caller);99		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100		let key = <Vec<u8>>::from(key)101			.try_into()102			.map_err(|_| "key too long")?;103		let value = value.try_into().map_err(|_| "value too long")?;104105		let nesting_budget = self106			.recorder107			.weight_calls_budget(<StructureWeight<T>>::find_parent());108109		<Pallet<T>>::set_token_property(110			self,111			&caller,112			TokenId(token_id),113			Property { key, value },114			&nesting_budget,115		)116		.map_err(dispatch_to_evm::<T>)117	}118119	/// @notice Delete token property value.120	/// @dev Throws error if `msg.sender` has no permission to edit the property.121	/// @param tokenId ID of the token.122	/// @param key Property key.123	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {124		let caller = T::CrossAccountId::from_eth(caller);125		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;126		let key = <Vec<u8>>::from(key)127			.try_into()128			.map_err(|_| "key too long")?;129130		let nesting_budget = self131			.recorder132			.weight_calls_budget(<StructureWeight<T>>::find_parent());133134		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)135			.map_err(dispatch_to_evm::<T>)136	}137138	/// @notice Get token property value.139	/// @dev Throws error if key not found140	/// @param tokenId ID of the token.141	/// @param key Property key.142	/// @return Property value bytes143	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {144		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;145		let key = <Vec<u8>>::from(key)146			.try_into()147			.map_err(|_| "key too long")?;148149		let props = <TokenProperties<T>>::get((self.id, token_id));150		let prop = props.get(&key).ok_or("key not found")?;151152		Ok(prop.to_vec())153	}154}155156#[derive(ToLog)]157pub enum ERC721Events {158	/// @dev This emits when ownership of any NFT changes by any mechanism.159	///  This event emits when NFTs are created (`from` == 0) and destroyed160	///  (`to` == 0). Exception: during contract creation, any number of NFTs161	///  may be created and assigned without emitting Transfer. At the time of162	///  any transfer, the approved address for that NFT (if any) is reset to none.163	Transfer {164		#[indexed]165		from: address,166		#[indexed]167		to: address,168		#[indexed]169		token_id: uint256,170	},171	/// @dev This emits when the approved address for an NFT is changed or172	///  reaffirmed. The zero address indicates there is no approved address.173	///  When a Transfer event emits, this also indicates that the approved174	///  address for that NFT (if any) is reset to none.175	Approval {176		#[indexed]177		owner: address,178		#[indexed]179		approved: address,180		#[indexed]181		token_id: uint256,182	},183	/// @dev This emits when an operator is enabled or disabled for an owner.184	///  The operator can manage all NFTs of the owner.185	#[allow(dead_code)]186	ApprovalForAll {187		#[indexed]188		owner: address,189		#[indexed]190		operator: address,191		approved: bool,192	},193}194195#[derive(ToLog)]196pub enum ERC721MintableEvents {197	#[allow(dead_code)]198	MintingFinished {},199}200201/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension202/// @dev See https://eips.ethereum.org/EIPS/eip-721203#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]204impl<T: Config> NonfungibleHandle<T> {205	/// @notice A descriptive name for a collection of NFTs in this contract206	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`207	#[solidity(hide, rename_selector = "name")]208	fn name_proxy(&self) -> Result<string> {209		self.name()210	}211212	/// @notice An abbreviated name for NFTs in this contract213	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`214	#[solidity(hide, rename_selector = "symbol")]215	fn symbol_proxy(&self) -> Result<string> {216		self.symbol()217	}218219	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.220	///221	/// @dev If the token has a `url` property and it is not empty, it is returned.222	///  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`.223	///  If the collection property `baseURI` is empty or absent, return "" (empty string)224	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix225	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).226	///227	/// @return token's const_metadata228	#[solidity(rename_selector = "tokenURI")]229	fn token_uri(&self, token_id: uint256) -> Result<string> {230		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;231232		match get_token_property(self, token_id_u32, &key::url()).as_deref() {233			Err(_) | Ok("") => (),234			Ok(url) => {235				return Ok(url.into());236			}237		};238239		let base_uri =240			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())241				.map(BoundedVec::into_inner)242				.map(string::from_utf8)243				.transpose()244				.map_err(|e| {245					Error::Revert(alloc::format!(246						"Can not convert value \"baseURI\" to string with error \"{}\"",247						e248					))249				})?;250251		let base_uri = match base_uri.as_deref() {252			None | Some("") => {253				return Ok("".into());254			}255			Some(base_uri) => base_uri.into(),256		};257258		Ok(259			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {260				Err(_) | Ok("") => base_uri,261				Ok(suffix) => base_uri + suffix,262			},263		)264	}265}266267/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension268/// @dev See https://eips.ethereum.org/EIPS/eip-721269#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]270impl<T: Config> NonfungibleHandle<T> {271	/// @notice Enumerate valid NFTs272	/// @param index A counter less than `totalSupply()`273	/// @return The token identifier for the `index`th NFT,274	///  (sort order not specified)275	fn token_by_index(&self, index: uint256) -> Result<uint256> {276		Ok(index)277	}278279	/// @dev Not implemented280	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {281		// TODO: Not implemetable282		Err("not implemented".into())283	}284285	/// @notice Count NFTs tracked by this contract286	/// @return A count of valid NFTs tracked by this contract, where each one of287	///  them has an assigned and queryable owner not equal to the zero address288	fn total_supply(&self) -> Result<uint256> {289		self.consume_store_reads(1)?;290		Ok(<Pallet<T>>::total_supply(self).into())291	}292}293294/// @title ERC-721 Non-Fungible Token Standard295/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md296#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]297impl<T: Config> NonfungibleHandle<T> {298	/// @notice Count all NFTs assigned to an owner299	/// @dev NFTs assigned to the zero address are considered invalid, and this300	///  function throws for queries about the zero address.301	/// @param owner An address for whom to query the balance302	/// @return The number of NFTs owned by `owner`, possibly zero303	fn balance_of(&self, owner: address) -> Result<uint256> {304		self.consume_store_reads(1)?;305		let owner = T::CrossAccountId::from_eth(owner);306		let balance = <AccountBalance<T>>::get((self.id, owner));307		Ok(balance.into())308	}309	/// @notice Find the owner of an NFT310	/// @dev NFTs assigned to zero address are considered invalid, and queries311	///  about them do throw.312	/// @param tokenId The identifier for an NFT313	/// @return The address of the owner of the NFT314	fn owner_of(&self, token_id: uint256) -> Result<address> {315		self.consume_store_reads(1)?;316		let token: TokenId = token_id.try_into()?;317		Ok(*<TokenData<T>>::get((self.id, token))318			.ok_or("token not found")?319			.owner320			.as_eth())321	}322	/// @dev Not implemented323	#[solidity(rename_selector = "safeTransferFrom")]324	fn safe_transfer_from_with_data(325		&mut self,326		_from: address,327		_to: address,328		_token_id: uint256,329		_data: bytes,330	) -> Result<void> {331		// TODO: Not implemetable332		Err("not implemented".into())333	}334	/// @dev Not implemented335	fn safe_transfer_from(336		&mut self,337		_from: address,338		_to: address,339		_token_id: uint256,340	) -> Result<void> {341		// TODO: Not implemetable342		Err("not implemented".into())343	}344345	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE346	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE347	///  THEY MAY BE PERMANENTLY LOST348	/// @dev Throws unless `msg.sender` is the current owner or an authorized349	///  operator for this NFT. Throws if `from` is not the current owner. Throws350	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.351	/// @param from The current owner of the NFT352	/// @param to The new owner353	/// @param tokenId The NFT to transfer354	#[weight(<SelfWeightOf<T>>::transfer_from())]355	fn transfer_from(356		&mut self,357		caller: caller,358		from: address,359		to: address,360		token_id: uint256,361	) -> Result<void> {362		let caller = T::CrossAccountId::from_eth(caller);363		let from = T::CrossAccountId::from_eth(from);364		let to = T::CrossAccountId::from_eth(to);365		let token = token_id.try_into()?;366		let budget = self367			.recorder368			.weight_calls_budget(<StructureWeight<T>>::find_parent());369370		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)371			.map_err(dispatch_to_evm::<T>)?;372		Ok(())373	}374375	/// @notice Set or reaffirm the approved address for an NFT376	/// @dev The zero address indicates there is no approved address.377	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized378	///  operator of the current owner.379	/// @param approved The new approved NFT controller380	/// @param tokenId The NFT to approve381	#[weight(<SelfWeightOf<T>>::approve())]382	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {383		let caller = T::CrossAccountId::from_eth(caller);384		let approved = T::CrossAccountId::from_eth(approved);385		let token = token_id.try_into()?;386387		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))388			.map_err(dispatch_to_evm::<T>)?;389		Ok(())390	}391392	/// @dev Not implemented393	fn set_approval_for_all(394		&mut self,395		_caller: caller,396		_operator: address,397		_approved: bool,398	) -> Result<void> {399		// TODO: Not implemetable400		Err("not implemented".into())401	}402403	/// @dev Not implemented404	fn get_approved(&self, _token_id: uint256) -> Result<address> {405		// TODO: Not implemetable406		Err("not implemented".into())407	}408409	/// @dev Not implemented410	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {411		// TODO: Not implemetable412		Err("not implemented".into())413	}414}415416/// @title ERC721 Token that can be irreversibly burned (destroyed).417#[solidity_interface(name = ERC721Burnable)]418impl<T: Config> NonfungibleHandle<T> {419	/// @notice Burns a specific ERC721 token.420	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized421	///  operator of the current owner.422	/// @param tokenId The NFT to approve423	#[weight(<SelfWeightOf<T>>::burn_item())]424	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {425		let caller = T::CrossAccountId::from_eth(caller);426		let token = token_id.try_into()?;427428		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;429		Ok(())430	}431}432433/// @title ERC721 minting logic.434#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]435impl<T: Config> NonfungibleHandle<T> {436	fn minting_finished(&self) -> Result<bool> {437		Ok(false)438	}439440	/// @notice Function to mint token.441	/// @dev `tokenId` should be obtained with `nextTokenId` method,442	///  unlike standard, you can't specify it manually443	/// @param to The new owner444	/// @param tokenId ID of the minted NFT445	#[weight(<SelfWeightOf<T>>::create_item())]446	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {447		let caller = T::CrossAccountId::from_eth(caller);448		let to = T::CrossAccountId::from_eth(to);449		let token_id: u32 = token_id.try_into()?;450		let budget = self451			.recorder452			.weight_calls_budget(<StructureWeight<T>>::find_parent());453454		if <TokensMinted<T>>::get(self.id)455			.checked_add(1)456			.ok_or("item id overflow")?457			!= token_id458		{459			return Err("item id should be next".into());460		}461462		<Pallet<T>>::create_item(463			self,464			&caller,465			CreateItemData::<T> {466				properties: BoundedVec::default(),467				owner: to,468			},469			&budget,470		)471		.map_err(dispatch_to_evm::<T>)?;472473		Ok(true)474	}475476	/// @notice Function to mint token with the given tokenUri.477	/// @dev `tokenId` should be obtained with `nextTokenId` method,478	///  unlike standard, you can't specify it manually479	/// @param to The new owner480	/// @param tokenId ID of the minted NFT481	/// @param tokenUri Token URI that would be stored in the NFT properties482	#[solidity(rename_selector = "mintWithTokenURI")]483	#[weight(<SelfWeightOf<T>>::create_item())]484	fn mint_with_token_uri(485		&mut self,486		caller: caller,487		to: address,488		token_id: uint256,489		token_uri: string,490	) -> Result<bool> {491		let key = key::url();492		let permission = get_token_permission::<T>(self.id, &key)?;493		if !permission.collection_admin {494			return Err("Operation is not allowed".into());495		}496497		let caller = T::CrossAccountId::from_eth(caller);498		let to = T::CrossAccountId::from_eth(to);499		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;500		let budget = self501			.recorder502			.weight_calls_budget(<StructureWeight<T>>::find_parent());503504		if <TokensMinted<T>>::get(self.id)505			.checked_add(1)506			.ok_or("item id overflow")?507			!= token_id508		{509			return Err("item id should be next".into());510		}511512		let mut properties = CollectionPropertiesVec::default();513		properties514			.try_push(Property {515				key,516				value: token_uri517					.into_bytes()518					.try_into()519					.map_err(|_| "token uri is too long")?,520			})521			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;522523		<Pallet<T>>::create_item(524			self,525			&caller,526			CreateItemData::<T> {527				properties,528				owner: to,529			},530			&budget,531		)532		.map_err(dispatch_to_evm::<T>)?;533		Ok(true)534	}535536	/// @dev Not implemented537	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {538		Err("not implementable".into())539	}540}541542fn get_token_property<T: Config>(543	collection: &CollectionHandle<T>,544	token_id: u32,545	key: &up_data_structs::PropertyKey,546) -> Result<string> {547	collection.consume_store_reads(1)?;548	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))549		.map_err(|_| Error::Revert("Token properties not found".into()))?;550	if let Some(property) = properties.get(key) {551		return Ok(string::from_utf8_lossy(property).into());552	}553554	Err("Property tokenURI not found".into())555}556557fn get_token_permission<T: Config>(558	collection_id: CollectionId,559	key: &PropertyKey,560) -> Result<PropertyPermission> {561	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)562		.map_err(|_| Error::Revert("No permissions for collection".into()))?;563	let a = token_property_permissions564		.get(key)565		.map(Clone::clone)566		.ok_or_else(|| {567			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();568			Error::Revert(alloc::format!("No permission for key {}", key))569		})?;570	Ok(a)571}572573/// @title Unique extensions for ERC721.574#[solidity_interface(name = ERC721UniqueExtensions)]575impl<T: Config> NonfungibleHandle<T> {576	/// @notice A descriptive name for a collection of NFTs in this contract577	fn name(&self) -> Result<string> {578		Ok(decode_utf16(self.name.iter().copied())579			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))580			.collect::<string>())581	}582583	/// @notice An abbreviated name for NFTs in this contract584	fn symbol(&self) -> Result<string> {585		Ok(string::from_utf8_lossy(&self.token_prefix).into())586	}587588	/// @notice Transfer ownership of an NFT589	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`590	///  is the zero address. Throws if `tokenId` is not a valid NFT.591	/// @param to The new owner592	/// @param tokenId The NFT to transfer593	#[weight(<SelfWeightOf<T>>::transfer())]594	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {595		let caller = T::CrossAccountId::from_eth(caller);596		let to = T::CrossAccountId::from_eth(to);597		let token = token_id.try_into()?;598		let budget = self599			.recorder600			.weight_calls_budget(<StructureWeight<T>>::find_parent());601602		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;603		Ok(())604	}605606	/// @notice Burns a specific ERC721 token.607	/// @dev Throws unless `msg.sender` is the current owner or an authorized608	///  operator for this NFT. Throws if `from` is not the current owner. Throws609	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.610	/// @param from The current owner of the NFT611	/// @param tokenId The NFT to transfer612	#[weight(<SelfWeightOf<T>>::burn_from())]613	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {614		let caller = T::CrossAccountId::from_eth(caller);615		let from = T::CrossAccountId::from_eth(from);616		let token = token_id.try_into()?;617		let budget = self618			.recorder619			.weight_calls_budget(<StructureWeight<T>>::find_parent());620621		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)622			.map_err(dispatch_to_evm::<T>)?;623		Ok(())624	}625626	/// @notice Returns next free NFT ID.627	fn next_token_id(&self) -> Result<uint256> {628		self.consume_store_reads(1)?;629		Ok(<TokensMinted<T>>::get(self.id)630			.checked_add(1)631			.ok_or("item id overflow")?632			.into())633	}634635	/// @notice Function to mint multiple tokens.636	/// @dev `tokenIds` should be an array of consecutive numbers and first number637	///  should be obtained with `nextTokenId` method638	/// @param to The new owner639	/// @param tokenIds IDs of the minted NFTs640	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]641	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {642		let caller = T::CrossAccountId::from_eth(caller);643		let to = T::CrossAccountId::from_eth(to);644		let mut expected_index = <TokensMinted<T>>::get(self.id)645			.checked_add(1)646			.ok_or("item id overflow")?;647		let budget = self648			.recorder649			.weight_calls_budget(<StructureWeight<T>>::find_parent());650651		let total_tokens = token_ids.len();652		for id in token_ids.into_iter() {653			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;654			if id != expected_index {655				return Err("item id should be next".into());656			}657			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;658		}659		let data = (0..total_tokens)660			.map(|_| CreateItemData::<T> {661				properties: BoundedVec::default(),662				owner: to.clone(),663			})664			.collect();665666		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)667			.map_err(dispatch_to_evm::<T>)?;668		Ok(true)669	}670671	/// @notice Function to mint multiple tokens with the given tokenUris.672	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive673	///  numbers and first number should be obtained with `nextTokenId` method674	/// @param to The new owner675	/// @param tokens array of pairs of token ID and token URI for minted tokens676	#[solidity(rename_selector = "mintBulkWithTokenURI")]677	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]678	fn mint_bulk_with_token_uri(679		&mut self,680		caller: caller,681		to: address,682		tokens: Vec<(uint256, string)>,683	) -> Result<bool> {684		let key = key::url();685		let caller = T::CrossAccountId::from_eth(caller);686		let to = T::CrossAccountId::from_eth(to);687		let mut expected_index = <TokensMinted<T>>::get(self.id)688			.checked_add(1)689			.ok_or("item id overflow")?;690		let budget = self691			.recorder692			.weight_calls_budget(<StructureWeight<T>>::find_parent());693694		let mut data = Vec::with_capacity(tokens.len());695		for (id, token_uri) in tokens {696			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;697			if id != expected_index {698				return Err("item id should be next".into());699			}700			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;701702			let mut properties = CollectionPropertiesVec::default();703			properties704				.try_push(Property {705					key: key.clone(),706					value: token_uri707						.into_bytes()708						.try_into()709						.map_err(|_| "token uri is too long")?,710				})711				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;712713			data.push(CreateItemData::<T> {714				properties,715				owner: to.clone(),716			});717		}718719		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)720			.map_err(dispatch_to_evm::<T>)?;721		Ok(true)722	}723}724725#[solidity_interface(726	name = UniqueNFT,727	is(728		ERC721,729		ERC721Enumerable,730		ERC721UniqueExtensions,731		ERC721Mintable,732		ERC721Burnable,733		ERC721Metadata(if(this.flags.erc721metadata)),734		Collection(via(common_mut returns CollectionHandle<T>)),735		TokenProperties,736	)737)]738impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}739740// Not a tests, but code generators741generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);742generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);743744impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>745where746	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,747{748	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");749750	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {751		call::<T, UniqueNFTCall<T>, _, _>(handle, self)752	}753}
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 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::{37		CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key,38		static_property::value,39	},40	CollectionHandle, CollectionPropertyPermissions,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4546use crate::{47	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,48	SelfWeightOf, weights::WeightInfo, TokenProperties,49};5051/// @title A contract that allows to set and delete token properties and change token property permissions.52#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54	/// @notice Set permissions for token property.55	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.56	/// @param key Property key.57	/// @param isMutable Permission to mutate property.58	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.59	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.60	fn set_token_property_permission(61		&mut self,62		caller: caller,63		key: string,64		is_mutable: bool,65		collection_admin: bool,66		token_owner: bool,67	) -> Result<()> {68		let caller = T::CrossAccountId::from_eth(caller);69		<Pallet<T>>::set_property_permission(70			self,71			&caller,72			PropertyKeyPermission {73				key: <Vec<u8>>::from(key)74					.try_into()75					.map_err(|_| "too long key")?,76				permission: PropertyPermission {77					mutable: is_mutable,78					collection_admin,79					token_owner,80				},81			},82		)83		.map_err(dispatch_to_evm::<T>)84	}8586	/// @notice Set token property value.87	/// @dev Throws error if `msg.sender` has no permission to edit the property.88	/// @param tokenId ID of the token.89	/// @param key Property key.90	/// @param value Property value.91	fn set_property(92		&mut self,93		caller: caller,94		token_id: uint256,95		key: string,96		value: bytes,97	) -> Result<()> {98		let caller = T::CrossAccountId::from_eth(caller);99		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100		let key = <Vec<u8>>::from(key)101			.try_into()102			.map_err(|_| "key too long")?;103		let value = value.try_into().map_err(|_| "value too long")?;104105		let nesting_budget = self106			.recorder107			.weight_calls_budget(<StructureWeight<T>>::find_parent());108109		<Pallet<T>>::set_token_property(110			self,111			&caller,112			TokenId(token_id),113			Property { key, value },114			&nesting_budget,115		)116		.map_err(dispatch_to_evm::<T>)117	}118119	/// @notice Delete token property value.120	/// @dev Throws error if `msg.sender` has no permission to edit the property.121	/// @param tokenId ID of the token.122	/// @param key Property key.123	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {124		let caller = T::CrossAccountId::from_eth(caller);125		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;126		let key = <Vec<u8>>::from(key)127			.try_into()128			.map_err(|_| "key too long")?;129130		let nesting_budget = self131			.recorder132			.weight_calls_budget(<StructureWeight<T>>::find_parent());133134		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)135			.map_err(dispatch_to_evm::<T>)136	}137138	/// @notice Get token property value.139	/// @dev Throws error if key not found140	/// @param tokenId ID of the token.141	/// @param key Property key.142	/// @return Property value bytes143	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {144		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;145		let key = <Vec<u8>>::from(key)146			.try_into()147			.map_err(|_| "key too long")?;148149		let props = <TokenProperties<T>>::get((self.id, token_id));150		let prop = props.get(&key).ok_or("key not found")?;151152		Ok(prop.to_vec())153	}154}155156#[derive(ToLog)]157pub enum ERC721Events {158	/// @dev This emits when ownership of any NFT changes by any mechanism.159	///  This event emits when NFTs are created (`from` == 0) and destroyed160	///  (`to` == 0). Exception: during contract creation, any number of NFTs161	///  may be created and assigned without emitting Transfer. At the time of162	///  any transfer, the approved address for that NFT (if any) is reset to none.163	Transfer {164		#[indexed]165		from: address,166		#[indexed]167		to: address,168		#[indexed]169		token_id: uint256,170	},171	/// @dev This emits when the approved address for an NFT is changed or172	///  reaffirmed. The zero address indicates there is no approved address.173	///  When a Transfer event emits, this also indicates that the approved174	///  address for that NFT (if any) is reset to none.175	Approval {176		#[indexed]177		owner: address,178		#[indexed]179		approved: address,180		#[indexed]181		token_id: uint256,182	},183	/// @dev This emits when an operator is enabled or disabled for an owner.184	///  The operator can manage all NFTs of the owner.185	#[allow(dead_code)]186	ApprovalForAll {187		#[indexed]188		owner: address,189		#[indexed]190		operator: address,191		approved: bool,192	},193}194195#[derive(ToLog)]196pub enum ERC721UniqueMintableEvents {197	#[allow(dead_code)]198	MintingFinished {},199}200201/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension202/// @dev See https://eips.ethereum.org/EIPS/eip-721203#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]204impl<T: Config> NonfungibleHandle<T> {205	/// @notice A descriptive name for a collection of NFTs in this contract206	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`207	#[solidity(hide, rename_selector = "name")]208	fn name_proxy(&self) -> Result<string> {209		self.name()210	}211212	/// @notice An abbreviated name for NFTs in this contract213	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`214	#[solidity(hide, rename_selector = "symbol")]215	fn symbol_proxy(&self) -> Result<string> {216		self.symbol()217	}218219	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.220	///221	/// @dev If the token has a `url` property and it is not empty, it is returned.222	///  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`.223	///  If the collection property `baseURI` is empty or absent, return "" (empty string)224	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix225	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).226	///227	/// @return token's const_metadata228	#[solidity(rename_selector = "tokenURI")]229	fn token_uri(&self, token_id: uint256) -> Result<string> {230		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;231232		match get_token_property(self, token_id_u32, &key::url()).as_deref() {233			Err(_) | Ok("") => (),234			Ok(url) => {235				return Ok(url.into());236			}237		};238239		let base_uri =240			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())241				.map(BoundedVec::into_inner)242				.map(string::from_utf8)243				.transpose()244				.map_err(|e| {245					Error::Revert(alloc::format!(246						"Can not convert value \"baseURI\" to string with error \"{}\"",247						e248					))249				})?;250251		let base_uri = match base_uri.as_deref() {252			None | Some("") => {253				return Ok("".into());254			}255			Some(base_uri) => base_uri.into(),256		};257258		Ok(259			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {260				Err(_) | Ok("") => base_uri,261				Ok(suffix) => base_uri + suffix,262			},263		)264	}265}266267/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension268/// @dev See https://eips.ethereum.org/EIPS/eip-721269#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]270impl<T: Config> NonfungibleHandle<T> {271	/// @notice Enumerate valid NFTs272	/// @param index A counter less than `totalSupply()`273	/// @return The token identifier for the `index`th NFT,274	///  (sort order not specified)275	fn token_by_index(&self, index: uint256) -> Result<uint256> {276		Ok(index)277	}278279	/// @dev Not implemented280	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {281		// TODO: Not implemetable282		Err("not implemented".into())283	}284285	/// @notice Count NFTs tracked by this contract286	/// @return A count of valid NFTs tracked by this contract, where each one of287	///  them has an assigned and queryable owner not equal to the zero address288	fn total_supply(&self) -> Result<uint256> {289		self.consume_store_reads(1)?;290		Ok(<Pallet<T>>::total_supply(self).into())291	}292}293294/// @title ERC-721 Non-Fungible Token Standard295/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md296#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]297impl<T: Config> NonfungibleHandle<T> {298	/// @notice Count all NFTs assigned to an owner299	/// @dev NFTs assigned to the zero address are considered invalid, and this300	///  function throws for queries about the zero address.301	/// @param owner An address for whom to query the balance302	/// @return The number of NFTs owned by `owner`, possibly zero303	fn balance_of(&self, owner: address) -> Result<uint256> {304		self.consume_store_reads(1)?;305		let owner = T::CrossAccountId::from_eth(owner);306		let balance = <AccountBalance<T>>::get((self.id, owner));307		Ok(balance.into())308	}309	/// @notice Find the owner of an NFT310	/// @dev NFTs assigned to zero address are considered invalid, and queries311	///  about them do throw.312	/// @param tokenId The identifier for an NFT313	/// @return The address of the owner of the NFT314	fn owner_of(&self, token_id: uint256) -> Result<address> {315		self.consume_store_reads(1)?;316		let token: TokenId = token_id.try_into()?;317		Ok(*<TokenData<T>>::get((self.id, token))318			.ok_or("token not found")?319			.owner320			.as_eth())321	}322	/// @dev Not implemented323	#[solidity(rename_selector = "safeTransferFrom")]324	fn safe_transfer_from_with_data(325		&mut self,326		_from: address,327		_to: address,328		_token_id: uint256,329		_data: bytes,330	) -> Result<void> {331		// TODO: Not implemetable332		Err("not implemented".into())333	}334	/// @dev Not implemented335	fn safe_transfer_from(336		&mut self,337		_from: address,338		_to: address,339		_token_id: uint256,340	) -> Result<void> {341		// TODO: Not implemetable342		Err("not implemented".into())343	}344345	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE346	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE347	///  THEY MAY BE PERMANENTLY LOST348	/// @dev Throws unless `msg.sender` is the current owner or an authorized349	///  operator for this NFT. Throws if `from` is not the current owner. Throws350	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.351	/// @param from The current owner of the NFT352	/// @param to The new owner353	/// @param tokenId The NFT to transfer354	#[weight(<SelfWeightOf<T>>::transfer_from())]355	fn transfer_from(356		&mut self,357		caller: caller,358		from: address,359		to: address,360		token_id: uint256,361	) -> Result<void> {362		let caller = T::CrossAccountId::from_eth(caller);363		let from = T::CrossAccountId::from_eth(from);364		let to = T::CrossAccountId::from_eth(to);365		let token = token_id.try_into()?;366		let budget = self367			.recorder368			.weight_calls_budget(<StructureWeight<T>>::find_parent());369370		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)371			.map_err(dispatch_to_evm::<T>)?;372		Ok(())373	}374375	/// @notice Set or reaffirm the approved address for an NFT376	/// @dev The zero address indicates there is no approved address.377	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized378	///  operator of the current owner.379	/// @param approved The new approved NFT controller380	/// @param tokenId The NFT to approve381	#[weight(<SelfWeightOf<T>>::approve())]382	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {383		let caller = T::CrossAccountId::from_eth(caller);384		let approved = T::CrossAccountId::from_eth(approved);385		let token = token_id.try_into()?;386387		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))388			.map_err(dispatch_to_evm::<T>)?;389		Ok(())390	}391392	/// @dev Not implemented393	fn set_approval_for_all(394		&mut self,395		_caller: caller,396		_operator: address,397		_approved: bool,398	) -> Result<void> {399		// TODO: Not implemetable400		Err("not implemented".into())401	}402403	/// @dev Not implemented404	fn get_approved(&self, _token_id: uint256) -> Result<address> {405		// TODO: Not implemetable406		Err("not implemented".into())407	}408409	/// @dev Not implemented410	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {411		// TODO: Not implemetable412		Err("not implemented".into())413	}414}415416/// @title ERC721 Token that can be irreversibly burned (destroyed).417#[solidity_interface(name = ERC721Burnable)]418impl<T: Config> NonfungibleHandle<T> {419	/// @notice Burns a specific ERC721 token.420	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized421	///  operator of the current owner.422	/// @param tokenId The NFT to approve423	#[weight(<SelfWeightOf<T>>::burn_item())]424	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {425		let caller = T::CrossAccountId::from_eth(caller);426		let token = token_id.try_into()?;427428		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;429		Ok(())430	}431}432433/// @title ERC721 minting logic.434#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]435impl<T: Config> NonfungibleHandle<T> {436	fn minting_finished(&self) -> Result<bool> {437		Ok(false)438	}439440	/// @notice Function to mint token.441	/// @param to The new owner442	/// @return uint256 The id of the newly minted token443	#[weight(<SelfWeightOf<T>>::create_item())]444	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {445		let token_id: uint256 = <TokensMinted<T>>::get(self.id)446			.checked_add(1)447			.ok_or("item id overflow")?448			.into();449		self.mint_check_id(caller, to, token_id)?;450		Ok(token_id)451	}452453	/// @notice Function to mint token.454	/// @dev `tokenId` should be obtained with `nextTokenId` method,455	///  unlike standard, you can't specify it manually456	/// @param to The new owner457	/// @param tokenId ID of the minted NFT458	#[solidity(hide, rename_selector = "mint")]459	#[weight(<SelfWeightOf<T>>::create_item())]460	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {461		let caller = T::CrossAccountId::from_eth(caller);462		let to = T::CrossAccountId::from_eth(to);463		let token_id: u32 = token_id.try_into()?;464		let budget = self465			.recorder466			.weight_calls_budget(<StructureWeight<T>>::find_parent());467468		if <TokensMinted<T>>::get(self.id)469			.checked_add(1)470			.ok_or("item id overflow")?471			!= token_id472		{473			return Err("item id should be next".into());474		}475476		<Pallet<T>>::create_item(477			self,478			&caller,479			CreateItemData::<T> {480				properties: BoundedVec::default(),481				owner: to,482			},483			&budget,484		)485		.map_err(dispatch_to_evm::<T>)?;486487		Ok(true)488	}489490	/// @notice Function to mint token with the given tokenUri.491	/// @param to The new owner492	/// @param tokenUri Token URI that would be stored in the NFT properties493	/// @return uint256 The id of the newly minted token494	#[solidity(rename_selector = "mintWithTokenURI")]495	#[weight(<SelfWeightOf<T>>::create_item())]496	fn mint_with_token_uri(497		&mut self,498		caller: caller,499		to: address,500		token_uri: string,501	) -> Result<uint256> {502		let token_id: uint256 = <TokensMinted<T>>::get(self.id)503			.checked_add(1)504			.ok_or("item id overflow")?505			.into();506		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;507		Ok(token_id)508	}509510	/// @notice Function to mint token with the given tokenUri.511	/// @dev `tokenId` should be obtained with `nextTokenId` method,512	///  unlike standard, you can't specify it manually513	/// @param to The new owner514	/// @param tokenId ID of the minted NFT515	/// @param tokenUri Token URI that would be stored in the NFT properties516	#[solidity(hide, rename_selector = "mintWithTokenURI")]517	#[weight(<SelfWeightOf<T>>::create_item())]518	fn mint_with_token_uri_check_id(519		&mut self,520		caller: caller,521		to: address,522		token_id: uint256,523		token_uri: string,524	) -> Result<bool> {525		let key = key::url();526		let permission = get_token_permission::<T>(self.id, &key)?;527		if !permission.collection_admin {528			return Err("Operation is not allowed".into());529		}530531		let caller = T::CrossAccountId::from_eth(caller);532		let to = T::CrossAccountId::from_eth(to);533		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;534		let budget = self535			.recorder536			.weight_calls_budget(<StructureWeight<T>>::find_parent());537538		if <TokensMinted<T>>::get(self.id)539			.checked_add(1)540			.ok_or("item id overflow")?541			!= token_id542		{543			return Err("item id should be next".into());544		}545546		let mut properties = CollectionPropertiesVec::default();547		properties548			.try_push(Property {549				key,550				value: token_uri551					.into_bytes()552					.try_into()553					.map_err(|_| "token uri is too long")?,554			})555			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;556557		<Pallet<T>>::create_item(558			self,559			&caller,560			CreateItemData::<T> {561				properties,562				owner: to,563			},564			&budget,565		)566		.map_err(dispatch_to_evm::<T>)?;567		Ok(true)568	}569570	/// @dev Not implemented571	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {572		Err("not implementable".into())573	}574}575576fn get_token_property<T: Config>(577	collection: &CollectionHandle<T>,578	token_id: u32,579	key: &up_data_structs::PropertyKey,580) -> Result<string> {581	collection.consume_store_reads(1)?;582	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))583		.map_err(|_| Error::Revert("Token properties not found".into()))?;584	if let Some(property) = properties.get(key) {585		return Ok(string::from_utf8_lossy(property).into());586	}587588	Err("Property tokenURI not found".into())589}590591fn get_token_permission<T: Config>(592	collection_id: CollectionId,593	key: &PropertyKey,594) -> Result<PropertyPermission> {595	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)596		.map_err(|_| Error::Revert("No permissions for collection".into()))?;597	let a = token_property_permissions598		.get(key)599		.map(Clone::clone)600		.ok_or_else(|| {601			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();602			Error::Revert(alloc::format!("No permission for key {}", key))603		})?;604	Ok(a)605}606607/// @title Unique extensions for ERC721.608#[solidity_interface(name = ERC721UniqueExtensions)]609impl<T: Config> NonfungibleHandle<T> {610	/// @notice A descriptive name for a collection of NFTs in this contract611	fn name(&self) -> Result<string> {612		Ok(decode_utf16(self.name.iter().copied())613			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))614			.collect::<string>())615	}616617	/// @notice An abbreviated name for NFTs in this contract618	fn symbol(&self) -> Result<string> {619		Ok(string::from_utf8_lossy(&self.token_prefix).into())620	}621622	/// @notice Transfer ownership of an NFT623	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`624	///  is the zero address. Throws if `tokenId` is not a valid NFT.625	/// @param to The new owner626	/// @param tokenId The NFT to transfer627	#[weight(<SelfWeightOf<T>>::transfer())]628	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {629		let caller = T::CrossAccountId::from_eth(caller);630		let to = T::CrossAccountId::from_eth(to);631		let token = token_id.try_into()?;632		let budget = self633			.recorder634			.weight_calls_budget(<StructureWeight<T>>::find_parent());635636		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;637		Ok(())638	}639640	/// @notice Burns a specific ERC721 token.641	/// @dev Throws unless `msg.sender` is the current owner or an authorized642	///  operator for this NFT. Throws if `from` is not the current owner. Throws643	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.644	/// @param from The current owner of the NFT645	/// @param tokenId The NFT to transfer646	#[weight(<SelfWeightOf<T>>::burn_from())]647	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {648		let caller = T::CrossAccountId::from_eth(caller);649		let from = T::CrossAccountId::from_eth(from);650		let token = token_id.try_into()?;651		let budget = self652			.recorder653			.weight_calls_budget(<StructureWeight<T>>::find_parent());654655		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)656			.map_err(dispatch_to_evm::<T>)?;657		Ok(())658	}659660	/// @notice Returns next free NFT ID.661	fn next_token_id(&self) -> Result<uint256> {662		self.consume_store_reads(1)?;663		Ok(<TokensMinted<T>>::get(self.id)664			.checked_add(1)665			.ok_or("item id overflow")?666			.into())667	}668669	/// @notice Function to mint multiple tokens.670	/// @dev `tokenIds` should be an array of consecutive numbers and first number671	///  should be obtained with `nextTokenId` method672	/// @param to The new owner673	/// @param tokenIds IDs of the minted NFTs674	#[solidity(hide)]675	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]676	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {677		let caller = T::CrossAccountId::from_eth(caller);678		let to = T::CrossAccountId::from_eth(to);679		let mut expected_index = <TokensMinted<T>>::get(self.id)680			.checked_add(1)681			.ok_or("item id overflow")?;682		let budget = self683			.recorder684			.weight_calls_budget(<StructureWeight<T>>::find_parent());685686		let total_tokens = token_ids.len();687		for id in token_ids.into_iter() {688			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;689			if id != expected_index {690				return Err("item id should be next".into());691			}692			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;693		}694		let data = (0..total_tokens)695			.map(|_| CreateItemData::<T> {696				properties: BoundedVec::default(),697				owner: to.clone(),698			})699			.collect();700701		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)702			.map_err(dispatch_to_evm::<T>)?;703		Ok(true)704	}705706	/// @notice Function to mint multiple tokens with the given tokenUris.707	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive708	///  numbers and first number should be obtained with `nextTokenId` method709	/// @param to The new owner710	/// @param tokens array of pairs of token ID and token URI for minted tokens711	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]712	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]713	fn mint_bulk_with_token_uri(714		&mut self,715		caller: caller,716		to: address,717		tokens: Vec<(uint256, string)>,718	) -> Result<bool> {719		let key = key::url();720		let caller = T::CrossAccountId::from_eth(caller);721		let to = T::CrossAccountId::from_eth(to);722		let mut expected_index = <TokensMinted<T>>::get(self.id)723			.checked_add(1)724			.ok_or("item id overflow")?;725		let budget = self726			.recorder727			.weight_calls_budget(<StructureWeight<T>>::find_parent());728729		let mut data = Vec::with_capacity(tokens.len());730		for (id, token_uri) in tokens {731			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;732			if id != expected_index {733				return Err("item id should be next".into());734			}735			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;736737			let mut properties = CollectionPropertiesVec::default();738			properties739				.try_push(Property {740					key: key.clone(),741					value: token_uri742						.into_bytes()743						.try_into()744						.map_err(|_| "token uri is too long")?,745				})746				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;747748			data.push(CreateItemData::<T> {749				properties,750				owner: to.clone(),751			});752		}753754		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)755			.map_err(dispatch_to_evm::<T>)?;756		Ok(true)757	}758}759760#[solidity_interface(761	name = UniqueNFT,762	is(763		ERC721,764		ERC721Enumerable,765		ERC721UniqueExtensions,766		ERC721UniqueMintable,767		ERC721Burnable,768		ERC721Metadata(if(this.flags.erc721metadata)),769		Collection(via(common_mut returns CollectionHandle<T>)),770		TokenProperties,771	)772)]773impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}774775// Not a tests, but code generators776generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);777generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);778779impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>780where781	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,782{783	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");784785	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {786		call::<T, UniqueNFTCall<T>, _, _>(handle, self)787	}788}
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -187,7 +187,7 @@
 }
 
 #[derive(ToLog)]
-pub enum ERC721MintableEvents {
+pub enum ERC721UniqueMintableEvents {
 	/// @dev Not supported
 	#[allow(dead_code)]
 	MintingFinished {},
@@ -449,19 +449,33 @@
 }
 
 /// @title ERC721 minting logic.
-#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]
+#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]
 impl<T: Config> RefungibleHandle<T> {
 	fn minting_finished(&self) -> Result<bool> {
 		Ok(false)
 	}
 
 	/// @notice Function to mint token.
+	/// @param to The new owner
+	/// @return uint256 The id of the newly minted token
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {
+		let token_id: uint256 = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			.into();
+		self.mint_check_id(caller, to, token_id)?;
+		Ok(token_id)
+	}
+
+	/// @notice Function to mint token.
 	/// @dev `tokenId` should be obtained with `nextTokenId` method,
 	///  unlike standard, you can't specify it manually
 	/// @param to The new owner
 	/// @param tokenId ID of the minted RFT
+	#[solidity(hide, rename_selector = "mint")]
 	#[weight(<SelfWeightOf<T>>::create_item())]
-	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
+	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let token_id: u32 = token_id.try_into()?;
@@ -497,14 +511,34 @@
 	}
 
 	/// @notice Function to mint token with the given tokenUri.
+	/// @param to The new owner
+	/// @param tokenUri Token URI that would be stored in the NFT properties
+	/// @return uint256 The id of the newly minted token
+	#[solidity(rename_selector = "mintWithTokenURI")]
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint_with_token_uri(
+		&mut self,
+		caller: caller,
+		to: address,
+		token_uri: string,
+	) -> Result<uint256> {
+		let token_id: uint256 = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			.into();
+		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;
+		Ok(token_id)
+	}
+
+	/// @notice Function to mint token with the given tokenUri.
 	/// @dev `tokenId` should be obtained with `nextTokenId` method,
 	///  unlike standard, you can't specify it manually
 	/// @param to The new owner
 	/// @param tokenId ID of the minted RFT
 	/// @param tokenUri Token URI that would be stored in the RFT properties
-	#[solidity(rename_selector = "mintWithTokenURI")]
+	#[solidity(hide, rename_selector = "mintWithTokenURI")]
 	#[weight(<SelfWeightOf<T>>::create_item())]
-	fn mint_with_token_uri(
+	fn mint_with_token_uri_check_id(
 		&mut self,
 		caller: caller,
 		to: address,
@@ -671,6 +705,7 @@
 	///  should be obtained with `nextTokenId` method
 	/// @param to The new owner
 	/// @param tokenIds IDs of the minted RFTs
+	#[solidity(hide)]
 	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]
 	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
@@ -713,7 +748,7 @@
 	///  numbers and first number should be obtained with `nextTokenId` method
 	/// @param to The new owner
 	/// @param tokens array of pairs of token ID and token URI for minted tokens
-	#[solidity(rename_selector = "mintBulkWithTokenURI")]
+	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]
 	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]
 	fn mint_bulk_with_token_uri(
 		&mut self,
@@ -784,7 +819,7 @@
 		ERC721,
 		ERC721Enumerable,
 		ERC721UniqueExtensions,
-		ERC721Mintable,
+		ERC721UniqueMintable,
 		ERC721Burnable,
 		ERC721Metadata(if(this.flags.erc721metadata)),
 		Collection(via(common_mut returns CollectionHandle<T>)),