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

difftreelog

feat Add AbiWrite support for vec with dynamic type

Trubnikov Sergey2022-10-24parent: #88bc48d.patch.diff
in: master

6 files changed

modifiedcrates/evm-coder/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -22,6 +22,7 @@
 # We want to assert some large binary blobs equality in tests
 hex = "0.4.3"
 hex-literal = "0.3.4"
+similar-asserts = "1.4.2"
 
 [features]
 default = ["std"]
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -25,7 +25,7 @@
 
 use crate::{
 	execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},
-	types::{string, self},
+	types::*,
 };
 use crate::execution::Result;
 
@@ -56,7 +56,7 @@
 		}
 	}
 	/// Start reading RLP buffer, parsing first 4 bytes as selector
-	pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {
+	pub fn new_call(buf: &'i [u8]) -> Result<(bytes4, Self)> {
 		if buf.len() < 4 {
 			return Err(Error::Error(ExitError::OutOfOffset));
 		}
@@ -252,11 +252,11 @@
 		self.static_part.extend(block);
 	}
 
-	fn write_padright(&mut self, bytes: &[u8]) {
-		assert!(bytes.len() <= ABI_ALIGNMENT);
-		self.static_part.extend(bytes);
+	fn write_padright(&mut self, block: &[u8]) {
+		assert!(block.len() <= ABI_ALIGNMENT);
+		self.static_part.extend(block);
 		self.static_part
-			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);
+			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);
 	}
 
 	/// Write [`H160`] to end of buffer
@@ -369,16 +369,30 @@
 	};
 }
 
-impl_abi_readable!(u8, uint8, false);
-impl_abi_readable!(u32, uint32, false);
-impl_abi_readable!(u64, uint64, false);
-impl_abi_readable!(u128, uint128, false);
-impl_abi_readable!(U256, uint256, false);
-impl_abi_readable!([u8; 4], bytes4, false);
-impl_abi_readable!(H160, address, false);
-impl_abi_readable!(Vec<u8>, bytes, true);
-impl_abi_readable!(bool, bool, true);
+impl_abi_readable!(bool, bool, false);
+impl_abi_readable!(uint8, uint8, false);
+impl_abi_readable!(uint32, uint32, false);
+impl_abi_readable!(uint64, uint64, false);
+impl_abi_readable!(uint128, uint128, false);
+impl_abi_readable!(uint256, uint256, false);
+impl_abi_readable!(bytes4, bytes4, false);
+impl_abi_readable!(address, address, false);
 impl_abi_readable!(string, string, true);
+// impl_abi_readable!(bytes, bytes, true);
+
+impl TypeHelper for bytes {
+	fn is_dynamic() -> bool {
+		true
+	}
+	fn size() -> usize {
+		ABI_ALIGNMENT
+	}
+}
+impl AbiRead<bytes> for AbiReader<'_> {
+	fn abi_read(&mut self) -> Result<bytes> {
+		Ok(bytes(self.bytes()?))
+	}
+}
 
 mod sealed {
 	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead
@@ -524,17 +538,19 @@
 impl_abi_writeable!(H160, address);
 impl_abi_writeable!(bool, bool);
 impl_abi_writeable!(&str, string);
+
 impl AbiWrite for string {
 	fn abi_write(&self, writer: &mut AbiWriter) {
 		writer.string(self)
 	}
 }
-// impl AbiWrite for Vec<u8> {
-// 	fn abi_write(&self, writer: &mut AbiWriter) {
-// 		writer.bytes(self)
-// 	}
-// }
 
+impl AbiWrite for bytes {
+	fn abi_write(&self, writer: &mut AbiWriter) {
+		writer.bytes(self.0.as_slice())
+	}
+}
+
 impl<T: AbiWrite + TypeHelper> AbiWrite for Vec<T> {
 	fn abi_write(&self, writer: &mut AbiWriter) {
 		let is_dynamic = T::is_dynamic();
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -133,8 +133,10 @@
 	pub type string = ::alloc::string::String;
 	#[cfg(feature = "std")]
 	pub type string = ::std::string::String;
-	pub type bytes = Vec<u8>;
 
+	#[derive(Default, Debug)]
+	pub struct bytes(pub Vec<u8>);
+
 	/// Solidity doesn't have `void` type, however we have special implementation
 	/// for empty tuple return type
 	pub type void = ();
@@ -157,6 +159,30 @@
 		/// and there is no `receiver()` function defined.
 		pub value: U256,
 	}
+
+	impl From<Vec<u8>> for bytes {
+		fn from(src: Vec<u8>) -> Self {
+			Self(src)
+		}
+	}
+
+	impl Into<Vec<u8>> for bytes {
+		fn into(self) -> Vec<u8> {
+			self.0
+		}
+	}
+
+	impl bytes {
+		#[must_use]
+		pub fn len(&self) -> usize {
+			self.0.len()
+		}
+
+		#[must_use]
+		pub fn is_empty(&self) -> bool {
+			self.len() == 0
+		}
+	}
 }
 
 /// Parseable EVM call, this trait should be implemented with [`solidity_interface`] macro
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -85,7 +85,7 @@
 		let key = <Vec<u8>>::from(key)
 			.try_into()
 			.map_err(|_| "key too large")?;
-		let value = value.try_into().map_err(|_| "value too large")?;
+		let value = value.0.try_into().map_err(|_| "value too large")?;
 
 		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })
 			.map_err(dispatch_to_evm::<T>)
