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

difftreelog

feat calculate signature in compile time

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

11 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1096,6 +1096,26 @@
 checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
 
 [[package]]
+name = "const_format"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
 name = "constant_time_eq"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2353,6 +2373,7 @@
 version = "0.1.3"
 dependencies = [
  "concat-idents",
+ "const_format",
  "ethereum",
  "evm-coder-procedural",
  "evm-core 0.35.0 (git+https://github.com/uniquenetwork/evm?branch=unique-polkadot-v0.9.30)",
@@ -2362,6 +2383,7 @@
  "impl-trait-for-tuples",
  "pallet-evm 6.0.0-dev (git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit)",
  "primitive-types",
+ "sha3-const",
  "similar-asserts",
  "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30)",
 ]
@@ -11085,6 +11107,12 @@
 ]
 
 [[package]]
+name = "sha3-const"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9625d558d174dbdc711248b479271c0fdca39e9d3d67f6e890b3d4251fbf8e"
+
+[[package]]
 name = "sharded-slab"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedcrates/evm-coder/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -5,6 +5,10 @@
 edition = "2021"
 
 [dependencies]
+const_format = { version = "0.2.26", default-features = false }
+sha3-const = { version = "0.1.0", default-features = false }
+# Ethereum uses keccak (=sha3) for selectors
+# sha3 = "0.10.1"
 # evm-coder reexports those proc-macro
 evm-coder-procedural = { path = "./procedural" }
 # Evm uses primitive-types for H160, H256 and others
modifiedcrates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -20,6 +20,7 @@
 // about Procedural Macros in Rust book:
 // https://doc.rust-lang.org/reference/procedural-macros.html
 
+use proc_macro2::{TokenStream, Group};
 use quote::{quote, ToTokens};
 use inflector::cases;
 use std::fmt::Write;
@@ -328,6 +329,7 @@
 	}
 }
 
+#[derive(Debug)]
 enum AbiType {
 	// type
 	Plain(Ident),
@@ -473,6 +475,7 @@
 	}
 }
 
+#[derive(Debug)]
 struct MethodArg {
 	name: Ident,
 	camel_name: String,
@@ -722,13 +725,25 @@
 		}
 	}
 
+	fn expand_selector(&self) -> proc_macro2::TokenStream {
+		let custom_signature = self.expand_custom_signature();
+		quote! {
+			 {
+				let a = ::evm_coder::sha3_const::Keccak256::new()
+					.update(#custom_signature.as_bytes())
+					.finalize();
+				[a[0], a[1], a[2], a[3]]
+			}
+		}
+	}
+
 	fn expand_const(&self) -> proc_macro2::TokenStream {
 		let screaming_name = &self.screaming_name;
-		let selector = u32::to_be_bytes(self.selector);
 		let selector_str = &self.selector_str;
+		let selector = &self.expand_selector();
 		quote! {
 			#[doc = #selector_str]
-			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
+			const #screaming_name: ::evm_coder::types::bytes4 = #selector;
 		}
 	}
 
@@ -831,6 +846,59 @@
 		}
 	}
 
