git.delta.rocks / unique-network / refs/commits / 171847de27f4

difftreelog

regenerate solidity&fix test

PraetorP2022-11-15parent: #b30d78a.patch.diff
in: master

6 files changed

modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

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::{28	abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29	weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34	CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},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.0.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 Set token properties value.120	/// @dev Throws error if `msg.sender` has no permission to edit the property.121	/// @param tokenId ID of the token.122	/// @param properties settable properties123	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124	fn set_properties(125		&mut self,126		caller: caller,127		token_id: uint256,128		properties: Vec<(string, bytes)>,129	) -> Result<()> {130		let caller = T::CrossAccountId::from_eth(caller);131		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133		let nesting_budget = self134			.recorder135			.weight_calls_budget(<StructureWeight<T>>::find_parent());136137		let properties = properties138			.into_iter()139			.map(|(key, value)| {140				let key = <Vec<u8>>::from(key)141					.try_into()142					.map_err(|_| "key too large")?;143144				let value = value.0.try_into().map_err(|_| "value too large")?;145146				Ok(Property { key, value })147			})148			.collect::<Result<Vec<_>>>()?;149150		<Pallet<T>>::set_token_properties(151			self,152			&caller,153			TokenId(token_id),154			properties.into_iter(),155			<Pallet<T>>::token_exists(&self, TokenId(token_id)),156			&nesting_budget,157		)158		.map_err(dispatch_to_evm::<T>)159	}160161	/// @notice Delete token property value.162	/// @dev Throws error if `msg.sender` has no permission to edit the property.163	/// @param tokenId ID of the token.164	/// @param key Property key.165	#[solidity(hide)]166	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {167		let caller = T::CrossAccountId::from_eth(caller);168		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169		let key = <Vec<u8>>::from(key)170			.try_into()171			.map_err(|_| "key too long")?;172173		let nesting_budget = self174			.recorder175			.weight_calls_budget(<StructureWeight<T>>::find_parent());176177		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)178			.map_err(dispatch_to_evm::<T>)179	}180181	/// @notice Delete token properties value.182	/// @dev Throws error if `msg.sender` has no permission to edit the property.183	/// @param tokenId ID of the token.184	/// @param keys Properties key.185	fn delete_properties(186		&mut self,187		token_id: uint256,188		caller: caller,189		keys: Vec<string>,190	) -> Result<()> {191		let caller = T::CrossAccountId::from_eth(caller);192		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193		let keys = keys194			.into_iter()195			.map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))196			.collect::<Result<Vec<_>>>()?;197198		let nesting_budget = self199			.recorder200			.weight_calls_budget(<StructureWeight<T>>::find_parent());201202		<Pallet<T>>::delete_token_properties(203			self,204			&caller,205			TokenId(token_id),206			keys.into_iter(),207			&nesting_budget,208		)209		.map_err(dispatch_to_evm::<T>)210	}211212	/// @notice Get token property value.213	/// @dev Throws error if key not found214	/// @param tokenId ID of the token.215	/// @param key Property key.216	/// @return Property value bytes217	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {218		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;219		let key = <Vec<u8>>::from(key)220			.try_into()221			.map_err(|_| "key too long")?;222223		let props = <TokenProperties<T>>::get((self.id, token_id));224		let prop = props.get(&key).ok_or("key not found")?;225226		Ok(prop.to_vec().into())227	}228}229230#[derive(ToLog)]231pub enum ERC721Events {232	/// @dev This emits when ownership of any NFT changes by any mechanism.233	///  This event emits when NFTs are created (`from` == 0) and destroyed234	///  (`to` == 0). Exception: during contract creation, any number of NFTs235	///  may be created and assigned without emitting Transfer. At the time of236	///  any transfer, the approved address for that NFT (if any) is reset to none.237	Transfer {238		#[indexed]239		from: address,240		#[indexed]241		to: address,242		#[indexed]243		token_id: uint256,244	},245	/// @dev This emits when the approved address for an NFT is changed or246	///  reaffirmed. The zero address indicates there is no approved address.247	///  When a Transfer event emits, this also indicates that the approved248	///  address for that NFT (if any) is reset to none.249	Approval {250		#[indexed]251		owner: address,252		#[indexed]253		approved: address,254		#[indexed]255		token_id: uint256,256	},257	/// @dev This emits when an operator is enabled or disabled for an owner.258	///  The operator can manage all NFTs of the owner.259	#[allow(dead_code)]260	ApprovalForAll {261		#[indexed]262		owner: address,263		#[indexed]264		operator: address,265		approved: bool,266	},267}268269#[derive(ToLog)]270pub enum ERC721UniqueMintableEvents {271	#[allow(dead_code)]272	MintingFinished {},273}274275/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension276/// @dev See https://eips.ethereum.org/EIPS/eip-721277#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]278impl<T: Config> NonfungibleHandle<T>279where280	T::AccountId: From<[u8; 32]>,281{282	/// @notice A descriptive name for a collection of NFTs in this contract283	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`284	#[solidity(hide, rename_selector = "name")]285	fn name_proxy(&self) -> Result<string> {286		self.name()287	}288289	/// @notice An abbreviated name for NFTs in this contract290	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`291	#[solidity(hide, rename_selector = "symbol")]292	fn symbol_proxy(&self) -> Result<string> {293		self.symbol()294	}295296	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.297	///298	/// @dev If the token has a `url` property and it is not empty, it is returned.299	///  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`.300	///  If the collection property `baseURI` is empty or absent, return "" (empty string)301	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix302	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).303	///304	/// @return token's const_metadata305	#[solidity(rename_selector = "tokenURI")]306	fn token_uri(&self, token_id: uint256) -> Result<string> {307		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;308309		match get_token_property(self, token_id_u32, &key::url()).as_deref() {310			Err(_) | Ok("") => (),311			Ok(url) => {312				return Ok(url.into());313			}314		};315316		let base_uri =317			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())318				.map(BoundedVec::into_inner)319				.map(string::from_utf8)320				.transpose()321				.map_err(|e| {322					Error::Revert(alloc::format!(323						"Can not convert value \"baseURI\" to string with error \"{}\"",324						e325					))326				})?;327328		let base_uri = match base_uri.as_deref() {329			None | Some("") => {330				return Ok("".into());331			}332			Some(base_uri) => base_uri.into(),333		};334335		Ok(336			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {337				Err(_) | Ok("") => base_uri,338				Ok(suffix) => base_uri + suffix,339			},340		)341	}342}343344/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension345/// @dev See https://eips.ethereum.org/EIPS/eip-721346#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]347impl<T: Config> NonfungibleHandle<T> {348	/// @notice Enumerate valid NFTs349	/// @param index A counter less than `totalSupply()`350	/// @return The token identifier for the `index`th NFT,351	///  (sort order not specified)352	fn token_by_index(&self, index: uint256) -> Result<uint256> {353		Ok(index)354	}355356	/// @dev Not implemented357	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {358		// TODO: Not implemetable359		Err("not implemented".into())360	}361362	/// @notice Count NFTs tracked by this contract363	/// @return A count of valid NFTs tracked by this contract, where each one of364	///  them has an assigned and queryable owner not equal to the zero address365	fn total_supply(&self) -> Result<uint256> {366		self.consume_store_reads(1)?;367		Ok(<Pallet<T>>::total_supply(self).into())368	}369}370371/// @title ERC-721 Non-Fungible Token Standard372/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md373#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]374impl<T: Config> NonfungibleHandle<T> {375	/// @notice Count all NFTs assigned to an owner376	/// @dev NFTs assigned to the zero address are considered invalid, and this377	///  function throws for queries about the zero address.378	/// @param owner An address for whom to query the balance379	/// @return The number of NFTs owned by `owner`, possibly zero380	fn balance_of(&self, owner: address) -> Result<uint256> {381		self.consume_store_reads(1)?;382		let owner = T::CrossAccountId::from_eth(owner);383		let balance = <AccountBalance<T>>::get((self.id, owner));384		Ok(balance.into())385	}386	/// @notice Find the owner of an NFT387	/// @dev NFTs assigned to zero address are considered invalid, and queries388	///  about them do throw.389	/// @param tokenId The identifier for an NFT390	/// @return The address of the owner of the NFT391	fn owner_of(&self, token_id: uint256) -> Result<address> {392		self.consume_store_reads(1)?;393		let token: TokenId = token_id.try_into()?;394		Ok(*<TokenData<T>>::get((self.id, token))395			.ok_or("token not found")?396			.owner397			.as_eth())398	}399	/// @dev Not implemented400	#[solidity(rename_selector = "safeTransferFrom")]401	fn safe_transfer_from_with_data(402		&mut self,403		_from: address,404		_to: address,405		_token_id: uint256,406		_data: bytes,407	) -> Result<void> {408		// TODO: Not implemetable409		Err("not implemented".into())410	}411	/// @dev Not implemented412	fn safe_transfer_from(413		&mut self,414		_from: address,415		_to: address,416		_token_id: uint256,417	) -> Result<void> {418		// TODO: Not implemetable419		Err("not implemented".into())420	}421422	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE423	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE424	///  THEY MAY BE PERMANENTLY LOST425	/// @dev Throws unless `msg.sender` is the current owner or an authorized426	///  operator for this NFT. Throws if `from` is not the current owner. Throws427	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.428	/// @param from The current owner of the NFT429	/// @param to The new owner430	/// @param tokenId The NFT to transfer431	#[weight(<SelfWeightOf<T>>::transfer_from())]432	fn transfer_from(433		&mut self,434		caller: caller,435		from: address,436		to: address,437		token_id: uint256,438	) -> Result<void> {439		let caller = T::CrossAccountId::from_eth(caller);440		let from = T::CrossAccountId::from_eth(from);441		let to = T::CrossAccountId::from_eth(to);442		let token = token_id.try_into()?;443		let budget = self444			.recorder445			.weight_calls_budget(<StructureWeight<T>>::find_parent());446447		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)448			.map_err(dispatch_to_evm::<T>)?;449		Ok(())450	}451452	/// @notice Set or reaffirm the approved address for an NFT453	/// @dev The zero address indicates there is no approved address.454	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized455	///  operator of the current owner.456	/// @param approved The new approved NFT controller457	/// @param tokenId The NFT to approve458	#[weight(<SelfWeightOf<T>>::approve())]459	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {460		let caller = T::CrossAccountId::from_eth(caller);461		let approved = T::CrossAccountId::from_eth(approved);462		let token = token_id.try_into()?;463464		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))465			.map_err(dispatch_to_evm::<T>)?;466		Ok(())467	}468469	/// @dev Not implemented470	fn set_approval_for_all(471		&mut self,472		_caller: caller,473		_operator: address,474		_approved: bool,475	) -> Result<void> {476		// TODO: Not implemetable477		Err("not implemented".into())478	}479480	/// @dev Not implemented481	fn get_approved(&self, _token_id: uint256) -> Result<address> {482		// TODO: Not implemetable483		Err("not implemented".into())484	}485486	/// @dev Not implemented487	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {488		// TODO: Not implemetable489		Err("not implemented".into())490	}491}492493/// @title ERC721 Token that can be irreversibly burned (destroyed).494#[solidity_interface(name = ERC721Burnable)]495impl<T: Config> NonfungibleHandle<T> {496	/// @notice Burns a specific ERC721 token.497	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized498	///  operator of the current owner.499	/// @param tokenId The NFT to approve500	#[weight(<SelfWeightOf<T>>::burn_item())]501	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {502		let caller = T::CrossAccountId::from_eth(caller);503		let token = token_id.try_into()?;504505		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;506		Ok(())507	}508}509510/// @title ERC721 minting logic.511#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]512impl<T: Config> NonfungibleHandle<T> {513	fn minting_finished(&self) -> Result<bool> {514		Ok(false)515	}516517	/// @notice Function to mint token.518	/// @param to The new owner519	/// @return uint256 The id of the newly minted token520	#[weight(<SelfWeightOf<T>>::create_item())]521	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {522		let token_id: uint256 = <TokensMinted<T>>::get(self.id)523			.checked_add(1)524			.ok_or("item id overflow")?525			.into();526		self.mint_check_id(caller, to, token_id)?;527		Ok(token_id)528	}529530	/// @notice Function to mint token.531	/// @dev `tokenId` should be obtained with `nextTokenId` method,532	///  unlike standard, you can't specify it manually533	/// @param to The new owner534	/// @param tokenId ID of the minted NFT535	#[solidity(hide, rename_selector = "mint")]536	#[weight(<SelfWeightOf<T>>::create_item())]537	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {538		let caller = T::CrossAccountId::from_eth(caller);539		let to = T::CrossAccountId::from_eth(to);540		let token_id: u32 = token_id.try_into()?;541		let budget = self542			.recorder543			.weight_calls_budget(<StructureWeight<T>>::find_parent());544545		if <TokensMinted<T>>::get(self.id)546			.checked_add(1)547			.ok_or("item id overflow")?548			!= token_id549		{550			return Err("item id should be next".into());551		}552553		<Pallet<T>>::create_item(554			self,555			&caller,556			CreateItemData::<T> {557				properties: BoundedVec::default(),558				owner: to,559			},560			&budget,561		)562		.map_err(dispatch_to_evm::<T>)?;563564		Ok(true)565	}566567	/// @notice Function to mint token with the given tokenUri.568	/// @param to The new owner569	/// @param tokenUri Token URI that would be stored in the NFT properties570	/// @return uint256 The id of the newly minted token571	#[solidity(rename_selector = "mintWithTokenURI")]572	#[weight(<SelfWeightOf<T>>::create_item())]573	fn mint_with_token_uri(574		&mut self,575		caller: caller,576		to: address,577		token_uri: string,578	) -> Result<uint256> {579		let token_id: uint256 = <TokensMinted<T>>::get(self.id)580			.checked_add(1)581			.ok_or("item id overflow")?582			.into();583		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584		Ok(token_id)585	}586587	/// @notice Function to mint token with the given tokenUri.588	/// @dev `tokenId` should be obtained with `nextTokenId` method,589	///  unlike standard, you can't specify it manually590	/// @param to The new owner591	/// @param tokenId ID of the minted NFT592	/// @param tokenUri Token URI that would be stored in the NFT properties593	#[solidity(hide, rename_selector = "mintWithTokenURI")]594	#[weight(<SelfWeightOf<T>>::create_item())]595	fn mint_with_token_uri_check_id(596		&mut self,597		caller: caller,598		to: address,599		token_id: uint256,600		token_uri: string,601	) -> Result<bool> {602		let key = key::url();603		let permission = get_token_permission::<T>(self.id, &key)?;604		if !permission.collection_admin {605			return Err("Operation is not allowed".into());606		}607608		let caller = T::CrossAccountId::from_eth(caller);609		let to = T::CrossAccountId::from_eth(to);610		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611		let budget = self612			.recorder613			.weight_calls_budget(<StructureWeight<T>>::find_parent());614615		if <TokensMinted<T>>::get(self.id)616			.checked_add(1)617			.ok_or("item id overflow")?618			!= token_id619		{620			return Err("item id should be next".into());621		}622623		let mut properties = CollectionPropertiesVec::default();624		properties625			.try_push(Property {626				key,627				value: token_uri628					.into_bytes()629					.try_into()630					.map_err(|_| "token uri is too long")?,631			})632			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634		<Pallet<T>>::create_item(635			self,636			&caller,637			CreateItemData::<T> {638				properties,639				owner: to,640			},641			&budget,642		)643		.map_err(dispatch_to_evm::<T>)?;644		Ok(true)645	}646647	/// @dev Not implemented648	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {649		Err("not implementable".into())650	}651}652653fn get_token_property<T: Config>(654	collection: &CollectionHandle<T>,655	token_id: u32,656	key: &up_data_structs::PropertyKey,657) -> Result<string> {658	collection.consume_store_reads(1)?;659	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))660		.map_err(|_| Error::Revert("Token properties not found".into()))?;661	if let Some(property) = properties.get(key) {662		return Ok(string::from_utf8_lossy(property).into());663	}664665	Err("Property tokenURI not found".into())666}667668fn get_token_permission<T: Config>(669	collection_id: CollectionId,670	key: &PropertyKey,671) -> Result<PropertyPermission> {672	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)673		.map_err(|_| Error::Revert("No permissions for collection".into()))?;674	let a = token_property_permissions675		.get(key)676		.map(Clone::clone)677		.ok_or_else(|| {678			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();679			Error::Revert(alloc::format!("No permission for key {}", key))680		})?;681	Ok(a)682}683684/// @title Unique extensions for ERC721.685#[solidity_interface(name = ERC721UniqueExtensions)]686impl<T: Config> NonfungibleHandle<T>687where688	T::AccountId: From<[u8; 32]>,689{690	/// @notice A descriptive name for a collection of NFTs in this contract691	fn name(&self) -> Result<string> {692		Ok(decode_utf16(self.name.iter().copied())693			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))694			.collect::<string>())695	}696697	/// @notice An abbreviated name for NFTs in this contract698	fn symbol(&self) -> Result<string> {699		Ok(string::from_utf8_lossy(&self.token_prefix).into())700	}701702	/// @notice Set or reaffirm the approved address for an NFT703	/// @dev The zero address indicates there is no approved address.704	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized705	///  operator of the current owner.706	/// @param approved The new substrate address approved NFT controller707	/// @param tokenId The NFT to approve708	#[weight(<SelfWeightOf<T>>::approve())]709	fn approve_cross(710		&mut self,711		caller: caller,712		approved: EthCrossAccount,713		token_id: uint256,714	) -> Result<void> {715		let caller = T::CrossAccountId::from_eth(caller);716		let approved = approved.into_sub_cross_account::<T>()?;717		let token = token_id.try_into()?;718719		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))720			.map_err(dispatch_to_evm::<T>)?;721		Ok(())722	}723724	/// @notice Transfer ownership of an NFT725	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`726	///  is the zero address. Throws if `tokenId` is not a valid NFT.727	/// @param to The new owner728	/// @param tokenId The NFT to transfer729	#[weight(<SelfWeightOf<T>>::transfer())]730	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {731		let caller = T::CrossAccountId::from_eth(caller);732		let to = T::CrossAccountId::from_eth(to);733		let token = token_id.try_into()?;734		let budget = self735			.recorder736			.weight_calls_budget(<StructureWeight<T>>::find_parent());737738		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;739		Ok(())740	}741	742	/// @notice Transfer ownership of an NFT743	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`744	///  is the zero address. Throws if `tokenId` is not a valid NFT.745	/// @param to The new owner746	/// @param tokenId The NFT to transfer747	#[weight(<SelfWeightOf<T>>::transfer())]748	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {749		let caller = T::CrossAccountId::from_eth(caller);750		let to = to.into_sub_cross_account::<T>()?;751		let token = token_id.try_into()?;752		let budget = self753			.recorder754			.weight_calls_budget(<StructureWeight<T>>::find_parent());755756		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;757		Ok(())758	}759	760	/// @notice Transfer ownership of an NFT from cross account address to cross account address761	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`762	///  is the zero address. Throws if `tokenId` is not a valid NFT.763	/// @param from Cross acccount address of current owner764	/// @param to Cross acccount address of new owner765	/// @param tokenId The NFT to transfer766	#[weight(<SelfWeightOf<T>>::transfer())]767	fn transfer_from_cross(768		&mut self,769		caller: caller,770		from: EthCrossAccount,771		to: EthCrossAccount,772		token_id: uint256,773	) -> Result<void> {774		let caller = T::CrossAccountId::from_eth(caller);775		let from = from.into_sub_cross_account::<T>()?;776		let to = to.into_sub_cross_account::<T>()?;777		let token_id = token_id.try_into()?;778		let budget = self779			.recorder780			.weight_calls_budget(<StructureWeight<T>>::find_parent());781		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)782			.map_err(dispatch_to_evm::<T>)?;783		Ok(())784	}785786	/// @notice Burns a specific ERC721 token.787	/// @dev Throws unless `msg.sender` is the current owner or an authorized788	///  operator for this NFT. Throws if `from` is not the current owner. Throws789	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.790	/// @param from The current owner of the NFT791	/// @param tokenId The NFT to transfer792	#[weight(<SelfWeightOf<T>>::burn_from())]793	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {794		let caller = T::CrossAccountId::from_eth(caller);795		let from = T::CrossAccountId::from_eth(from);796		let token = token_id.try_into()?;797		let budget = self798			.recorder799			.weight_calls_budget(<StructureWeight<T>>::find_parent());800801		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)802			.map_err(dispatch_to_evm::<T>)?;803		Ok(())804	}805806	/// @notice Burns a specific ERC721 token.807	/// @dev Throws unless `msg.sender` is the current owner or an authorized808	///  operator for this NFT. Throws if `from` is not the current owner. Throws809	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.810	/// @param from The current owner of the NFT811	/// @param tokenId The NFT to transfer812	#[weight(<SelfWeightOf<T>>::burn_from())]813	fn burn_from_cross(814		&mut self,815		caller: caller,816		from: EthCrossAccount,817		token_id: uint256,818	) -> Result<void> {819		let caller = T::CrossAccountId::from_eth(caller);820		let from = from.into_sub_cross_account::<T>()?;821		let token = token_id.try_into()?;822		let budget = self823			.recorder824			.weight_calls_budget(<StructureWeight<T>>::find_parent());825826		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)827			.map_err(dispatch_to_evm::<T>)?;828		Ok(())829	}830831	/// @notice Returns next free NFT ID.832	fn next_token_id(&self) -> Result<uint256> {833		self.consume_store_reads(1)?;834		Ok(<TokensMinted<T>>::get(self.id)835			.checked_add(1)836			.ok_or("item id overflow")?837			.into())838	}839840	/// @notice Function to mint multiple tokens.841	/// @dev `tokenIds` should be an array of consecutive numbers and first number842	///  should be obtained with `nextTokenId` method843	/// @param to The new owner844	/// @param tokenIds IDs of the minted NFTs845	#[solidity(hide)]846	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]847	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {848		let caller = T::CrossAccountId::from_eth(caller);849		let to = T::CrossAccountId::from_eth(to);850		let mut expected_index = <TokensMinted<T>>::get(self.id)851			.checked_add(1)852			.ok_or("item id overflow")?;853		let budget = self854			.recorder855			.weight_calls_budget(<StructureWeight<T>>::find_parent());856857		let total_tokens = token_ids.len();858		for id in token_ids.into_iter() {859			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;860			if id != expected_index {861				return Err("item id should be next".into());862			}863			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;864		}865		let data = (0..total_tokens)866			.map(|_| CreateItemData::<T> {867				properties: BoundedVec::default(),868				owner: to.clone(),869			})870			.collect();871872		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)873			.map_err(dispatch_to_evm::<T>)?;874		Ok(true)875	}876877	/// @notice Function to mint multiple tokens with the given tokenUris.878	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive879	///  numbers and first number should be obtained with `nextTokenId` method880	/// @param to The new owner881	/// @param tokens array of pairs of token ID and token URI for minted tokens882	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]883	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]884	fn mint_bulk_with_token_uri(885		&mut self,886		caller: caller,887		to: address,888		tokens: Vec<(uint256, string)>,889	) -> Result<bool> {890		let key = key::url();891		let caller = T::CrossAccountId::from_eth(caller);892		let to = T::CrossAccountId::from_eth(to);893		let mut expected_index = <TokensMinted<T>>::get(self.id)894			.checked_add(1)895			.ok_or("item id overflow")?;896		let budget = self897			.recorder898			.weight_calls_budget(<StructureWeight<T>>::find_parent());899900		let mut data = Vec::with_capacity(tokens.len());901		for (id, token_uri) in tokens {902			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;903			if id != expected_index {904				return Err("item id should be next".into());905			}906			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;907908			let mut properties = CollectionPropertiesVec::default();909			properties910				.try_push(Property {911					key: key.clone(),912					value: token_uri913						.into_bytes()914						.try_into()915						.map_err(|_| "token uri is too long")?,916				})917				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;918919			data.push(CreateItemData::<T> {920				properties,921				owner: to.clone(),922			});923		}924925		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)926			.map_err(dispatch_to_evm::<T>)?;927		Ok(true)928	}929}930931#[solidity_interface(932	name = UniqueNFT,933	is(934		ERC721,935		ERC721Enumerable,936		ERC721UniqueExtensions,937		ERC721UniqueMintable,938		ERC721Burnable,939		ERC721Metadata(if(this.flags.erc721metadata)),940		Collection(via(common_mut returns CollectionHandle<T>)),941		TokenProperties,942	)943)]944impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}945946// Not a tests, but code generators947generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);948generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);949950impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>951where952	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,953{954	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");955956	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {957		call::<T, UniqueNFTCall<T>, _, _>(handle, self)958	}959}
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::{28	abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29	weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34	CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},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.0.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 Set token properties value.120	/// @dev Throws error if `msg.sender` has no permission to edit the property.121	/// @param tokenId ID of the token.122	/// @param properties settable properties123	#[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124	fn set_properties(125		&mut self,126		caller: caller,127		token_id: uint256,128		properties: Vec<(string, bytes)>,129	) -> Result<()> {130		let caller = T::CrossAccountId::from_eth(caller);131		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133		let nesting_budget = self134			.recorder135			.weight_calls_budget(<StructureWeight<T>>::find_parent());136137		let properties = properties138			.into_iter()139			.map(|(key, value)| {140				let key = <Vec<u8>>::from(key)141					.try_into()142					.map_err(|_| "key too large")?;143144				let value = value.0.try_into().map_err(|_| "value too large")?;145146				Ok(Property { key, value })147			})148			.collect::<Result<Vec<_>>>()?;149150		<Pallet<T>>::set_token_properties(151			self,152			&caller,153			TokenId(token_id),154			properties.into_iter(),155			<Pallet<T>>::token_exists(&self, TokenId(token_id)),156			&nesting_budget,157		)158		.map_err(dispatch_to_evm::<T>)159	}160161	/// @notice Delete token property value.162	/// @dev Throws error if `msg.sender` has no permission to edit the property.163	/// @param tokenId ID of the token.164	/// @param key Property key.165	#[solidity(hide)]166	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {167		let caller = T::CrossAccountId::from_eth(caller);168		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169		let key = <Vec<u8>>::from(key)170			.try_into()171			.map_err(|_| "key too long")?;172173		let nesting_budget = self174			.recorder175			.weight_calls_budget(<StructureWeight<T>>::find_parent());176177		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)178			.map_err(dispatch_to_evm::<T>)179	}180181	/// @notice Delete token properties value.182	/// @dev Throws error if `msg.sender` has no permission to edit the property.183	/// @param tokenId ID of the token.184	/// @param keys Properties key.185	fn delete_properties(186		&mut self,187		token_id: uint256,188		caller: caller,189		keys: Vec<string>,190	) -> Result<()> {191		let caller = T::CrossAccountId::from_eth(caller);192		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193		let keys = keys194			.into_iter()195			.map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))196			.collect::<Result<Vec<_>>>()?;197198		let nesting_budget = self199			.recorder200			.weight_calls_budget(<StructureWeight<T>>::find_parent());201202		<Pallet<T>>::delete_token_properties(203			self,204			&caller,205			TokenId(token_id),206			keys.into_iter(),207			&nesting_budget,208		)209		.map_err(dispatch_to_evm::<T>)210	}211212	/// @notice Get token property value.213	/// @dev Throws error if key not found214	/// @param tokenId ID of the token.215	/// @param key Property key.216	/// @return Property value bytes217	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {218		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;219		let key = <Vec<u8>>::from(key)220			.try_into()221			.map_err(|_| "key too long")?;222223		let props = <TokenProperties<T>>::get((self.id, token_id));224		let prop = props.get(&key).ok_or("key not found")?;225226		Ok(prop.to_vec().into())227	}228}229230#[derive(ToLog)]231pub enum ERC721Events {232	/// @dev This emits when ownership of any NFT changes by any mechanism.233	///  This event emits when NFTs are created (`from` == 0) and destroyed234	///  (`to` == 0). Exception: during contract creation, any number of NFTs235	///  may be created and assigned without emitting Transfer. At the time of236	///  any transfer, the approved address for that NFT (if any) is reset to none.237	Transfer {238		#[indexed]239		from: address,240		#[indexed]241		to: address,242		#[indexed]243		token_id: uint256,244	},245	/// @dev This emits when the approved address for an NFT is changed or246	///  reaffirmed. The zero address indicates there is no approved address.247	///  When a Transfer event emits, this also indicates that the approved248	///  address for that NFT (if any) is reset to none.249	Approval {250		#[indexed]251		owner: address,252		#[indexed]253		approved: address,254		#[indexed]255		token_id: uint256,256	},257	/// @dev This emits when an operator is enabled or disabled for an owner.258	///  The operator can manage all NFTs of the owner.259	#[allow(dead_code)]260	ApprovalForAll {261		#[indexed]262		owner: address,263		#[indexed]264		operator: address,265		approved: bool,266	},267}268269#[derive(ToLog)]270pub enum ERC721UniqueMintableEvents {271	#[allow(dead_code)]272	MintingFinished {},273}274275/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension276/// @dev See https://eips.ethereum.org/EIPS/eip-721277#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]278impl<T: Config> NonfungibleHandle<T>279where280	T::AccountId: From<[u8; 32]>,281{282	/// @notice A descriptive name for a collection of NFTs in this contract283	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`284	#[solidity(hide, rename_selector = "name")]285	fn name_proxy(&self) -> Result<string> {286		self.name()287	}288289	/// @notice An abbreviated name for NFTs in this contract290	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`291	#[solidity(hide, rename_selector = "symbol")]292	fn symbol_proxy(&self) -> Result<string> {293		self.symbol()294	}295296	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.297	///298	/// @dev If the token has a `url` property and it is not empty, it is returned.299	///  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`.300	///  If the collection property `baseURI` is empty or absent, return "" (empty string)301	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix302	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).303	///304	/// @return token's const_metadata305	#[solidity(rename_selector = "tokenURI")]306	fn token_uri(&self, token_id: uint256) -> Result<string> {307		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;308309		match get_token_property(self, token_id_u32, &key::url()).as_deref() {310			Err(_) | Ok("") => (),311			Ok(url) => {312				return Ok(url.into());313			}314		};315316		let base_uri =317			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())318				.map(BoundedVec::into_inner)319				.map(string::from_utf8)320				.transpose()321				.map_err(|e| {322					Error::Revert(alloc::format!(323						"Can not convert value \"baseURI\" to string with error \"{}\"",324						e325					))326				})?;327328		let base_uri = match base_uri.as_deref() {329			None | Some("") => {330				return Ok("".into());331			}332			Some(base_uri) => base_uri.into(),333		};334335		Ok(336			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {337				Err(_) | Ok("") => base_uri,338				Ok(suffix) => base_uri + suffix,339			},340		)341	}342}343344/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension345/// @dev See https://eips.ethereum.org/EIPS/eip-721346#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]347impl<T: Config> NonfungibleHandle<T> {348	/// @notice Enumerate valid NFTs349	/// @param index A counter less than `totalSupply()`350	/// @return The token identifier for the `index`th NFT,351	///  (sort order not specified)352	fn token_by_index(&self, index: uint256) -> Result<uint256> {353		Ok(index)354	}355356	/// @dev Not implemented357	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {358		// TODO: Not implemetable359		Err("not implemented".into())360	}361362	/// @notice Count NFTs tracked by this contract363	/// @return A count of valid NFTs tracked by this contract, where each one of364	///  them has an assigned and queryable owner not equal to the zero address365	fn total_supply(&self) -> Result<uint256> {366		self.consume_store_reads(1)?;367		Ok(<Pallet<T>>::total_supply(self).into())368	}369}370371/// @title ERC-721 Non-Fungible Token Standard372/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md373#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]374impl<T: Config> NonfungibleHandle<T> {375	/// @notice Count all NFTs assigned to an owner376	/// @dev NFTs assigned to the zero address are considered invalid, and this377	///  function throws for queries about the zero address.378	/// @param owner An address for whom to query the balance379	/// @return The number of NFTs owned by `owner`, possibly zero380	fn balance_of(&self, owner: address) -> Result<uint256> {381		self.consume_store_reads(1)?;382		let owner = T::CrossAccountId::from_eth(owner);383		let balance = <AccountBalance<T>>::get((self.id, owner));384		Ok(balance.into())385	}386	/// @notice Find the owner of an NFT387	/// @dev NFTs assigned to zero address are considered invalid, and queries388	///  about them do throw.389	/// @param tokenId The identifier for an NFT390	/// @return The address of the owner of the NFT391	fn owner_of(&self, token_id: uint256) -> Result<address> {392		self.consume_store_reads(1)?;393		let token: TokenId = token_id.try_into()?;394		Ok(*<TokenData<T>>::get((self.id, token))395			.ok_or("token not found")?396			.owner397			.as_eth())398	}399	/// @dev Not implemented400	#[solidity(rename_selector = "safeTransferFrom")]401	fn safe_transfer_from_with_data(402		&mut self,403		_from: address,404		_to: address,405		_token_id: uint256,406		_data: bytes,407	) -> Result<void> {408		// TODO: Not implemetable409		Err("not implemented".into())410	}411	/// @dev Not implemented412	fn safe_transfer_from(413		&mut self,414		_from: address,415		_to: address,416		_token_id: uint256,417	) -> Result<void> {418		// TODO: Not implemetable419		Err("not implemented".into())420	}421422	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE423	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE424	///  THEY MAY BE PERMANENTLY LOST425	/// @dev Throws unless `msg.sender` is the current owner or an authorized426	///  operator for this NFT. Throws if `from` is not the current owner. Throws427	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.428	/// @param from The current owner of the NFT429	/// @param to The new owner430	/// @param tokenId The NFT to transfer431	#[weight(<SelfWeightOf<T>>::transfer_from())]432	fn transfer_from(433		&mut self,434		caller: caller,435		from: address,436		to: address,437		token_id: uint256,438	) -> Result<void> {439		let caller = T::CrossAccountId::from_eth(caller);440		let from = T::CrossAccountId::from_eth(from);441		let to = T::CrossAccountId::from_eth(to);442		let token = token_id.try_into()?;443		let budget = self444			.recorder445			.weight_calls_budget(<StructureWeight<T>>::find_parent());446447		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)448			.map_err(dispatch_to_evm::<T>)?;449		Ok(())450	}451452	/// @notice Set or reaffirm the approved address for an NFT453	/// @dev The zero address indicates there is no approved address.454	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized455	///  operator of the current owner.456	/// @param approved The new approved NFT controller457	/// @param tokenId The NFT to approve458	#[weight(<SelfWeightOf<T>>::approve())]459	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {460		let caller = T::CrossAccountId::from_eth(caller);461		let approved = T::CrossAccountId::from_eth(approved);462		let token = token_id.try_into()?;463464		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))465			.map_err(dispatch_to_evm::<T>)?;466		Ok(())467	}468469	/// @dev Not implemented470	fn set_approval_for_all(471		&mut self,472		_caller: caller,473		_operator: address,474		_approved: bool,475	) -> Result<void> {476		// TODO: Not implemetable477		Err("not implemented".into())478	}479480	/// @dev Not implemented481	fn get_approved(&self, _token_id: uint256) -> Result<address> {482		// TODO: Not implemetable483		Err("not implemented".into())484	}485486	/// @dev Not implemented487	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {488		// TODO: Not implemetable489		Err("not implemented".into())490	}491}492493/// @title ERC721 Token that can be irreversibly burned (destroyed).494#[solidity_interface(name = ERC721Burnable)]495impl<T: Config> NonfungibleHandle<T> {496	/// @notice Burns a specific ERC721 token.497	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized498	///  operator of the current owner.499	/// @param tokenId The NFT to approve500	#[weight(<SelfWeightOf<T>>::burn_item())]501	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {502		let caller = T::CrossAccountId::from_eth(caller);503		let token = token_id.try_into()?;504505		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;506		Ok(())507	}508}509510/// @title ERC721 minting logic.511#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]512impl<T: Config> NonfungibleHandle<T> {513	fn minting_finished(&self) -> Result<bool> {514		Ok(false)515	}516517	/// @notice Function to mint token.518	/// @param to The new owner519	/// @return uint256 The id of the newly minted token520	#[weight(<SelfWeightOf<T>>::create_item())]521	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {522		let token_id: uint256 = <TokensMinted<T>>::get(self.id)523			.checked_add(1)524			.ok_or("item id overflow")?525			.into();526		self.mint_check_id(caller, to, token_id)?;527		Ok(token_id)528	}529530	/// @notice Function to mint token.531	/// @dev `tokenId` should be obtained with `nextTokenId` method,532	///  unlike standard, you can't specify it manually533	/// @param to The new owner534	/// @param tokenId ID of the minted NFT535	#[solidity(hide, rename_selector = "mint")]536	#[weight(<SelfWeightOf<T>>::create_item())]537	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {538		let caller = T::CrossAccountId::from_eth(caller);539		let to = T::CrossAccountId::from_eth(to);540		let token_id: u32 = token_id.try_into()?;541		let budget = self542			.recorder543			.weight_calls_budget(<StructureWeight<T>>::find_parent());544545		if <TokensMinted<T>>::get(self.id)546			.checked_add(1)547			.ok_or("item id overflow")?548			!= token_id549		{550			return Err("item id should be next".into());551		}552553		<Pallet<T>>::create_item(554			self,555			&caller,556			CreateItemData::<T> {557				properties: BoundedVec::default(),558				owner: to,559			},560			&budget,561		)562		.map_err(dispatch_to_evm::<T>)?;563564		Ok(true)565	}566567	/// @notice Function to mint token with the given tokenUri.568	/// @param to The new owner569	/// @param tokenUri Token URI that would be stored in the NFT properties570	/// @return uint256 The id of the newly minted token571	#[solidity(rename_selector = "mintWithTokenURI")]572	#[weight(<SelfWeightOf<T>>::create_item())]573	fn mint_with_token_uri(574		&mut self,575		caller: caller,576		to: address,577		token_uri: string,578	) -> Result<uint256> {579		let token_id: uint256 = <TokensMinted<T>>::get(self.id)580			.checked_add(1)581			.ok_or("item id overflow")?582			.into();583		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584		Ok(token_id)585	}586587	/// @notice Function to mint token with the given tokenUri.588	/// @dev `tokenId` should be obtained with `nextTokenId` method,589	///  unlike standard, you can't specify it manually590	/// @param to The new owner591	/// @param tokenId ID of the minted NFT592	/// @param tokenUri Token URI that would be stored in the NFT properties593	#[solidity(hide, rename_selector = "mintWithTokenURI")]594	#[weight(<SelfWeightOf<T>>::create_item())]595	fn mint_with_token_uri_check_id(596		&mut self,597		caller: caller,598		to: address,599		token_id: uint256,600		token_uri: string,601	) -> Result<bool> {602		let key = key::url();603		let permission = get_token_permission::<T>(self.id, &key)?;604		if !permission.collection_admin {605			return Err("Operation is not allowed".into());606		}607608		let caller = T::CrossAccountId::from_eth(caller);609		let to = T::CrossAccountId::from_eth(to);610		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611		let budget = self612			.recorder613			.weight_calls_budget(<StructureWeight<T>>::find_parent());614615		if <TokensMinted<T>>::get(self.id)616			.checked_add(1)617			.ok_or("item id overflow")?618			!= token_id619		{620			return Err("item id should be next".into());621		}622623		let mut properties = CollectionPropertiesVec::default();624		properties625			.try_push(Property {626				key,627				value: token_uri628					.into_bytes()629					.try_into()630					.map_err(|_| "token uri is too long")?,631			})632			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634		<Pallet<T>>::create_item(635			self,636			&caller,637			CreateItemData::<T> {638				properties,639				owner: to,640			},641			&budget,642		)643		.map_err(dispatch_to_evm::<T>)?;644		Ok(true)645	}646647	/// @dev Not implemented648	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {649		Err("not implementable".into())650	}651}652653fn get_token_property<T: Config>(654	collection: &CollectionHandle<T>,655	token_id: u32,656	key: &up_data_structs::PropertyKey,657) -> Result<string> {658	collection.consume_store_reads(1)?;659	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))660		.map_err(|_| Error::Revert("Token properties not found".into()))?;661	if let Some(property) = properties.get(key) {662		return Ok(string::from_utf8_lossy(property).into());663	}664665	Err("Property tokenURI not found".into())666}667668fn get_token_permission<T: Config>(669	collection_id: CollectionId,670	key: &PropertyKey,671) -> Result<PropertyPermission> {672	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)673		.map_err(|_| Error::Revert("No permissions for collection".into()))?;674	let a = token_property_permissions675		.get(key)676		.map(Clone::clone)677		.ok_or_else(|| {678			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();679			Error::Revert(alloc::format!("No permission for key {}", key))680		})?;681	Ok(a)682}683684/// @title Unique extensions for ERC721.685#[solidity_interface(name = ERC721UniqueExtensions)]686impl<T: Config> NonfungibleHandle<T>687where688	T::AccountId: From<[u8; 32]>,689{690	/// @notice A descriptive name for a collection of NFTs in this contract691	fn name(&self) -> Result<string> {692		Ok(decode_utf16(self.name.iter().copied())693			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))694			.collect::<string>())695	}696697	/// @notice An abbreviated name for NFTs in this contract698	fn symbol(&self) -> Result<string> {699		Ok(string::from_utf8_lossy(&self.token_prefix).into())700	}701702	/// @notice Set or reaffirm the approved address for an NFT703	/// @dev The zero address indicates there is no approved address.704	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized705	///  operator of the current owner.706	/// @param approved The new substrate address approved NFT controller707	/// @param tokenId The NFT to approve708	#[weight(<SelfWeightOf<T>>::approve())]709	fn approve_cross(710		&mut self,711		caller: caller,712		approved: EthCrossAccount,713		token_id: uint256,714	) -> Result<void> {715		let caller = T::CrossAccountId::from_eth(caller);716		let approved = approved.into_sub_cross_account::<T>()?;717		let token = token_id.try_into()?;718719		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))720			.map_err(dispatch_to_evm::<T>)?;721		Ok(())722	}723724	/// @notice Transfer ownership of an NFT725	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`726	///  is the zero address. Throws if `tokenId` is not a valid NFT.727	/// @param to The new owner728	/// @param tokenId The NFT to transfer729	#[weight(<SelfWeightOf<T>>::transfer())]730	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {731		let caller = T::CrossAccountId::from_eth(caller);732		let to = T::CrossAccountId::from_eth(to);733		let token = token_id.try_into()?;734		let budget = self735			.recorder736			.weight_calls_budget(<StructureWeight<T>>::find_parent());737738		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;739		Ok(())740	}741742	/// @notice Transfer ownership of an NFT743	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`744	///  is the zero address. Throws if `tokenId` is not a valid NFT.745	/// @param to The new owner746	/// @param tokenId The NFT to transfer747	#[weight(<SelfWeightOf<T>>::transfer())]748	fn transfer_cross(749		&mut self,750		caller: caller,751		to: EthCrossAccount,752		token_id: uint256,753	) -> Result<void> {754		let caller = T::CrossAccountId::from_eth(caller);755		let to = to.into_sub_cross_account::<T>()?;756		let token = token_id.try_into()?;757		let budget = self758			.recorder759			.weight_calls_budget(<StructureWeight<T>>::find_parent());760761		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;762		Ok(())763	}764765	/// @notice Transfer ownership of an NFT from cross account address to cross account address766	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`767	///  is the zero address. Throws if `tokenId` is not a valid NFT.768	/// @param from Cross acccount address of current owner769	/// @param to Cross acccount address of new owner770	/// @param tokenId The NFT to transfer771	#[weight(<SelfWeightOf<T>>::transfer())]772	fn transfer_from_cross(773		&mut self,774		caller: caller,775		from: EthCrossAccount,776		to: EthCrossAccount,777		token_id: uint256,778	) -> Result<void> {779		let caller = T::CrossAccountId::from_eth(caller);780		let from = from.into_sub_cross_account::<T>()?;781		let to = to.into_sub_cross_account::<T>()?;782		let token_id = token_id.try_into()?;783		let budget = self784			.recorder785			.weight_calls_budget(<StructureWeight<T>>::find_parent());786		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)787			.map_err(dispatch_to_evm::<T>)?;788		Ok(())789	}790791	/// @notice Burns a specific ERC721 token.792	/// @dev Throws unless `msg.sender` is the current owner or an authorized793	///  operator for this NFT. Throws if `from` is not the current owner. Throws794	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.795	/// @param from The current owner of the NFT796	/// @param tokenId The NFT to transfer797	#[weight(<SelfWeightOf<T>>::burn_from())]798	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {799		let caller = T::CrossAccountId::from_eth(caller);800		let from = T::CrossAccountId::from_eth(from);801		let token = token_id.try_into()?;802		let budget = self803			.recorder804			.weight_calls_budget(<StructureWeight<T>>::find_parent());805806		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)807			.map_err(dispatch_to_evm::<T>)?;808		Ok(())809	}810811	/// @notice Burns a specific ERC721 token.812	/// @dev Throws unless `msg.sender` is the current owner or an authorized813	///  operator for this NFT. Throws if `from` is not the current owner. Throws814	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.815	/// @param from The current owner of the NFT816	/// @param tokenId The NFT to transfer817	#[weight(<SelfWeightOf<T>>::burn_from())]818	fn burn_from_cross(819		&mut self,820		caller: caller,821		from: EthCrossAccount,822		token_id: uint256,823	) -> Result<void> {824		let caller = T::CrossAccountId::from_eth(caller);825		let from = from.into_sub_cross_account::<T>()?;826		let token = token_id.try_into()?;827		let budget = self828			.recorder829			.weight_calls_budget(<StructureWeight<T>>::find_parent());830831		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)832			.map_err(dispatch_to_evm::<T>)?;833		Ok(())834	}835836	/// @notice Returns next free NFT ID.837	fn next_token_id(&self) -> Result<uint256> {838		self.consume_store_reads(1)?;839		Ok(<TokensMinted<T>>::get(self.id)840			.checked_add(1)841			.ok_or("item id overflow")?842			.into())843	}844845	/// @notice Function to mint multiple tokens.846	/// @dev `tokenIds` should be an array of consecutive numbers and first number847	///  should be obtained with `nextTokenId` method848	/// @param to The new owner849	/// @param tokenIds IDs of the minted NFTs850	#[solidity(hide)]851	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]852	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {853		let caller = T::CrossAccountId::from_eth(caller);854		let to = T::CrossAccountId::from_eth(to);855		let mut expected_index = <TokensMinted<T>>::get(self.id)856			.checked_add(1)857			.ok_or("item id overflow")?;858		let budget = self859			.recorder860			.weight_calls_budget(<StructureWeight<T>>::find_parent());861862		let total_tokens = token_ids.len();863		for id in token_ids.into_iter() {864			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;865			if id != expected_index {866				return Err("item id should be next".into());867			}868			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;869		}870		let data = (0..total_tokens)871			.map(|_| CreateItemData::<T> {872				properties: BoundedVec::default(),873				owner: to.clone(),874			})875			.collect();876877		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)878			.map_err(dispatch_to_evm::<T>)?;879		Ok(true)880	}881882	/// @notice Function to mint multiple tokens with the given tokenUris.883	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive884	///  numbers and first number should be obtained with `nextTokenId` method885	/// @param to The new owner886	/// @param tokens array of pairs of token ID and token URI for minted tokens887	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]888	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]889	fn mint_bulk_with_token_uri(890		&mut self,891		caller: caller,892		to: address,893		tokens: Vec<(uint256, string)>,894	) -> Result<bool> {895		let key = key::url();896		let caller = T::CrossAccountId::from_eth(caller);897		let to = T::CrossAccountId::from_eth(to);898		let mut expected_index = <TokensMinted<T>>::get(self.id)899			.checked_add(1)900			.ok_or("item id overflow")?;901		let budget = self902			.recorder903			.weight_calls_budget(<StructureWeight<T>>::find_parent());904905		let mut data = Vec::with_capacity(tokens.len());906		for (id, token_uri) in tokens {907			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;908			if id != expected_index {909				return Err("item id should be next".into());910			}911			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;912913			let mut properties = CollectionPropertiesVec::default();914			properties915				.try_push(Property {916					key: key.clone(),917					value: token_uri918						.into_bytes()919						.try_into()920						.map_err(|_| "token uri is too long")?,921				})922				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;923924			data.push(CreateItemData::<T> {925				properties,926				owner: to.clone(),927			});928		}929930		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)931			.map_err(dispatch_to_evm::<T>)?;932		Ok(true)933	}934}935936#[solidity_interface(937	name = UniqueNFT,938	is(939		ERC721,940		ERC721Enumerable,941		ERC721UniqueExtensions,942		ERC721UniqueMintable,943		ERC721Burnable,944		ERC721Metadata(if(this.flags.erc721metadata)),945		Collection(via(common_mut returns CollectionHandle<T>)),946		TokenProperties,947	)948)]949impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}950951// Not a tests, but code generators952generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);953generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);954955impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>956where957	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,958{959	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");960961	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {962		call::<T, UniqueNFTCall<T>, _, _>(handle, self)963	}964}
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -756,7 +756,12 @@
 	/// @param to The new owner
 	/// @param tokenId The RFT to transfer
 	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
-	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {
+	fn transfer_cross(
+		&mut self,
+		caller: caller,
+		to: EthCrossAccount,
+		token_id: uint256,
+	) -> Result<void> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let token = token_id.try_into()?;
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -117,7 +117,7 @@
   });
 
   itEth('ERC721UniqueExtensions support', async ({helper}) => {
-    await checkInterface(helper, '0x244543ee', true, true);
+    await checkInterface(helper, '0x0e9fc611', true, true);
   });
 
   itEth('ERC721Burnable - 0x42966c68 - support', async ({helper}) => {