@@ -120,7 +120,7 @@
 		let props = <CollectionProperties<T>>::get(self.id);
 		let prop = props.get(&key).ok_or("key not found")?;
 
-		Ok(prop.to_vec())
+		Ok(bytes(prop.to_vec()))
 	}
 
 	/// Set the sponsor of the collection.
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
before · pallets/nonfungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24	char::{REPLACEMENT_CHARACTER, decode_utf16},25	convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31	CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37	CollectionHandle, CollectionPropertyPermissions,38};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::call;41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4243use crate::{44	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,45	SelfWeightOf, weights::WeightInfo, TokenProperties,46};4748/// @title A contract that allows to set and delete token properties and change token property permissions.49#[solidity_interface(name = TokenProperties)]50impl<T: Config> NonfungibleHandle<T> {51	/// @notice Set permissions for token property.52	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.53	/// @param key Property key.54	/// @param isMutable Permission to mutate property.55	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.56	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.57	fn set_token_property_permission(58		&mut self,59		caller: caller,60		key: string,61		is_mutable: bool,62		collection_admin: bool,63		token_owner: bool,64	) -> Result<()> {65		let caller = T::CrossAccountId::from_eth(caller);66		<Pallet<T>>::set_property_permission(67			self,68			&caller,69			PropertyKeyPermission {70				key: <Vec<u8>>::from(key)71					.try_into()72					.map_err(|_| "too long key")?,73				permission: PropertyPermission {74					mutable: is_mutable,75					collection_admin,76					token_owner,77				},78			},79		)80		.map_err(dispatch_to_evm::<T>)81	}8283	/// @notice Set token property value.84	/// @dev Throws error if `msg.sender` has no permission to edit the property.85	/// @param tokenId ID of the token.86	/// @param key Property key.87	/// @param value Property value.88	fn set_property(89		&mut self,90		caller: caller,91		token_id: uint256,92		key: string,93		value: bytes,94	) -> Result<()> {95		let caller = T::CrossAccountId::from_eth(caller);96		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;97		let key = <Vec<u8>>::from(key)98			.try_into()99			.map_err(|_| "key too long")?;100		let value = value.try_into().map_err(|_| "value too long")?;101102		let nesting_budget = self103			.recorder104			.weight_calls_budget(<StructureWeight<T>>::find_parent());105106		<Pallet<T>>::set_token_property(107			self,108			&caller,109			TokenId(token_id),110			Property { key, value },111			&nesting_budget,112		)113		.map_err(dispatch_to_evm::<T>)114	}115116	/// @notice Delete token property value.117	/// @dev Throws error if `msg.sender` has no permission to edit the property.118	/// @param tokenId ID of the token.119	/// @param key Property key.120	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {121		let caller = T::CrossAccountId::from_eth(caller);122		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;123		let key = <Vec<u8>>::from(key)124			.try_into()125			.map_err(|_| "key too long")?;126127		let nesting_budget = self128			.recorder129			.weight_calls_budget(<StructureWeight<T>>::find_parent());130131		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)132			.map_err(dispatch_to_evm::<T>)133	}134135	/// @notice Get token property value.136	/// @dev Throws error if key not found137	/// @param tokenId ID of the token.138	/// @param key Property key.139	/// @return Property value bytes140	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {141		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;142		let key = <Vec<u8>>::from(key)143			.try_into()144			.map_err(|_| "key too long")?;145146		let props = <TokenProperties<T>>::get((self.id, token_id));147		let prop = props.get(&key).ok_or("key not found")?;148149		Ok(prop.to_vec())150	}151}152153#[derive(ToLog)]154pub enum ERC721Events {155	/// @dev This emits when ownership of any NFT changes by any mechanism.156	///  This event emits when NFTs are created (`from` == 0) and destroyed157	///  (`to` == 0). Exception: during contract creation, any number of NFTs158	///  may be created and assigned without emitting Transfer. At the time of159	///  any transfer, the approved address for that NFT (if any) is reset to none.160	Transfer {161		#[indexed]162		from: address,163		#[indexed]164		to: address,165		#[indexed]166		token_id: uint256,167	},168	/// @dev This emits when the approved address for an NFT is changed or169	///  reaffirmed. The zero address indicates there is no approved address.170	///  When a Transfer event emits, this also indicates that the approved171	///  address for that NFT (if any) is reset to none.172	Approval {173		#[indexed]174		owner: address,175		#[indexed]176		approved: address,177		#[indexed]178		token_id: uint256,179	},180	/// @dev This emits when an operator is enabled or disabled for an owner.181	///  The operator can manage all NFTs of the owner.182	#[allow(dead_code)]183	ApprovalForAll {184		#[indexed]185		owner: address,186		#[indexed]187		operator: address,188		approved: bool,189	},190}191192#[derive(ToLog)]193pub enum ERC721UniqueMintableEvents {194	#[allow(dead_code)]195	MintingFinished {},196}197198/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension199/// @dev See https://eips.ethereum.org/EIPS/eip-721200#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]201impl<T: Config> NonfungibleHandle<T> {202	/// @notice A descriptive name for a collection of NFTs in this contract203	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`204	#[solidity(hide, rename_selector = "name")]205	fn name_proxy(&self) -> Result<string> {206		self.name()207	}208209	/// @notice An abbreviated name for NFTs in this contract210	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`211	#[solidity(hide, rename_selector = "symbol")]212	fn symbol_proxy(&self) -> Result<string> {213		self.symbol()214	}215216	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.217	///218	/// @dev If the token has a `url` property and it is not empty, it is returned.219	///  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`.220	///  If the collection property `baseURI` is empty or absent, return "" (empty string)221	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix222	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).223	///224	/// @return token's const_metadata225	#[solidity(rename_selector = "tokenURI")]226	fn token_uri(&self, token_id: uint256) -> Result<string> {227		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;228229		match get_token_property(self, token_id_u32, &key::url()).as_deref() {230			Err(_) | Ok("") => (),231			Ok(url) => {232				return Ok(url.into());233			}234		};235236		let base_uri =237			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())238				.map(BoundedVec::into_inner)239				.map(string::from_utf8)240				.transpose()241				.map_err(|e| {242					Error::Revert(alloc::format!(243						"Can not convert value \"baseURI\" to string with error \"{}\"",244						e245					))246				})?;247248		let base_uri = match base_uri.as_deref() {249			None | Some("") => {250				return Ok("".into());251			}252			Some(base_uri) => base_uri.into(),253		};254255		Ok(256			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {257				Err(_) | Ok("") => base_uri,258				Ok(suffix) => base_uri + suffix,259			},260		)261	}262}263264/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension265/// @dev See https://eips.ethereum.org/EIPS/eip-721266#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]267impl<T: Config> NonfungibleHandle<T> {268	/// @notice Enumerate valid NFTs269	/// @param index A counter less than `totalSupply()`270	/// @return The token identifier for the `index`th NFT,271	///  (sort order not specified)272	fn token_by_index(&self, index: uint256) -> Result<uint256> {273		Ok(index)274	}275276	/// @dev Not implemented277	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {278		// TODO: Not implemetable279		Err("not implemented".into())280	}281282	/// @notice Count NFTs tracked by this contract283	/// @return A count of valid NFTs tracked by this contract, where each one of284	///  them has an assigned and queryable owner not equal to the zero address285	fn total_supply(&self) -> Result<uint256> {286		self.consume_store_reads(1)?;287		Ok(<Pallet<T>>::total_supply(self).into())288	}289}290291/// @title ERC-721 Non-Fungible Token Standard292/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md293#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]294impl<T: Config> NonfungibleHandle<T> {295	/// @notice Count all NFTs assigned to an owner296	/// @dev NFTs assigned to the zero address are considered invalid, and this297	///  function throws for queries about the zero address.298	/// @param owner An address for whom to query the balance299	/// @return The number of NFTs owned by `owner`, possibly zero300	fn balance_of(&self, owner: address) -> Result<uint256> {301		self.consume_store_reads(1)?;302		let owner = T::CrossAccountId::from_eth(owner);303		let balance = <AccountBalance<T>>::get((self.id, owner));304		Ok(balance.into())305	}306	/// @notice Find the owner of an NFT307	/// @dev NFTs assigned to zero address are considered invalid, and queries308	///  about them do throw.309	/// @param tokenId The identifier for an NFT310	/// @return The address of the owner of the NFT311	fn owner_of(&self, token_id: uint256) -> Result<address> {312		self.consume_store_reads(1)?;313		let token: TokenId = token_id.try_into()?;314		Ok(*<TokenData<T>>::get((self.id, token))315			.ok_or("token not found")?316			.owner317			.as_eth())318	}319	/// @dev Not implemented320	#[solidity(rename_selector = "safeTransferFrom")]321	fn safe_transfer_from_with_data(322		&mut self,323		_from: address,324		_to: address,325		_token_id: uint256,326		_data: bytes,327	) -> Result<void> {328		// TODO: Not implemetable329		Err("not implemented".into())330	}331	/// @dev Not implemented332	fn safe_transfer_from(333		&mut self,334		_from: address,335		_to: address,336		_token_id: uint256,337	) -> Result<void> {338		// TODO: Not implemetable339		Err("not implemented".into())340	}341342	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE343	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE344	///  THEY MAY BE PERMANENTLY LOST345	/// @dev Throws unless `msg.sender` is the current owner or an authorized346	///  operator for this NFT. Throws if `from` is not the current owner. Throws347	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.348	/// @param from The current owner of the NFT349	/// @param to The new owner350	/// @param tokenId The NFT to transfer351	#[weight(<SelfWeightOf<T>>::transfer_from())]352	fn transfer_from(353		&mut self,354		caller: caller,355		from: address,356		to: address,357		token_id: uint256,358	) -> Result<void> {359		let caller = T::CrossAccountId::from_eth(caller);360		let from = T::CrossAccountId::from_eth(from);361		let to = T::CrossAccountId::from_eth(to);362		let token = token_id.try_into()?;363		let budget = self364			.recorder365			.weight_calls_budget(<StructureWeight<T>>::find_parent());366367		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)368			.map_err(dispatch_to_evm::<T>)?;369		Ok(())370	}371372	/// @notice Set or reaffirm the approved address for an NFT373	/// @dev The zero address indicates there is no approved address.374	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized375	///  operator of the current owner.376	/// @param approved The new approved NFT controller377	/// @param tokenId The NFT to approve378	#[weight(<SelfWeightOf<T>>::approve())]379	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {380		let caller = T::CrossAccountId::from_eth(caller);381		let approved = T::CrossAccountId::from_eth(approved);382		let token = token_id.try_into()?;383384		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))385			.map_err(dispatch_to_evm::<T>)?;386		Ok(())387	}388389	/// @dev Not implemented390	fn set_approval_for_all(391		&mut self,392		_caller: caller,393		_operator: address,394		_approved: bool,395	) -> Result<void> {396		// TODO: Not implemetable397		Err("not implemented".into())398	}399400	/// @dev Not implemented401	fn get_approved(&self, _token_id: uint256) -> Result<address> {402		// TODO: Not implemetable403		Err("not implemented".into())404	}405406	/// @dev Not implemented407	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {408		// TODO: Not implemetable409		Err("not implemented".into())410	}411}412413/// @title ERC721 Token that can be irreversibly burned (destroyed).414#[solidity_interface(name = ERC721Burnable)]415impl<T: Config> NonfungibleHandle<T> {416	/// @notice Burns a specific ERC721 token.417	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized418	///  operator of the current owner.419	/// @param tokenId The NFT to approve420	#[weight(<SelfWeightOf<T>>::burn_item())]421	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {422		let caller = T::CrossAccountId::from_eth(caller);423		let token = token_id.try_into()?;424425		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;426		Ok(())427	}428}429430/// @title ERC721 minting logic.431#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]432impl<T: Config> NonfungibleHandle<T> {433	fn minting_finished(&self) -> Result<bool> {434		Ok(false)435	}436437	/// @notice Function to mint token.438	/// @param to The new owner439	/// @return uint256 The id of the newly minted token440	#[weight(<SelfWeightOf<T>>::create_item())]441	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {442		let token_id: uint256 = <TokensMinted<T>>::get(self.id)443			.checked_add(1)444			.ok_or("item id overflow")?445			.into();446		self.mint_check_id(caller, to, token_id)?;447		Ok(token_id)448	}449450	/// @notice Function to mint token.451	/// @dev `tokenId` should be obtained with `nextTokenId` method,452	///  unlike standard, you can't specify it manually453	/// @param to The new owner454	/// @param tokenId ID of the minted NFT455	#[solidity(hide, rename_selector = "mint")]456	#[weight(<SelfWeightOf<T>>::create_item())]457	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {458		let caller = T::CrossAccountId::from_eth(caller);459		let to = T::CrossAccountId::from_eth(to);460		let token_id: u32 = token_id.try_into()?;461		let budget = self462			.recorder463			.weight_calls_budget(<StructureWeight<T>>::find_parent());464465		if <TokensMinted<T>>::get(self.id)466			.checked_add(1)467			.ok_or("item id overflow")?468			!= token_id469		{470			return Err("item id should be next".into());471		}472473		<Pallet<T>>::create_item(474			self,475			&caller,476			CreateItemData::<T> {477				properties: BoundedVec::default(),478				owner: to,479			},480			&budget,481		)482		.map_err(dispatch_to_evm::<T>)?;483484		Ok(true)485	}486487	/// @notice Function to mint token with the given tokenUri.488	/// @param to The new owner489	/// @param tokenUri Token URI that would be stored in the NFT properties490	/// @return uint256 The id of the newly minted token491	#[solidity(rename_selector = "mintWithTokenURI")]492	#[weight(<SelfWeightOf<T>>::create_item())]493	fn mint_with_token_uri(494		&mut self,495		caller: caller,496		to: address,497		token_uri: string,498	) -> Result<uint256> {499		let token_id: uint256 = <TokensMinted<T>>::get(self.id)500			.checked_add(1)501			.ok_or("item id overflow")?502			.into();503		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;504		Ok(token_id)505	}506507	/// @notice Function to mint token with the given tokenUri.508	/// @dev `tokenId` should be obtained with `nextTokenId` method,509	///  unlike standard, you can't specify it manually510	/// @param to The new owner511	/// @param tokenId ID of the minted NFT512	/// @param tokenUri Token URI that would be stored in the NFT properties513	#[solidity(hide, rename_selector = "mintWithTokenURI")]514	#[weight(<SelfWeightOf<T>>::create_item())]515	fn mint_with_token_uri_check_id(516		&mut self,517		caller: caller,518		to: address,519		token_id: uint256,520		token_uri: string,521	) -> Result<bool> {522		let key = key::url();523		let permission = get_token_permission::<T>(self.id, &key)?;524		if !permission.collection_admin {525			return Err("Operation is not allowed".into());526		}527528		let caller = T::CrossAccountId::from_eth(caller);529		let to = T::CrossAccountId::from_eth(to);530		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;531		let budget = self532			.recorder533			.weight_calls_budget(<StructureWeight<T>>::find_parent());534535		if <TokensMinted<T>>::get(self.id)536			.checked_add(1)537			.ok_or("item id overflow")?538			!= token_id539		{540			return Err("item id should be next".into());541		}542543		let mut properties = CollectionPropertiesVec::default();544		properties545			.try_push(Property {546				key,547				value: token_uri548					.into_bytes()549					.try_into()550					.map_err(|_| "token uri is too long")?,551			})552			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;553554		<Pallet<T>>::create_item(555			self,556			&caller,557			CreateItemData::<T> {558				properties,559				owner: to,560			},561			&budget,562		)563		.map_err(dispatch_to_evm::<T>)?;564		Ok(true)565	}566567	/// @dev Not implemented568	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {569		Err("not implementable".into())570	}571}572573fn get_token_property<T: Config>(574	collection: &CollectionHandle<T>,575	token_id: u32,576	key: &up_data_structs::PropertyKey,577) -> Result<string> {578	collection.consume_store_reads(1)?;579	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))580		.map_err(|_| Error::Revert("Token properties not found".into()))?;581	if let Some(property) = properties.get(key) {582		return Ok(string::from_utf8_lossy(property).into());583	}584585	Err("Property tokenURI not found".into())586}587588fn get_token_permission<T: Config>(589	collection_id: CollectionId,590	key: &PropertyKey,591) -> Result<PropertyPermission> {592	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)593		.map_err(|_| Error::Revert("No permissions for collection".into()))?;594	let a = token_property_permissions595		.get(key)596		.map(Clone::clone)597		.ok_or_else(|| {598			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();599			Error::Revert(alloc::format!("No permission for key {}", key))600		})?;601	Ok(a)602}603604/// @title Unique extensions for ERC721.605#[solidity_interface(name = ERC721UniqueExtensions)]606impl<T: Config> NonfungibleHandle<T> {607	/// @notice A descriptive name for a collection of NFTs in this contract608	fn name(&self) -> Result<string> {609		Ok(decode_utf16(self.name.iter().copied())610			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))611			.collect::<string>())612	}613614	/// @notice An abbreviated name for NFTs in this contract615	fn symbol(&self) -> Result<string> {616		Ok(string::from_utf8_lossy(&self.token_prefix).into())617	}618619	/// @notice Transfer ownership of an NFT620	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`621	///  is the zero address. Throws if `tokenId` is not a valid NFT.622	/// @param to The new owner623	/// @param tokenId The NFT to transfer624	#[weight(<SelfWeightOf<T>>::transfer())]625	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {626		let caller = T::CrossAccountId::from_eth(caller);627		let to = T::CrossAccountId::from_eth(to);628		let token = token_id.try_into()?;629		let budget = self630			.recorder631			.weight_calls_budget(<StructureWeight<T>>::find_parent());632633		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;634		Ok(())635	}636637	/// @notice Burns a specific ERC721 token.638	/// @dev Throws unless `msg.sender` is the current owner or an authorized639	///  operator for this NFT. Throws if `from` is not the current owner. Throws640	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.641	/// @param from The current owner of the NFT642	/// @param tokenId The NFT to transfer643	#[weight(<SelfWeightOf<T>>::burn_from())]644	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {645		let caller = T::CrossAccountId::from_eth(caller);646		let from = T::CrossAccountId::from_eth(from);647		let token = token_id.try_into()?;648		let budget = self649			.recorder650			.weight_calls_budget(<StructureWeight<T>>::find_parent());651652		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)653			.map_err(dispatch_to_evm::<T>)?;654		Ok(())655	}656657	/// @notice Returns next free NFT ID.658	fn next_token_id(&self) -> Result<uint256> {659		self.consume_store_reads(1)?;660		Ok(<TokensMinted<T>>::get(self.id)661			.checked_add(1)662			.ok_or("item id overflow")?663			.into())664	}665666	/// @notice Function to mint multiple tokens.667	/// @dev `tokenIds` should be an array of consecutive numbers and first number668	///  should be obtained with `nextTokenId` method669	/// @param to The new owner670	/// @param tokenIds IDs of the minted NFTs671	#[solidity(hide)]672	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]673	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {674		let caller = T::CrossAccountId::from_eth(caller);675		let to = T::CrossAccountId::from_eth(to);676		let mut expected_index = <TokensMinted<T>>::get(self.id)677			.checked_add(1)678			.ok_or("item id overflow")?;679		let budget = self680			.recorder681			.weight_calls_budget(<StructureWeight<T>>::find_parent());682683		let total_tokens = token_ids.len();684		for id in token_ids.into_iter() {685			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;686			if id != expected_index {687				return Err("item id should be next".into());688			}689			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;690		}691		let data = (0..total_tokens)692			.map(|_| CreateItemData::<T> {693				properties: BoundedVec::default(),694				owner: to.clone(),695			})696			.collect();697698		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)699			.map_err(dispatch_to_evm::<T>)?;700		Ok(true)701	}702703	/// @notice Function to mint multiple tokens with the given tokenUris.704	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive705	///  numbers and first number should be obtained with `nextTokenId` method706	/// @param to The new owner707	/// @param tokens array of pairs of token ID and token URI for minted tokens708	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]709	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]710	fn mint_bulk_with_token_uri(711		&mut self,712		caller: caller,713		to: address,714		tokens: Vec<(uint256, string)>,715	) -> Result<bool> {716		let key = key::url();717		let caller = T::CrossAccountId::from_eth(caller);718		let to = T::CrossAccountId::from_eth(to);719		let mut expected_index = <TokensMinted<T>>::get(self.id)720			.checked_add(1)721			.ok_or("item id overflow")?;722		let budget = self723			.recorder724			.weight_calls_budget(<StructureWeight<T>>::find_parent());725726		let mut data = Vec::with_capacity(tokens.len());727		for (id, token_uri) in tokens {728			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;729			if id != expected_index {730				return Err("item id should be next".into());731			}732			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;733734			let mut properties = CollectionPropertiesVec::default();735			properties736				.try_push(Property {737					key: key.clone(),738					value: token_uri739						.into_bytes()740						.try_into()741						.map_err(|_| "token uri is too long")?,742				})743				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;744745			data.push(CreateItemData::<T> {746				properties,747				owner: to.clone(),748			});749		}750751		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)752			.map_err(dispatch_to_evm::<T>)?;753		Ok(true)754	}755}756757#[solidity_interface(758	name = UniqueNFT,759	is(760		ERC721,761		ERC721Enumerable,762		ERC721UniqueExtensions,763		ERC721UniqueMintable,764		ERC721Burnable,765		ERC721Metadata(if(this.flags.erc721metadata)),766		Collection(via(common_mut returns CollectionHandle<T>)),767		TokenProperties,768	)769)]770impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}771772// Not a tests, but code generators773generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);774generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);775776impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>777where778	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,779{780	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");781782	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {783		call::<T, UniqueNFTCall<T>, _, _>(handle, self)784	}785}
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -100,7 +100,7 @@
 		let key = <Vec<u8>>::from(key)
 			.try_into()
 			.map_err(|_| "key too long")?;
-		let value = value.try_into().map_err(|_| "value too long")?;
+		let value = value.0.try_into().map_err(|_| "value too long")?;
 
 		let nesting_budget = self
 			.recorder
@@ -149,7 +149,7 @@
 		let props = <TokenProperties<T>>::get((self.id, token_id));
 		let prop = props.get(&key).ok_or("key not found")?;
 
-		Ok(prop.to_vec())
+		Ok(prop.to_vec().into())
 	}
 }