+	fn expand_custom_signature(&self) -> proc_macro2::TokenStream {
+		let mut first_comma = true;
+		let mut custom_signature = TokenStream::new();
+		let mut template = self.camel_name.clone() + "(";
+		self.args
+			.iter()
+			.filter_map(|a| {
+				if a.is_special() {
+					return None;
+				};
+
+				match a.ty {
+					AbiType::Plain(ref ident) => Some(ident),
+					_ => None,
+				}
+			})
+			.for_each(|ident| {
+				if !first_comma {
+					custom_signature.extend(quote!(,));
+					template.push(',');
+				} else {
+					first_comma = false;
+				};
+				template.push_str("{}");
+				let ident_str = ident.to_string();
+				match ident_str.as_str() {
+					"address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128"
+					| "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller"
+					| "bool" | "" => {
+						custom_signature.extend(quote!(#ident_str));
+					}
+					_ => {
+						custom_signature.extend(quote! {
+							#ident::SIGNATURE_STRING
+						});
+					}
+				}
+			});
+
+		template.push(')');
+		let mut template = quote!(#template);
+		template.extend(quote!(,));
+		template.extend(custom_signature);
+		let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template);
+		let mut custom_signature = quote! {
+			::evm_coder::const_format::formatcp!
+		};
+		custom_signature.extend(custom_signature_group.to_token_stream());
+
+		// println!("!!!!! {}", custom_signature);
+		custom_signature
+	}
+
 	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
 		let camel_name = &self.camel_name;
 		let mutability = match self.mutability {
@@ -847,15 +915,17 @@
 			.map(MethodArg::expand_solidity_argument);
 		let docs = &self.docs;
 		let selector_str = &self.selector_str;
-		let selector = self.selector;
+		let selector = &self.expand_selector();
 		let hide = self.hide;
+		let custom_signature = self.expand_custom_signature();
 		let is_payable = self.has_value_args;
 		quote! {
 			SolidityFunction {
 				docs: &[#(#docs),*],
 				selector_str: #selector_str,
-				selector: #selector,
 				hide: #hide,
+				selector: u32::from_be_bytes(#selector),
+				custom_signature: #custom_signature,
 				name: #camel_name,
 				mutability: #mutability,
 				is_payable: #is_payable,
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -90,6 +90,8 @@
 pub use evm_coder_procedural::solidity;
 /// See [`solidity_interface`]
 pub use evm_coder_procedural::weight;
+pub use const_format;
+pub use sha3_const;
 
 /// Derives [`ToLog`] for enum
 ///
@@ -227,6 +229,14 @@
 		}
 	}
 
+	impl SignatureString for EthCrossAccount {
+		const SIGNATURE_STRING: &'static str = "(address,uint256)";
+	}
+
+	pub trait SignatureString {
+		const SIGNATURE_STRING: &'static str;
+	}
+
 	/// Convert `CrossAccountId` to `uint256`.
 	pub fn convert_cross_account_to_uint256<T: pallet_evm::account::Config>(
 		from: &T::CrossAccountId,
@@ -348,6 +358,18 @@
 		assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
 	}
 
+	// #[test]
+	// fn function_selector_generation_1() {
+	// 	assert_eq!(
+	// 		fn_selector!(transferFromCrossAccountToCrossAccount(
+	// 			EthCrossAccount,
+	// 			EthCrossAccount,
+	// 			uint256
+	// 		)),
+	// 		2543295963
+	// 	);
+	// }
+
 	#[test]
 	fn event_topic_generation() {
 		assert_eq!(
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -35,6 +35,12 @@
 use crate::types::*;
 
 #[derive(Default)]
+pub struct FunctionSelectorMaker {
+	pub name: string,
+	pub args: Vec<fn() -> string>,
+}
+
+#[derive(Default)]
 pub struct TypeCollector {
 	/// Code => id
 	/// id ordering is required to perform topo-sort on the resulting data
@@ -482,11 +488,25 @@
 	View,
 	Mutable,
 }
+
+// fn fn_selector_str(input: &str) -> u32 {
+// 	use sha3::Digest;
+// 	let mut hasher = sha3::Keccak256::new();
+// 	hasher.update(input.as_bytes());
+// 	let result = hasher.finalize();
+
+// 	let mut selector_bytes = [0; 4];
+// 	selector_bytes.copy_from_slice(&result[0..4]);
+
+// 	u32::from_be_bytes(selector_bytes)
+// }
+
 pub struct SolidityFunction<A, R> {
 	pub docs: &'static [&'static str],
 	pub selector_str: &'static str,
 	pub selector: u32,
 	pub hide: bool,
+	pub custom_signature: &'static str,
 	pub name: &'static str,
 	pub args: A,
 	pub result: R,
@@ -512,7 +532,7 @@
 		writeln!(
 			writer,
 			"\t{hide_comment}///  or in textual repr: {}",
-			self.selector_str
+			self.custom_signature
 		)?;
 		write!(writer, "\t{hide_comment}function {}(", self.name)?;
 		self.args.solidity_name(writer, tc)?;
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -27,7 +27,7 @@
 evm-coder = { default-features = false, path = '../../crates/evm-coder' }
 pallet-common = { default-features = false, path = '../../pallets/common' }
 pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
-pallet-evm-transaction-payment = {  default-features = false, path = '../../pallets/evm-transaction-payment' }
+pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [
     'serde1',
 ] }
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,6 +16,8 @@
 
 //! Implementation of magic contract
 
+extern crate alloc;
+use alloc::{format, string::ToString};
 use core::marker::PhantomData;
 use evm_coder::{
 	abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -16,6 +16,8 @@
 
 //! ERC-20 standart support implementation.
 
+extern crate alloc;
+use alloc::{format, string::ToString};
 use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
 use core::convert::TryInto;
 use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -20,6 +20,7 @@
 //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
 
 extern crate alloc;
+use alloc::{format, string::ToString};
 use core::{
 	char::{REPLACEMENT_CHARACTER, decode_utf16},
 	convert::TryInto,
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -26,7 +26,9 @@
 struct-versioning = { path = "../../crates/struct-versioning" }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 ethereum = { version = "0.12.0", default-features = false }
-scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+scale-info = { version = "2.0.1", default-features = false, features = [
+    "derive",
+] }
 derivative = { version = "2.2.0", features = ["use_core"] }
 
 [features]
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
before · pallets/refungible/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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25	char::{REPLACEMENT_CHARACTER, decode_utf16},26	convert::TryInto,27};28use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};29use frame_support::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31	CollectionHandle, CollectionPropertyPermissions,32	erc::{CommonEvmHandler, CollectionCall, static_property::key},33	eth::convert_tuple_to_cross_account,34};35use pallet_evm::{account::CrossAccountId, PrecompileHandle};36use pallet_evm_coder_substrate::{call, dispatch_to_evm};37use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};38use sp_core::H160;39use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};40use up_data_structs::{41	CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,42	PropertyKeyPermission, PropertyPermission, TokenId,43};4445use crate::{46	AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,47	TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,48};4950pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5152/// @title A contract that allows to set and delete token properties and change token property permissions.53#[solidity_interface(name = TokenProperties)]54impl<T: Config> RefungibleHandle<T> {55	/// @notice Set permissions for token property.56	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.57	/// @param key Property key.58	/// @param isMutable Permission to mutate property.59	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.60	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.61	fn set_token_property_permission(62		&mut self,63		caller: caller,64		key: string,65		is_mutable: bool,66		collection_admin: bool,67		token_owner: bool,68	) -> Result<()> {69		let caller = T::CrossAccountId::from_eth(caller);70		<Pallet<T>>::set_token_property_permissions(71			self,72			&caller,73			vec![PropertyKeyPermission {74				key: <Vec<u8>>::from(key)75					.try_into()76					.map_err(|_| "too long key")?,77				permission: PropertyPermission {78					mutable: is_mutable,79					collection_admin,80					token_owner,81				},82			}],83		)84		.map_err(dispatch_to_evm::<T>)85	}8687	/// @notice Set token property value.88	/// @dev Throws error if `msg.sender` has no permission to edit the property.89	/// @param tokenId ID of the token.90	/// @param key Property key.91	/// @param value Property value.92	fn set_property(93		&mut self,94		caller: caller,95		token_id: uint256,96		key: string,97		value: bytes,98	) -> Result<()> {99		let caller = T::CrossAccountId::from_eth(caller);100		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;101		let key = <Vec<u8>>::from(key)102			.try_into()103			.map_err(|_| "key too long")?;104		let value = value.0.try_into().map_err(|_| "value too long")?;105106		let nesting_budget = self107			.recorder108			.weight_calls_budget(<StructureWeight<T>>::find_parent());109110		<Pallet<T>>::set_token_property(111			self,112			&caller,113			TokenId(token_id),114			Property { key, value },115			&nesting_budget,116		)117		.map_err(dispatch_to_evm::<T>)118	}119120	/// @notice Set token properties value.121	/// @dev Throws error if `msg.sender` has no permission to edit the property.122	/// @param tokenId ID of the token.123	/// @param properties settable properties124	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	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {166		let caller = T::CrossAccountId::from_eth(caller);167		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;168		let key = <Vec<u8>>::from(key)169			.try_into()170			.map_err(|_| "key too long")?;171172		let nesting_budget = self173			.recorder174			.weight_calls_budget(<StructureWeight<T>>::find_parent());175176		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)177			.map_err(dispatch_to_evm::<T>)178	}179180	/// @notice Get token property value.181	/// @dev Throws error if key not found182	/// @param tokenId ID of the token.183	/// @param key Property key.184	/// @return Property value bytes185	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {186		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;187		let key = <Vec<u8>>::from(key)188			.try_into()189			.map_err(|_| "key too long")?;190191		let props = <TokenProperties<T>>::get((self.id, token_id));192		let prop = props.get(&key).ok_or("key not found")?;193194		Ok(prop.to_vec().into())195	}196}197198#[derive(ToLog)]199pub enum ERC721Events {200	/// @dev This event emits when NFTs are created (`from` == 0) and destroyed201	///  (`to` == 0). Exception: during contract creation, any number of RFTs202	///  may be created and assigned without emitting Transfer.203	Transfer {204		#[indexed]205		from: address,206		#[indexed]207		to: address,208		#[indexed]209		token_id: uint256,210	},211	/// @dev Not supported212	Approval {213		#[indexed]214		owner: address,215		#[indexed]216		approved: address,217		#[indexed]218		token_id: uint256,219	},220	/// @dev Not supported221	#[allow(dead_code)]222	ApprovalForAll {223		#[indexed]224		owner: address,225		#[indexed]226		operator: address,227		approved: bool,228	},229}230231#[derive(ToLog)]232pub enum ERC721UniqueMintableEvents {233	/// @dev Not supported234	#[allow(dead_code)]235	MintingFinished {},236}237238#[solidity_interface(name = ERC721Metadata)]239impl<T: Config> RefungibleHandle<T>240where241	T::AccountId: From<[u8; 32]>,242{243	/// @notice A descriptive name for a collection of NFTs in this contract244	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`245	#[solidity(hide, rename_selector = "name")]246	fn name_proxy(&self) -> Result<string> {247		self.name()248	}249250	/// @notice An abbreviated name for NFTs in this contract251	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`252	#[solidity(hide, rename_selector = "symbol")]253	fn symbol_proxy(&self) -> Result<string> {254		self.symbol()255	}256257	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.258	///259	/// @dev If the token has a `url` property and it is not empty, it is returned.260	///  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`.261	///  If the collection property `baseURI` is empty or absent, return "" (empty string)262	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix263	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).264	///265	/// @return token's const_metadata266	#[solidity(rename_selector = "tokenURI")]267	fn token_uri(&self, token_id: uint256) -> Result<string> {268		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;269270		match get_token_property(self, token_id_u32, &key::url()).as_deref() {271			Err(_) | Ok("") => (),272			Ok(url) => {273				return Ok(url.into());274			}275		};276277		let base_uri =278			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())279				.map(BoundedVec::into_inner)280				.map(string::from_utf8)281				.transpose()282				.map_err(|e| {283					Error::Revert(alloc::format!(284						"Can not convert value \"baseURI\" to string with error \"{}\"",285						e286					))287				})?;288289		let base_uri = match base_uri.as_deref() {290			None | Some("") => {291				return Ok("".into());292			}293			Some(base_uri) => base_uri.into(),294		};295296		Ok(297			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {298				Err(_) | Ok("") => base_uri,299				Ok(suffix) => base_uri + suffix,300			},301		)302	}303}304305/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension306/// @dev See https://eips.ethereum.org/EIPS/eip-721307#[solidity_interface(name = ERC721Enumerable)]308impl<T: Config> RefungibleHandle<T> {309	/// @notice Enumerate valid RFTs310	/// @param index A counter less than `totalSupply()`311	/// @return The token identifier for the `index`th NFT,312	///  (sort order not specified)313	fn token_by_index(&self, index: uint256) -> Result<uint256> {314		Ok(index)315	}316317	/// Not implemented318	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {319		// TODO: Not implemetable320		Err("not implemented".into())321	}322323	/// @notice Count RFTs tracked by this contract324	/// @return A count of valid RFTs tracked by this contract, where each one of325	///  them has an assigned and queryable owner not equal to the zero address326	fn total_supply(&self) -> Result<uint256> {327		self.consume_store_reads(1)?;328		Ok(<Pallet<T>>::total_supply(self).into())329	}330}331332/// @title ERC-721 Non-Fungible Token Standard333/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md334#[solidity_interface(name = ERC721, events(ERC721Events))]335impl<T: Config> RefungibleHandle<T> {336	/// @notice Count all RFTs assigned to an owner337	/// @dev RFTs assigned to the zero address are considered invalid, and this338	///  function throws for queries about the zero address.339	/// @param owner An address for whom to query the balance340	/// @return The number of RFTs owned by `owner`, possibly zero341	fn balance_of(&self, owner: address) -> Result<uint256> {342		self.consume_store_reads(1)?;343		let owner = T::CrossAccountId::from_eth(owner);344		let balance = <AccountBalance<T>>::get((self.id, owner));345		Ok(balance.into())346	}347348	/// @notice Find the owner of an RFT349	/// @dev RFTs assigned to zero address are considered invalid, and queries350	///  about them do throw.351	///  Returns special 0xffffffffffffffffffffffffffffffffffffffff address for352	///  the tokens that are partially owned.353	/// @param tokenId The identifier for an RFT354	/// @return The address of the owner of the RFT355	fn owner_of(&self, token_id: uint256) -> Result<address> {356		self.consume_store_reads(2)?;357		let token = token_id.try_into()?;358		let owner = <Pallet<T>>::token_owner(self.id, token);359		Ok(owner360			.map(|address| *address.as_eth())361			.unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))362	}363364	/// @dev Not implemented365	fn safe_transfer_from_with_data(366		&mut self,367		_from: address,368		_to: address,369		_token_id: uint256,370		_data: bytes,371	) -> Result<void> {372		// TODO: Not implemetable373		Err("not implemented".into())374	}375376	/// @dev Not implemented377	fn safe_transfer_from(378		&mut self,379		_from: address,380		_to: address,381		_token_id: uint256,382	) -> Result<void> {383		// TODO: Not implemetable384		Err("not implemented".into())385	}386387	/// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE388	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE389	///  THEY MAY BE PERMANENTLY LOST390	/// @dev Throws unless `msg.sender` is the current owner or an authorized391	///  operator for this RFT. Throws if `from` is not the current owner. Throws392	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.393	///  Throws if RFT pieces have multiple owners.394	/// @param from The current owner of the NFT395	/// @param to The new owner396	/// @param tokenId The NFT to transfer397	#[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]398	fn transfer_from(399		&mut self,400		caller: caller,401		from: address,402		to: address,403		token_id: uint256,404	) -> Result<void> {405		let caller = T::CrossAccountId::from_eth(caller);406		let from = T::CrossAccountId::from_eth(from);407		let to = T::CrossAccountId::from_eth(to);408		let token = token_id.try_into()?;409		let budget = self410			.recorder411			.weight_calls_budget(<StructureWeight<T>>::find_parent());412413		let balance = balance(&self, token, &from)?;414		ensure_single_owner(&self, token, balance)?;415416		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)417			.map_err(dispatch_to_evm::<T>)?;418419		Ok(())420	}421422	/// @dev Not implemented423	fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {424		Err("not implemented".into())425	}426427	/// @dev Not implemented428	fn set_approval_for_all(429		&mut self,430		_caller: caller,431		_operator: address,432		_approved: bool,433	) -> Result<void> {434		// TODO: Not implemetable435		Err("not implemented".into())436	}437438	/// @dev Not implemented439	fn get_approved(&self, _token_id: uint256) -> Result<address> {440		// TODO: Not implemetable441		Err("not implemented".into())442	}443444	/// @dev Not implemented445	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {446		// TODO: Not implemetable447		Err("not implemented".into())448	}449}450451/// Returns amount of pieces of `token` that `owner` have452pub fn balance<T: Config>(453	collection: &RefungibleHandle<T>,454	token: TokenId,455	owner: &T::CrossAccountId,456) -> Result<u128> {457	collection.consume_store_reads(1)?;458	let balance = <Balance<T>>::get((collection.id, token, &owner));459	Ok(balance)460}461462/// Throws if `owner_balance` is lower than total amount of `token` pieces463pub fn ensure_single_owner<T: Config>(464	collection: &RefungibleHandle<T>,465	token: TokenId,466	owner_balance: u128,467) -> Result<()> {468	collection.consume_store_reads(1)?;469	let total_supply = <TotalSupply<T>>::get((collection.id, token));470	if total_supply != owner_balance {471		return Err("token has multiple owners".into());472	}473	Ok(())474}475476/// @title ERC721 Token that can be irreversibly burned (destroyed).477#[solidity_interface(name = ERC721Burnable)]478impl<T: Config> RefungibleHandle<T> {479	/// @notice Burns a specific ERC721 token.480	/// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized481	///  operator of the current owner.482	/// @param tokenId The RFT to approve483	#[weight(<SelfWeightOf<T>>::burn_item_fully())]484	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {485		let caller = T::CrossAccountId::from_eth(caller);486		let token = token_id.try_into()?;487488		let balance = balance(&self, token, &caller)?;489		ensure_single_owner(&self, token, balance)?;490491		<Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;492		Ok(())493	}494}495496/// @title ERC721 minting logic.497#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]498impl<T: Config> RefungibleHandle<T> {499	fn minting_finished(&self) -> Result<bool> {500		Ok(false)501	}502503	/// @notice Function to mint token.504	/// @param to The new owner505	/// @return uint256 The id of the newly minted token506	#[weight(<SelfWeightOf<T>>::create_item())]507	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {508		let token_id: uint256 = <TokensMinted<T>>::get(self.id)509			.checked_add(1)510			.ok_or("item id overflow")?511			.into();512		self.mint_check_id(caller, to, token_id)?;513		Ok(token_id)514	}515516	/// @notice Function to mint token.517	/// @dev `tokenId` should be obtained with `nextTokenId` method,518	///  unlike standard, you can't specify it manually519	/// @param to The new owner520	/// @param tokenId ID of the minted RFT521	#[solidity(hide, rename_selector = "mint")]522	#[weight(<SelfWeightOf<T>>::create_item())]523	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {524		let caller = T::CrossAccountId::from_eth(caller);525		let to = T::CrossAccountId::from_eth(to);526		let token_id: u32 = token_id.try_into()?;527		let budget = self528			.recorder529			.weight_calls_budget(<StructureWeight<T>>::find_parent());530531		if <TokensMinted<T>>::get(self.id)532			.checked_add(1)533			.ok_or("item id overflow")?534			!= token_id535		{536			return Err("item id should be next".into());537		}538539		let users = [(to.clone(), 1)]540			.into_iter()541			.collect::<BTreeMap<_, _>>()542			.try_into()543			.unwrap();544		<Pallet<T>>::create_item(545			self,546			&caller,547			CreateItemData::<T::CrossAccountId> {548				users,549				properties: CollectionPropertiesVec::default(),550			},551			&budget,552		)553		.map_err(dispatch_to_evm::<T>)?;554555		Ok(true)556	}557558	/// @notice Function to mint token with the given tokenUri.559	/// @param to The new owner560	/// @param tokenUri Token URI that would be stored in the NFT properties561	/// @return uint256 The id of the newly minted token562	#[solidity(rename_selector = "mintWithTokenURI")]563	#[weight(<SelfWeightOf<T>>::create_item())]564	fn mint_with_token_uri(565		&mut self,566		caller: caller,567		to: address,568		token_uri: string,569	) -> Result<uint256> {570		let token_id: uint256 = <TokensMinted<T>>::get(self.id)571			.checked_add(1)572			.ok_or("item id overflow")?573			.into();574		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;575		Ok(token_id)576	}577578	/// @notice Function to mint token with the given tokenUri.579	/// @dev `tokenId` should be obtained with `nextTokenId` method,580	///  unlike standard, you can't specify it manually581	/// @param to The new owner582	/// @param tokenId ID of the minted RFT583	/// @param tokenUri Token URI that would be stored in the RFT properties584	#[solidity(hide, rename_selector = "mintWithTokenURI")]585	#[weight(<SelfWeightOf<T>>::create_item())]586	fn mint_with_token_uri_check_id(587		&mut self,588		caller: caller,589		to: address,590		token_id: uint256,591		token_uri: string,592	) -> Result<bool> {593		let key = key::url();594		let permission = get_token_permission::<T>(self.id, &key)?;595		if !permission.collection_admin {596			return Err("Operation is not allowed".into());597		}598599		let caller = T::CrossAccountId::from_eth(caller);600		let to = T::CrossAccountId::from_eth(to);601		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;602		let budget = self603			.recorder604			.weight_calls_budget(<StructureWeight<T>>::find_parent());605606		if <TokensMinted<T>>::get(self.id)607			.checked_add(1)608			.ok_or("item id overflow")?609			!= token_id610		{611			return Err("item id should be next".into());612		}613614		let mut properties = CollectionPropertiesVec::default();615		properties616			.try_push(Property {617				key,618				value: token_uri619					.into_bytes()620					.try_into()621					.map_err(|_| "token uri is too long")?,622			})623			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;624625		let users = [(to.clone(), 1)]626			.into_iter()627			.collect::<BTreeMap<_, _>>()628			.try_into()629			.unwrap();630		<Pallet<T>>::create_item(631			self,632			&caller,633			CreateItemData::<T::CrossAccountId> { users, properties },634			&budget,635		)636		.map_err(dispatch_to_evm::<T>)?;637		Ok(true)638	}639640	/// @dev Not implemented641	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {642		Err("not implementable".into())643	}644}645646fn get_token_property<T: Config>(647	collection: &CollectionHandle<T>,648	token_id: u32,649	key: &up_data_structs::PropertyKey,650) -> Result<string> {651	collection.consume_store_reads(1)?;652	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))653		.map_err(|_| Error::Revert("Token properties not found".into()))?;654	if let Some(property) = properties.get(key) {655		return Ok(string::from_utf8_lossy(property).into());656	}657658	Err("Property tokenURI not found".into())659}660661fn get_token_permission<T: Config>(662	collection_id: CollectionId,663	key: &PropertyKey,664) -> Result<PropertyPermission> {665	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)666		.map_err(|_| Error::Revert("No permissions for collection".into()))?;667	let a = token_property_permissions668		.get(key)669		.map(Clone::clone)670		.ok_or_else(|| {671			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();672			Error::Revert(alloc::format!("No permission for key {}", key))673		})?;674	Ok(a)675}676677/// @title Unique extensions for ERC721.678#[solidity_interface(name = ERC721UniqueExtensions)]679impl<T: Config> RefungibleHandle<T>680where681	T::AccountId: From<[u8; 32]>,682{683	/// @notice A descriptive name for a collection of NFTs in this contract684	fn name(&self) -> Result<string> {685		Ok(decode_utf16(self.name.iter().copied())686			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))687			.collect::<string>())688	}689690	/// @notice An abbreviated name for NFTs in this contract691	fn symbol(&self) -> Result<string> {692		Ok(string::from_utf8_lossy(&self.token_prefix).into())693	}694695	/// @notice Transfer ownership of an RFT696	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`697	///  is the zero address. Throws if `tokenId` is not a valid RFT.698	///  Throws if RFT pieces have multiple owners.699	/// @param to The new owner700	/// @param tokenId The RFT to transfer701	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]702	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {703		let caller = T::CrossAccountId::from_eth(caller);704		let to = T::CrossAccountId::from_eth(to);705		let token = token_id.try_into()?;706		let budget = self707			.recorder708			.weight_calls_budget(<StructureWeight<T>>::find_parent());709710		let balance = balance(self, token, &caller)?;711		ensure_single_owner(self, token, balance)?;712713		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)714			.map_err(dispatch_to_evm::<T>)?;715		Ok(())716	}717718	/// @notice Transfer ownership of an RFT719	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`720	///  is the zero address. Throws if `tokenId` is not a valid RFT.721	///  Throws if RFT pieces have multiple owners.722	/// @param to The new owner723	/// @param tokenId The RFT to transfer724	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]725	fn transfer_from_cross(726		&mut self,727		caller: caller,728		from: (address, uint256),729		to: (address, uint256),730		token_id: uint256,731	) -> Result<void> {732		let caller = T::CrossAccountId::from_eth(caller);733		// let from = convert_tuple_to_cross_account::<T>(from)?;734		// let to = convert_tuple_to_cross_account::<T>(to)?;735		// let token_id = token_id.try_into()?;736		// let budget = self737		// 	.recorder738		// 	.weight_calls_budget(<StructureWeight<T>>::find_parent());739740		// let balance = balance(self, token_id, &from)?;741		// ensure_single_owner(self, token_id, balance)?;742743		// Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)744		// 	.map_err(dispatch_to_evm::<T>)?;745		Ok(())746	}747748	/// @notice Burns a specific ERC721 token.749	/// @dev Throws unless `msg.sender` is the current owner or an authorized750	///  operator for this RFT. Throws if `from` is not the current owner. Throws751	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.752	///  Throws if RFT pieces have multiple owners.753	/// @param from The current owner of the RFT754	/// @param tokenId The RFT to transfer755	#[weight(<SelfWeightOf<T>>::burn_from())]756	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {757		let caller = T::CrossAccountId::from_eth(caller);758		let from = T::CrossAccountId::from_eth(from);759		let token = token_id.try_into()?;760		let budget = self761			.recorder762			.weight_calls_budget(<StructureWeight<T>>::find_parent());763764		let balance = balance(self, token, &from)?;765		ensure_single_owner(self, token, balance)?;766767		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)768			.map_err(dispatch_to_evm::<T>)?;769		Ok(())770	}771772	/// @notice Burns a specific ERC721 token.773	/// @dev Throws unless `msg.sender` is the current owner or an authorized774	///  operator for this RFT. Throws if `from` is not the current owner. Throws775	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.776	///  Throws if RFT pieces have multiple owners.777	/// @param from The current owner of the RFT778	/// @param tokenId The RFT to transfer779	#[weight(<SelfWeightOf<T>>::burn_from())]780	fn burn_from_cross(781		&mut self,782		caller: caller,783		from: (address, uint256),784		token_id: uint256,785	) -> Result<void> {786		let caller = T::CrossAccountId::from_eth(caller);787		let from = convert_tuple_to_cross_account::<T>(from)?;788		let token = token_id.try_into()?;789		let budget = self790			.recorder791			.weight_calls_budget(<StructureWeight<T>>::find_parent());792793		let balance = balance(self, token, &from)?;794		ensure_single_owner(self, token, balance)?;795796		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)797			.map_err(dispatch_to_evm::<T>)?;798		Ok(())799	}800801	/// @notice Returns next free RFT ID.802	fn next_token_id(&self) -> Result<uint256> {803		self.consume_store_reads(1)?;804		Ok(<TokensMinted<T>>::get(self.id)805			.checked_add(1)806			.ok_or("item id overflow")?807			.into())808	}809810	/// @notice Function to mint multiple tokens.811	/// @dev `tokenIds` should be an array of consecutive numbers and first number812	///  should be obtained with `nextTokenId` method813	/// @param to The new owner814	/// @param tokenIds IDs of the minted RFTs815	#[solidity(hide)]816	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]817	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {818		let caller = T::CrossAccountId::from_eth(caller);819		let to = T::CrossAccountId::from_eth(to);820		let mut expected_index = <TokensMinted<T>>::get(self.id)821			.checked_add(1)822			.ok_or("item id overflow")?;823		let budget = self824			.recorder825			.weight_calls_budget(<StructureWeight<T>>::find_parent());826827		let total_tokens = token_ids.len();828		for id in token_ids.into_iter() {829			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;830			if id != expected_index {831				return Err("item id should be next".into());832			}833			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;834		}835		let users = [(to.clone(), 1)]836			.into_iter()837			.collect::<BTreeMap<_, _>>()838			.try_into()839			.unwrap();840		let create_item_data = CreateItemData::<T::CrossAccountId> {841			users,842			properties: CollectionPropertiesVec::default(),843		};844		let data = (0..total_tokens)845			.map(|_| create_item_data.clone())846			.collect();847848		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)849			.map_err(dispatch_to_evm::<T>)?;850		Ok(true)851	}852853	/// @notice Function to mint multiple tokens with the given tokenUris.854	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive855	///  numbers and first number should be obtained with `nextTokenId` method856	/// @param to The new owner857	/// @param tokens array of pairs of token ID and token URI for minted tokens858	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]859	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]860	fn mint_bulk_with_token_uri(861		&mut self,862		caller: caller,863		to: address,864		tokens: Vec<(uint256, string)>,865	) -> Result<bool> {866		let key = key::url();867		let caller = T::CrossAccountId::from_eth(caller);868		let to = T::CrossAccountId::from_eth(to);869		let mut expected_index = <TokensMinted<T>>::get(self.id)870			.checked_add(1)871			.ok_or("item id overflow")?;872		let budget = self873			.recorder874			.weight_calls_budget(<StructureWeight<T>>::find_parent());875876		let mut data = Vec::with_capacity(tokens.len());877		let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]878			.into_iter()879			.collect::<BTreeMap<_, _>>()880			.try_into()881			.unwrap();882		for (id, token_uri) in tokens {883			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;884			if id != expected_index {885				return Err("item id should be next".into());886			}887			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;888889			let mut properties = CollectionPropertiesVec::default();890			properties891				.try_push(Property {892					key: key.clone(),893					value: token_uri894						.into_bytes()895						.try_into()896						.map_err(|_| "token uri is too long")?,897				})898				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;899900			let create_item_data = CreateItemData::<T::CrossAccountId> {901				users: users.clone(),902				properties,903			};904			data.push(create_item_data);905		}906907		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)908			.map_err(dispatch_to_evm::<T>)?;909		Ok(true)910	}911912	/// Returns EVM address for refungible token913	///914	/// @param token ID of the token915	fn token_contract_address(&self, token: uint256) -> Result<address> {916		Ok(T::EvmTokenAddressMapping::token_to_address(917			self.id,918			token.try_into().map_err(|_| "token id overflow")?,919		))920	}921}922923#[solidity_interface(924	name = UniqueRefungible,925	is(926		ERC721,927		ERC721Enumerable,928		ERC721UniqueExtensions,929		ERC721UniqueMintable,930		ERC721Burnable,931		ERC721Metadata(if(this.flags.erc721metadata)),932		Collection(via(common_mut returns CollectionHandle<T>)),933		TokenProperties,934	)935)]936impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}937938// Not a tests, but code generators939generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);940generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);941942impl<T: Config> CommonEvmHandler for RefungibleHandle<T>943where944	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,945{946	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");947	fn call(948		self,949		handle: &mut impl PrecompileHandle,950	) -> Option<pallet_common::erc::PrecompileResult> {951		call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)952	}953}
after · pallets/refungible/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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::{format, string::ToString};25use core::{26	char::{REPLACEMENT_CHARACTER, decode_utf16},27	convert::TryInto,28};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};30use frame_support::{BoundedBTreeMap, BoundedVec};31use pallet_common::{32	CollectionHandle, CollectionPropertyPermissions,33	erc::{CommonEvmHandler, CollectionCall, static_property::key},34	eth::convert_tuple_to_cross_account,35};36use pallet_evm::{account::CrossAccountId, PrecompileHandle};37use pallet_evm_coder_substrate::{call, dispatch_to_evm};38use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};39use sp_core::H160;40use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};41use up_data_structs::{42	CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,43	PropertyKeyPermission, PropertyPermission, TokenId,44};4546use crate::{47	AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,48	TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,49};5051pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5253/// @title A contract that allows to set and delete token properties and change token property permissions.54#[solidity_interface(name = TokenProperties)]55impl<T: Config> RefungibleHandle<T> {56	/// @notice Set permissions for token property.57	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.58	/// @param key Property key.59	/// @param isMutable Permission to mutate property.60	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.61	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.62	fn set_token_property_permission(63		&mut self,64		caller: caller,65		key: string,66		is_mutable: bool,67		collection_admin: bool,68		token_owner: bool,69	) -> Result<()> {70		let caller = T::CrossAccountId::from_eth(caller);71		<Pallet<T>>::set_token_property_permissions(72			self,73			&caller,74			vec![PropertyKeyPermission {75				key: <Vec<u8>>::from(key)76					.try_into()77					.map_err(|_| "too long key")?,78				permission: PropertyPermission {79					mutable: is_mutable,80					collection_admin,81					token_owner,82				},83			}],84		)85		.map_err(dispatch_to_evm::<T>)86	}8788	/// @notice Set token property value.89	/// @dev Throws error if `msg.sender` has no permission to edit the property.90	/// @param tokenId ID of the token.91	/// @param key Property key.92	/// @param value Property value.93	fn set_property(94		&mut self,95		caller: caller,96		token_id: uint256,97		key: string,98		value: bytes,99	) -> Result<()> {100		let caller = T::CrossAccountId::from_eth(caller);101		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102		let key = <Vec<u8>>::from(key)103			.try_into()104			.map_err(|_| "key too long")?;105		let value = value.0.try_into().map_err(|_| "value too long")?;106107		let nesting_budget = self108			.recorder109			.weight_calls_budget(<StructureWeight<T>>::find_parent());110111		<Pallet<T>>::set_token_property(112			self,113			&caller,114			TokenId(token_id),115			Property { key, value },116			&nesting_budget,117		)118		.map_err(dispatch_to_evm::<T>)119	}120121	/// @notice Set token properties value.122	/// @dev Throws error if `msg.sender` has no permission to edit the property.123	/// @param tokenId ID of the token.124	/// @param properties settable properties125	fn set_properties(126		&mut self,127		caller: caller,128		token_id: uint256,129		properties: Vec<(string, bytes)>,130	) -> Result<()> {131		let caller = T::CrossAccountId::from_eth(caller);132		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;133134		let nesting_budget = self135			.recorder136			.weight_calls_budget(<StructureWeight<T>>::find_parent());137138		let properties = properties139			.into_iter()140			.map(|(key, value)| {141				let key = <Vec<u8>>::from(key)142					.try_into()143					.map_err(|_| "key too large")?;144145				let value = value.0.try_into().map_err(|_| "value too large")?;146147				Ok(Property { key, value })148			})149			.collect::<Result<Vec<_>>>()?;150151		<Pallet<T>>::set_token_properties(152			self,153			&caller,154			TokenId(token_id),155			properties.into_iter(),156			<Pallet<T>>::token_exists(&self, TokenId(token_id)),157			&nesting_budget,158		)159		.map_err(dispatch_to_evm::<T>)160	}161162	/// @notice Delete token property value.163	/// @dev Throws error if `msg.sender` has no permission to edit the property.164	/// @param tokenId ID of the token.165	/// @param key Property key.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 Get token property value.182	/// @dev Throws error if key not found183	/// @param tokenId ID of the token.184	/// @param key Property key.185	/// @return Property value bytes186	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {187		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;188		let key = <Vec<u8>>::from(key)189			.try_into()190			.map_err(|_| "key too long")?;191192		let props = <TokenProperties<T>>::get((self.id, token_id));193		let prop = props.get(&key).ok_or("key not found")?;194195		Ok(prop.to_vec().into())196	}197}198199#[derive(ToLog)]200pub enum ERC721Events {201	/// @dev This event emits when NFTs are created (`from` == 0) and destroyed202	///  (`to` == 0). Exception: during contract creation, any number of RFTs203	///  may be created and assigned without emitting Transfer.204	Transfer {205		#[indexed]206		from: address,207		#[indexed]208		to: address,209		#[indexed]210		token_id: uint256,211	},212	/// @dev Not supported213	Approval {214		#[indexed]215		owner: address,216		#[indexed]217		approved: address,218		#[indexed]219		token_id: uint256,220	},221	/// @dev Not supported222	#[allow(dead_code)]223	ApprovalForAll {224		#[indexed]225		owner: address,226		#[indexed]227		operator: address,228		approved: bool,229	},230}231232#[derive(ToLog)]233pub enum ERC721UniqueMintableEvents {234	/// @dev Not supported235	#[allow(dead_code)]236	MintingFinished {},237}238239#[solidity_interface(name = ERC721Metadata)]240impl<T: Config> RefungibleHandle<T>241where242	T::AccountId: From<[u8; 32]>,243{244	/// @notice A descriptive name for a collection of NFTs in this contract245	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`246	#[solidity(hide, rename_selector = "name")]247	fn name_proxy(&self) -> Result<string> {248		self.name()249	}250251	/// @notice An abbreviated name for NFTs in this contract252	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`253	#[solidity(hide, rename_selector = "symbol")]254	fn symbol_proxy(&self) -> Result<string> {255		self.symbol()256	}257258	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.259	///260	/// @dev If the token has a `url` property and it is not empty, it is returned.261	///  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`.262	///  If the collection property `baseURI` is empty or absent, return "" (empty string)263	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix264	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).265	///266	/// @return token's const_metadata267	#[solidity(rename_selector = "tokenURI")]268	fn token_uri(&self, token_id: uint256) -> Result<string> {269		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;270271		match get_token_property(self, token_id_u32, &key::url()).as_deref() {272			Err(_) | Ok("") => (),273			Ok(url) => {274				return Ok(url.into());275			}276		};277278		let base_uri =279			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())280				.map(BoundedVec::into_inner)281				.map(string::from_utf8)282				.transpose()283				.map_err(|e| {284					Error::Revert(alloc::format!(285						"Can not convert value \"baseURI\" to string with error \"{}\"",286						e287					))288				})?;289290		let base_uri = match base_uri.as_deref() {291			None | Some("") => {292				return Ok("".into());293			}294			Some(base_uri) => base_uri.into(),295		};296297		Ok(298			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {299				Err(_) | Ok("") => base_uri,300				Ok(suffix) => base_uri + suffix,301			},302		)303	}304}305306/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension307/// @dev See https://eips.ethereum.org/EIPS/eip-721308#[solidity_interface(name = ERC721Enumerable)]309impl<T: Config> RefungibleHandle<T> {310	/// @notice Enumerate valid RFTs311	/// @param index A counter less than `totalSupply()`312	/// @return The token identifier for the `index`th NFT,313	///  (sort order not specified)314	fn token_by_index(&self, index: uint256) -> Result<uint256> {315		Ok(index)316	}317318	/// Not implemented319	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {320		// TODO: Not implemetable321		Err("not implemented".into())322	}323324	/// @notice Count RFTs tracked by this contract325	/// @return A count of valid RFTs tracked by this contract, where each one of326	///  them has an assigned and queryable owner not equal to the zero address327	fn total_supply(&self) -> Result<uint256> {328		self.consume_store_reads(1)?;329		Ok(<Pallet<T>>::total_supply(self).into())330	}331}332333/// @title ERC-721 Non-Fungible Token Standard334/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md335#[solidity_interface(name = ERC721, events(ERC721Events))]336impl<T: Config> RefungibleHandle<T> {337	/// @notice Count all RFTs assigned to an owner338	/// @dev RFTs assigned to the zero address are considered invalid, and this339	///  function throws for queries about the zero address.340	/// @param owner An address for whom to query the balance341	/// @return The number of RFTs owned by `owner`, possibly zero342	fn balance_of(&self, owner: address) -> Result<uint256> {343		self.consume_store_reads(1)?;344		let owner = T::CrossAccountId::from_eth(owner);345		let balance = <AccountBalance<T>>::get((self.id, owner));346		Ok(balance.into())347	}348349	/// @notice Find the owner of an RFT350	/// @dev RFTs assigned to zero address are considered invalid, and queries351	///  about them do throw.352	///  Returns special 0xffffffffffffffffffffffffffffffffffffffff address for353	///  the tokens that are partially owned.354	/// @param tokenId The identifier for an RFT355	/// @return The address of the owner of the RFT356	fn owner_of(&self, token_id: uint256) -> Result<address> {357		self.consume_store_reads(2)?;358		let token = token_id.try_into()?;359		let owner = <Pallet<T>>::token_owner(self.id, token);360		Ok(owner361			.map(|address| *address.as_eth())362			.unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))363	}364365	/// @dev Not implemented366	fn safe_transfer_from_with_data(367		&mut self,368		_from: address,369		_to: address,370		_token_id: uint256,371		_data: bytes,372	) -> Result<void> {373		// TODO: Not implemetable374		Err("not implemented".into())375	}376377	/// @dev Not implemented378	fn safe_transfer_from(379		&mut self,380		_from: address,381		_to: address,382		_token_id: uint256,383	) -> Result<void> {384		// TODO: Not implemetable385		Err("not implemented".into())386	}387388	/// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE389	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE390	///  THEY MAY BE PERMANENTLY LOST391	/// @dev Throws unless `msg.sender` is the current owner or an authorized392	///  operator for this RFT. Throws if `from` is not the current owner. Throws393	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.394	///  Throws if RFT pieces have multiple owners.395	/// @param from The current owner of the NFT396	/// @param to The new owner397	/// @param tokenId The NFT to transfer398	#[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]399	fn transfer_from(400		&mut self,401		caller: caller,402		from: address,403		to: address,404		token_id: uint256,405	) -> Result<void> {406		let caller = T::CrossAccountId::from_eth(caller);407		let from = T::CrossAccountId::from_eth(from);408		let to = T::CrossAccountId::from_eth(to);409		let token = token_id.try_into()?;410		let budget = self411			.recorder412			.weight_calls_budget(<StructureWeight<T>>::find_parent());413414		let balance = balance(&self, token, &from)?;415		ensure_single_owner(&self, token, balance)?;416417		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)418			.map_err(dispatch_to_evm::<T>)?;419420		Ok(())421	}422423	/// @dev Not implemented424	fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {425		Err("not implemented".into())426	}427428	/// @dev Not implemented429	fn set_approval_for_all(430		&mut self,431		_caller: caller,432		_operator: address,433		_approved: bool,434	) -> Result<void> {435		// TODO: Not implemetable436		Err("not implemented".into())437	}438439	/// @dev Not implemented440	fn get_approved(&self, _token_id: uint256) -> Result<address> {441		// TODO: Not implemetable442		Err("not implemented".into())443	}444445	/// @dev Not implemented446	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {447		// TODO: Not implemetable448		Err("not implemented".into())449	}450}451452/// Returns amount of pieces of `token` that `owner` have453pub fn balance<T: Config>(454	collection: &RefungibleHandle<T>,455	token: TokenId,456	owner: &T::CrossAccountId,457) -> Result<u128> {458	collection.consume_store_reads(1)?;459	let balance = <Balance<T>>::get((collection.id, token, &owner));460	Ok(balance)461}462463/// Throws if `owner_balance` is lower than total amount of `token` pieces464pub fn ensure_single_owner<T: Config>(465	collection: &RefungibleHandle<T>,466	token: TokenId,467	owner_balance: u128,468) -> Result<()> {469	collection.consume_store_reads(1)?;470	let total_supply = <TotalSupply<T>>::get((collection.id, token));471	if total_supply != owner_balance {472		return Err("token has multiple owners".into());473	}474	Ok(())475}476477/// @title ERC721 Token that can be irreversibly burned (destroyed).478#[solidity_interface(name = ERC721Burnable)]479impl<T: Config> RefungibleHandle<T> {480	/// @notice Burns a specific ERC721 token.481	/// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized482	///  operator of the current owner.483	/// @param tokenId The RFT to approve484	#[weight(<SelfWeightOf<T>>::burn_item_fully())]485	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {486		let caller = T::CrossAccountId::from_eth(caller);487		let token = token_id.try_into()?;488489		let balance = balance(&self, token, &caller)?;490		ensure_single_owner(&self, token, balance)?;491492		<Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;493		Ok(())494	}495}496497/// @title ERC721 minting logic.498#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]499impl<T: Config> RefungibleHandle<T> {500	fn minting_finished(&self) -> Result<bool> {501		Ok(false)502	}503504	/// @notice Function to mint token.505	/// @param to The new owner506	/// @return uint256 The id of the newly minted token507	#[weight(<SelfWeightOf<T>>::create_item())]508	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {509		let token_id: uint256 = <TokensMinted<T>>::get(self.id)510			.checked_add(1)511			.ok_or("item id overflow")?512			.into();513		self.mint_check_id(caller, to, token_id)?;514		Ok(token_id)515	}516517	/// @notice Function to mint token.518	/// @dev `tokenId` should be obtained with `nextTokenId` method,519	///  unlike standard, you can't specify it manually520	/// @param to The new owner521	/// @param tokenId ID of the minted RFT522	#[solidity(hide, rename_selector = "mint")]523	#[weight(<SelfWeightOf<T>>::create_item())]524	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {525		let caller = T::CrossAccountId::from_eth(caller);526		let to = T::CrossAccountId::from_eth(to);527		let token_id: u32 = token_id.try_into()?;528		let budget = self529			.recorder530			.weight_calls_budget(<StructureWeight<T>>::find_parent());531532		if <TokensMinted<T>>::get(self.id)533			.checked_add(1)534			.ok_or("item id overflow")?535			!= token_id536		{537			return Err("item id should be next".into());538		}539540		let users = [(to.clone(), 1)]541			.into_iter()542			.collect::<BTreeMap<_, _>>()543			.try_into()544			.unwrap();545		<Pallet<T>>::create_item(546			self,547			&caller,548			CreateItemData::<T::CrossAccountId> {549				users,550				properties: CollectionPropertiesVec::default(),551			},552			&budget,553		)554		.map_err(dispatch_to_evm::<T>)?;555556		Ok(true)557	}558559	/// @notice Function to mint token with the given tokenUri.560	/// @param to The new owner561	/// @param tokenUri Token URI that would be stored in the NFT properties562	/// @return uint256 The id of the newly minted token563	#[solidity(rename_selector = "mintWithTokenURI")]564	#[weight(<SelfWeightOf<T>>::create_item())]565	fn mint_with_token_uri(566		&mut self,567		caller: caller,568		to: address,569		token_uri: string,570	) -> Result<uint256> {571		let token_id: uint256 = <TokensMinted<T>>::get(self.id)572			.checked_add(1)573			.ok_or("item id overflow")?574			.into();575		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;576		Ok(token_id)577	}578579	/// @notice Function to mint token with the given tokenUri.580	/// @dev `tokenId` should be obtained with `nextTokenId` method,581	///  unlike standard, you can't specify it manually582	/// @param to The new owner583	/// @param tokenId ID of the minted RFT584	/// @param tokenUri Token URI that would be stored in the RFT properties585	#[solidity(hide, rename_selector = "mintWithTokenURI")]586	#[weight(<SelfWeightOf<T>>::create_item())]587	fn mint_with_token_uri_check_id(588		&mut self,589		caller: caller,590		to: address,591		token_id: uint256,592		token_uri: string,593	) -> Result<bool> {594		let key = key::url();595		let permission = get_token_permission::<T>(self.id, &key)?;596		if !permission.collection_admin {597			return Err("Operation is not allowed".into());598		}599600		let caller = T::CrossAccountId::from_eth(caller);601		let to = T::CrossAccountId::from_eth(to);602		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;603		let budget = self604			.recorder605			.weight_calls_budget(<StructureWeight<T>>::find_parent());606607		if <TokensMinted<T>>::get(self.id)608			.checked_add(1)609			.ok_or("item id overflow")?610			!= token_id611		{612			return Err("item id should be next".into());613		}614615		let mut properties = CollectionPropertiesVec::default();616		properties617			.try_push(Property {618				key,619				value: token_uri620					.into_bytes()621					.try_into()622					.map_err(|_| "token uri is too long")?,623			})624			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;625626		let users = [(to.clone(), 1)]627			.into_iter()628			.collect::<BTreeMap<_, _>>()629			.try_into()630			.unwrap();631		<Pallet<T>>::create_item(632			self,633			&caller,634			CreateItemData::<T::CrossAccountId> { users, properties },635			&budget,636		)637		.map_err(dispatch_to_evm::<T>)?;638		Ok(true)639	}640641	/// @dev Not implemented642	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {643		Err("not implementable".into())644	}645}646647fn get_token_property<T: Config>(648	collection: &CollectionHandle<T>,649	token_id: u32,650	key: &up_data_structs::PropertyKey,651) -> Result<string> {652	collection.consume_store_reads(1)?;653	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))654		.map_err(|_| Error::Revert("Token properties not found".into()))?;655	if let Some(property) = properties.get(key) {656		return Ok(string::from_utf8_lossy(property).into());657	}658659	Err("Property tokenURI not found".into())660}661662fn get_token_permission<T: Config>(663	collection_id: CollectionId,664	key: &PropertyKey,665) -> Result<PropertyPermission> {666	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)667		.map_err(|_| Error::Revert("No permissions for collection".into()))?;668	let a = token_property_permissions669		.get(key)670		.map(Clone::clone)671		.ok_or_else(|| {672			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();673			Error::Revert(alloc::format!("No permission for key {}", key))674		})?;675	Ok(a)676}677678/// @title Unique extensions for ERC721.679#[solidity_interface(name = ERC721UniqueExtensions)]680impl<T: Config> RefungibleHandle<T>681where682	T::AccountId: From<[u8; 32]>,683{684	/// @notice A descriptive name for a collection of NFTs in this contract685	fn name(&self) -> Result<string> {686		Ok(decode_utf16(self.name.iter().copied())687			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))688			.collect::<string>())689	}690691	/// @notice An abbreviated name for NFTs in this contract692	fn symbol(&self) -> Result<string> {693		Ok(string::from_utf8_lossy(&self.token_prefix).into())694	}695696	/// @notice Transfer ownership of an RFT697	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`698	///  is the zero address. Throws if `tokenId` is not a valid RFT.699	///  Throws if RFT pieces have multiple owners.700	/// @param to The new owner701	/// @param tokenId The RFT to transfer702	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]703	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {704		let caller = T::CrossAccountId::from_eth(caller);705		let to = T::CrossAccountId::from_eth(to);706		let token = token_id.try_into()?;707		let budget = self708			.recorder709			.weight_calls_budget(<StructureWeight<T>>::find_parent());710711		let balance = balance(self, token, &caller)?;712		ensure_single_owner(self, token, balance)?;713714		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)715			.map_err(dispatch_to_evm::<T>)?;716		Ok(())717	}718719	/// @notice Transfer ownership of an RFT720	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`721	///  is the zero address. Throws if `tokenId` is not a valid RFT.722	///  Throws if RFT pieces have multiple owners.723	/// @param to The new owner724	/// @param tokenId The RFT to transfer725	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]726	fn transfer_from_cross(727		&mut self,728		caller: caller,729		from: (address, uint256),730		to: (address, uint256),731		token_id: uint256,732	) -> Result<void> {733		let caller = T::CrossAccountId::from_eth(caller);734		// let from = convert_tuple_to_cross_account::<T>(from)?;735		// let to = convert_tuple_to_cross_account::<T>(to)?;736		// let token_id = token_id.try_into()?;737		// let budget = self738		// 	.recorder739		// 	.weight_calls_budget(<StructureWeight<T>>::find_parent());740741		// let balance = balance(self, token_id, &from)?;742		// ensure_single_owner(self, token_id, balance)?;743744		// Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)745		// 	.map_err(dispatch_to_evm::<T>)?;746		Ok(())747	}748749	/// @notice Burns a specific ERC721 token.750	/// @dev Throws unless `msg.sender` is the current owner or an authorized751	///  operator for this RFT. Throws if `from` is not the current owner. Throws752	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.753	///  Throws if RFT pieces have multiple owners.754	/// @param from The current owner of the RFT755	/// @param tokenId The RFT to transfer756	#[weight(<SelfWeightOf<T>>::burn_from())]757	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {758		let caller = T::CrossAccountId::from_eth(caller);759		let from = T::CrossAccountId::from_eth(from);760		let token = token_id.try_into()?;761		let budget = self762			.recorder763			.weight_calls_budget(<StructureWeight<T>>::find_parent());764765		let balance = balance(self, token, &from)?;766		ensure_single_owner(self, token, balance)?;767768		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)769			.map_err(dispatch_to_evm::<T>)?;770		Ok(())771	}772773	/// @notice Burns a specific ERC721 token.774	/// @dev Throws unless `msg.sender` is the current owner or an authorized775	///  operator for this RFT. Throws if `from` is not the current owner. Throws776	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.777	///  Throws if RFT pieces have multiple owners.778	/// @param from The current owner of the RFT779	/// @param tokenId The RFT to transfer780	#[weight(<SelfWeightOf<T>>::burn_from())]781	fn burn_from_cross(782		&mut self,783		caller: caller,784		from: (address, uint256),785		token_id: uint256,786	) -> Result<void> {787		let caller = T::CrossAccountId::from_eth(caller);788		let from = convert_tuple_to_cross_account::<T>(from)?;789		let token = token_id.try_into()?;790		let budget = self791			.recorder792			.weight_calls_budget(<StructureWeight<T>>::find_parent());793794		let balance = balance(self, token, &from)?;795		ensure_single_owner(self, token, balance)?;796797		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)798			.map_err(dispatch_to_evm::<T>)?;799		Ok(())800	}801802	/// @notice Returns next free RFT ID.803	fn next_token_id(&self) -> Result<uint256> {804		self.consume_store_reads(1)?;805		Ok(<TokensMinted<T>>::get(self.id)806			.checked_add(1)807			.ok_or("item id overflow")?808			.into())809	}810811	/// @notice Function to mint multiple tokens.812	/// @dev `tokenIds` should be an array of consecutive numbers and first number813	///  should be obtained with `nextTokenId` method814	/// @param to The new owner815	/// @param tokenIds IDs of the minted RFTs816	#[solidity(hide)]817	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]818	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {819		let caller = T::CrossAccountId::from_eth(caller);820		let to = T::CrossAccountId::from_eth(to);821		let mut expected_index = <TokensMinted<T>>::get(self.id)822			.checked_add(1)823			.ok_or("item id overflow")?;824		let budget = self825			.recorder826			.weight_calls_budget(<StructureWeight<T>>::find_parent());827828		let total_tokens = token_ids.len();829		for id in token_ids.into_iter() {830			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;831			if id != expected_index {832				return Err("item id should be next".into());833			}834			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;835		}836		let users = [(to.clone(), 1)]837			.into_iter()838			.collect::<BTreeMap<_, _>>()839			.try_into()840			.unwrap();841		let create_item_data = CreateItemData::<T::CrossAccountId> {842			users,843			properties: CollectionPropertiesVec::default(),844		};845		let data = (0..total_tokens)846			.map(|_| create_item_data.clone())847			.collect();848849		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)850			.map_err(dispatch_to_evm::<T>)?;851		Ok(true)852	}853854	/// @notice Function to mint multiple tokens with the given tokenUris.855	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive856	///  numbers and first number should be obtained with `nextTokenId` method857	/// @param to The new owner858	/// @param tokens array of pairs of token ID and token URI for minted tokens859	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]860	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]861	fn mint_bulk_with_token_uri(862		&mut self,863		caller: caller,864		to: address,865		tokens: Vec<(uint256, string)>,866	) -> Result<bool> {867		let key = key::url();868		let caller = T::CrossAccountId::from_eth(caller);869		let to = T::CrossAccountId::from_eth(to);870		let mut expected_index = <TokensMinted<T>>::get(self.id)871			.checked_add(1)872			.ok_or("item id overflow")?;873		let budget = self874			.recorder875			.weight_calls_budget(<StructureWeight<T>>::find_parent());876877		let mut data = Vec::with_capacity(tokens.len());878		let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]879			.into_iter()880			.collect::<BTreeMap<_, _>>()881			.try_into()882			.unwrap();883		for (id, token_uri) in tokens {884			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;885			if id != expected_index {886				return Err("item id should be next".into());887			}888			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;889890			let mut properties = CollectionPropertiesVec::default();891			properties892				.try_push(Property {893					key: key.clone(),894					value: token_uri895						.into_bytes()896						.try_into()897						.map_err(|_| "token uri is too long")?,898				})899				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;900901			let create_item_data = CreateItemData::<T::CrossAccountId> {902				users: users.clone(),903				properties,904			};905			data.push(create_item_data);906		}907908		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)909			.map_err(dispatch_to_evm::<T>)?;910		Ok(true)911	}912913	/// Returns EVM address for refungible token914	///915	/// @param token ID of the token916	fn token_contract_address(&self, token: uint256) -> Result<address> {917		Ok(T::EvmTokenAddressMapping::token_to_address(918			self.id,919			token.try_into().map_err(|_| "token id overflow")?,920		))921	}922}923924#[solidity_interface(925	name = UniqueRefungible,926	is(927		ERC721,928		ERC721Enumerable,929		ERC721UniqueExtensions,930		ERC721UniqueMintable,931		ERC721Burnable,932		ERC721Metadata(if(this.flags.erc721metadata)),933		Collection(via(common_mut returns CollectionHandle<T>)),934		TokenProperties,935	)936)]937impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}938939// Not a tests, but code generators940generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);941generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);942943impl<T: Config> CommonEvmHandler for RefungibleHandle<T>944where945	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,946{947	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");948	fn call(949		self,950		handle: &mut impl PrecompileHandle,951	) -> Option<pallet_common::erc::PrecompileResult> {952		call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)953	}954}