12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4546use crate::{47 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,48 SelfWeightOf, weights::WeightInfo, TokenProperties,49};505152#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54 55 56 57 58 59 60 fn set_token_property_permission(61 &mut self,62 caller: caller,63 key: string,64 is_mutable: bool,65 collection_admin: bool,66 token_owner: bool,67 ) -> Result<()> {68 let caller = T::CrossAccountId::from_eth(caller);69 <Pallet<T>>::set_property_permission(70 self,71 &caller,72 PropertyKeyPermission {73 key: <Vec<u8>>::from(key)74 .try_into()75 .map_err(|_| "too long key")?,76 permission: PropertyPermission {77 mutable: is_mutable,78 collection_admin,79 token_owner,80 },81 },82 )83 .map_err(dispatch_to_evm::<T>)84 }8586 87 88 89 90 91 fn set_property(92 &mut self,93 caller: caller,94 token_id: uint256,95 key: string,96 value: bytes,97 ) -> Result<()> {98 let caller = T::CrossAccountId::from_eth(caller);99 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too long")?;103 let value = value.0.try_into().map_err(|_| "value too long")?;104105 let nesting_budget = self106 .recorder107 .weight_calls_budget(<StructureWeight<T>>::find_parent());108109 <Pallet<T>>::set_token_property(110 self,111 &caller,112 TokenId(token_id),113 Property { key, value },114 &nesting_budget,115 )116 .map_err(dispatch_to_evm::<T>)117 }118119 120 121 122 123 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124 fn set_properties(125 &mut self,126 caller: caller,127 token_id: uint256,128 properties: Vec<(string, bytes)>,129 ) -> Result<()> {130 let caller = T::CrossAccountId::from_eth(caller);131 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133 let nesting_budget = self134 .recorder135 .weight_calls_budget(<StructureWeight<T>>::find_parent());136137 let properties = properties138 .into_iter()139 .map(|(key, value)| {140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too large")?;143144 let value = value.0.try_into().map_err(|_| "value too large")?;145146 Ok(Property { key, value })147 })148 .collect::<Result<Vec<_>>>()?;149150 <Pallet<T>>::set_token_properties(151 self,152 &caller,153 TokenId(token_id),154 properties.into_iter(),155 <Pallet<T>>::token_exists(&self, TokenId(token_id)),156 &nesting_budget,157 )158 .map_err(dispatch_to_evm::<T>)159 }160161 162 163 164 165 #[solidity(hide)]166 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {167 let caller = T::CrossAccountId::from_eth(caller);168 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169 let key = <Vec<u8>>::from(key)170 .try_into()171 .map_err(|_| "key too long")?;172173 let nesting_budget = self174 .recorder175 .weight_calls_budget(<StructureWeight<T>>::find_parent());176177 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)178 .map_err(dispatch_to_evm::<T>)179 }180181 182 183 184 185 fn delete_properties(186 &mut self,187 token_id: uint256,188 caller: caller,189 keys: Vec<string>,190 ) -> Result<()> {191 let caller = T::CrossAccountId::from_eth(caller);192 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193 let keys = keys194 .into_iter()195 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))196 .collect::<Result<Vec<_>>>()?;197198 let nesting_budget = self199 .recorder200 .weight_calls_budget(<StructureWeight<T>>::find_parent());201202 <Pallet<T>>::delete_token_properties(203 self,204 &caller,205 TokenId(token_id),206 keys.into_iter(),207 &nesting_budget,208 )209 .map_err(dispatch_to_evm::<T>)210 }211212 213 214 215 216 217 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {218 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;219 let key = <Vec<u8>>::from(key)220 .try_into()221 .map_err(|_| "key too long")?;222223 let props = <TokenProperties<T>>::get((self.id, token_id));224 let prop = props.get(&key).ok_or("key not found")?;225226 Ok(prop.to_vec().into())227 }228}229230#[derive(ToLog)]231pub enum ERC721Events {232 233 234 235 236 237 Transfer {238 #[indexed]239 from: address,240 #[indexed]241 to: address,242 #[indexed]243 token_id: uint256,244 },245 246 247 248 249 Approval {250 #[indexed]251 owner: address,252 #[indexed]253 approved: address,254 #[indexed]255 token_id: uint256,256 },257 258 259 #[allow(dead_code)]260 ApprovalForAll {261 #[indexed]262 owner: address,263 #[indexed]264 operator: address,265 approved: bool,266 },267}268269#[derive(ToLog)]270pub enum ERC721UniqueMintableEvents {271 #[allow(dead_code)]272 MintingFinished {},273}274275276277#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]278impl<T: Config> NonfungibleHandle<T>279where280 T::AccountId: From<[u8; 32]>,281{282 283 284 #[solidity(hide, rename_selector = "name")]285 fn name_proxy(&self) -> Result<string> {286 self.name()287 }288289 290 291 #[solidity(hide, rename_selector = "symbol")]292 fn symbol_proxy(&self) -> Result<string> {293 self.symbol()294 }295296 297 298 299 300 301 302 303 304 305 #[solidity(rename_selector = "tokenURI")]306 fn token_uri(&self, token_id: uint256) -> Result<string> {307 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;308309 match get_token_property(self, token_id_u32, &key::url()).as_deref() {310 Err(_) | Ok("") => (),311 Ok(url) => {312 return Ok(url.into());313 }314 };315316 let base_uri =317 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())318 .map(BoundedVec::into_inner)319 .map(string::from_utf8)320 .transpose()321 .map_err(|e| {322 Error::Revert(alloc::format!(323 "Can not convert value \"baseURI\" to string with error \"{}\"",324 e325 ))326 })?;327328 let base_uri = match base_uri.as_deref() {329 None | Some("") => {330 return Ok("".into());331 }332 Some(base_uri) => base_uri.into(),333 };334335 Ok(336 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {337 Err(_) | Ok("") => base_uri,338 Ok(suffix) => base_uri + suffix,339 },340 )341 }342}343344345346#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]347impl<T: Config> NonfungibleHandle<T> {348 349 350 351 352 fn token_by_index(&self, index: uint256) -> Result<uint256> {353 Ok(index)354 }355356 357 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {358 359 Err("not implemented".into())360 }361362 363 364 365 fn total_supply(&self) -> Result<uint256> {366 self.consume_store_reads(1)?;367 Ok(<Pallet<T>>::total_supply(self).into())368 }369}370371372373#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]374impl<T: Config> NonfungibleHandle<T> {375 376 377 378 379 380 fn balance_of(&self, owner: address) -> Result<uint256> {381 self.consume_store_reads(1)?;382 let owner = T::CrossAccountId::from_eth(owner);383 let balance = <AccountBalance<T>>::get((self.id, owner));384 Ok(balance.into())385 }386 387 388 389 390 391 fn owner_of(&self, token_id: uint256) -> Result<address> {392 self.consume_store_reads(1)?;393 let token: TokenId = token_id.try_into()?;394 Ok(*<TokenData<T>>::get((self.id, token))395 .ok_or("token not found")?396 .owner397 .as_eth())398 }399 400 #[solidity(rename_selector = "safeTransferFrom")]401 fn safe_transfer_from_with_data(402 &mut self,403 _from: address,404 _to: address,405 _token_id: uint256,406 _data: bytes,407 ) -> Result<void> {408 409 Err("not implemented".into())410 }411 412 fn safe_transfer_from(413 &mut self,414 _from: address,415 _to: address,416 _token_id: uint256,417 ) -> Result<void> {418 419 Err("not implemented".into())420 }421422 423 424 425 426 427 428 429 430 431 #[weight(<SelfWeightOf<T>>::transfer_from())]432 fn transfer_from(433 &mut self,434 caller: caller,435 from: address,436 to: address,437 token_id: uint256,438 ) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let from = T::CrossAccountId::from_eth(from);441 let to = T::CrossAccountId::from_eth(to);442 let token = token_id.try_into()?;443 let budget = self444 .recorder445 .weight_calls_budget(<StructureWeight<T>>::find_parent());446447 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)448 .map_err(dispatch_to_evm::<T>)?;449 Ok(())450 }451452 453 454 455 456 457 458 #[weight(<SelfWeightOf<T>>::approve())]459 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {460 let caller = T::CrossAccountId::from_eth(caller);461 let approved = T::CrossAccountId::from_eth(approved);462 let token = token_id.try_into()?;463464 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))465 .map_err(dispatch_to_evm::<T>)?;466 Ok(())467 }468469 470 fn set_approval_for_all(471 &mut self,472 _caller: caller,473 _operator: address,474 _approved: bool,475 ) -> Result<void> {476 477 Err("not implemented".into())478 }479480 481 fn get_approved(&self, _token_id: uint256) -> Result<address> {482 483 Err("not implemented".into())484 }485486 487 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {488 489 Err("not implemented".into())490 }491}492493494#[solidity_interface(name = ERC721Burnable)]495impl<T: Config> NonfungibleHandle<T> {496 497 498 499 500 #[weight(<SelfWeightOf<T>>::burn_item())]501 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {502 let caller = T::CrossAccountId::from_eth(caller);503 let token = token_id.try_into()?;504505 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;506 Ok(())507 }508}509510511#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]512impl<T: Config> NonfungibleHandle<T> {513 fn minting_finished(&self) -> Result<bool> {514 Ok(false)515 }516517 518 519 520 #[weight(<SelfWeightOf<T>>::create_item())]521 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {522 let token_id: uint256 = <TokensMinted<T>>::get(self.id)523 .checked_add(1)524 .ok_or("item id overflow")?525 .into();526 self.mint_check_id(caller, to, token_id)?;527 Ok(token_id)528 }529530 531 532 533 534 535 #[solidity(hide, rename_selector = "mint")]536 #[weight(<SelfWeightOf<T>>::create_item())]537 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {538 let caller = T::CrossAccountId::from_eth(caller);539 let to = T::CrossAccountId::from_eth(to);540 let token_id: u32 = token_id.try_into()?;541 let budget = self542 .recorder543 .weight_calls_budget(<StructureWeight<T>>::find_parent());544545 if <TokensMinted<T>>::get(self.id)546 .checked_add(1)547 .ok_or("item id overflow")?548 != token_id549 {550 return Err("item id should be next".into());551 }552553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T> {557 properties: BoundedVec::default(),558 owner: to,559 },560 &budget,561 )562 .map_err(dispatch_to_evm::<T>)?;563564 Ok(true)565 }566567 568 569 570 571 #[solidity(rename_selector = "mintWithTokenURI")]572 #[weight(<SelfWeightOf<T>>::create_item())]573 fn mint_with_token_uri(574 &mut self,575 caller: caller,576 to: address,577 token_uri: string,578 ) -> Result<uint256> {579 let token_id: uint256 = <TokensMinted<T>>::get(self.id)580 .checked_add(1)581 .ok_or("item id overflow")?582 .into();583 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584 Ok(token_id)585 }586587 588 589 590 591 592 593 #[solidity(hide, rename_selector = "mintWithTokenURI")]594 #[weight(<SelfWeightOf<T>>::create_item())]595 fn mint_with_token_uri_check_id(596 &mut self,597 caller: caller,598 to: address,599 token_id: uint256,600 token_uri: string,601 ) -> Result<bool> {602 let key = key::url();603 let permission = get_token_permission::<T>(self.id, &key)?;604 if !permission.collection_admin {605 return Err("Operation is not allowed".into());606 }607608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 if <TokensMinted<T>>::get(self.id)616 .checked_add(1)617 .ok_or("item id overflow")?618 != token_id619 {620 return Err("item id should be next".into());621 }622623 let mut properties = CollectionPropertiesVec::default();624 properties625 .try_push(Property {626 key,627 value: token_uri628 .into_bytes()629 .try_into()630 .map_err(|_| "token uri is too long")?,631 })632 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634 <Pallet<T>>::create_item(635 self,636 &caller,637 CreateItemData::<T> {638 properties,639 owner: to,640 },641 &budget,642 )643 .map_err(dispatch_to_evm::<T>)?;644 Ok(true)645 }646647 648 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {649 Err("not implementable".into())650 }651}652653fn get_token_property<T: Config>(654 collection: &CollectionHandle<T>,655 token_id: u32,656 key: &up_data_structs::PropertyKey,657) -> Result<string> {658 collection.consume_store_reads(1)?;659 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))660 .map_err(|_| Error::Revert("Token properties not found".into()))?;661 if let Some(property) = properties.get(key) {662 return Ok(string::from_utf8_lossy(property).into());663 }664665 Err("Property tokenURI not found".into())666}667668fn get_token_permission<T: Config>(669 collection_id: CollectionId,670 key: &PropertyKey,671) -> Result<PropertyPermission> {672 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)673 .map_err(|_| Error::Revert("No permissions for collection".into()))?;674 let a = token_property_permissions675 .get(key)676 .map(Clone::clone)677 .ok_or_else(|| {678 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();679 Error::Revert(alloc::format!("No permission for key {}", key))680 })?;681 Ok(a)682}683684685#[solidity_interface(name = ERC721UniqueExtensions)]686impl<T: Config> NonfungibleHandle<T>687where688 T::AccountId: From<[u8; 32]>,689{690 691 fn name(&self) -> Result<string> {692 Ok(decode_utf16(self.name.iter().copied())693 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))694 .collect::<string>())695 }696697 698 fn symbol(&self) -> Result<string> {699 Ok(string::from_utf8_lossy(&self.token_prefix).into())700 }701702 703 704 705 706 707 708 #[weight(<SelfWeightOf<T>>::approve())]709 fn approve_cross(710 &mut self,711 caller: caller,712 approved: EthCrossAccount,713 token_id: uint256,714 ) -> Result<void> {715 let caller = T::CrossAccountId::from_eth(caller);716 let approved = approved.into_sub_cross_account::<T>()?;717 let token = token_id.try_into()?;718719 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))720 .map_err(dispatch_to_evm::<T>)?;721 Ok(())722 }723724 725 726 727 728 729 #[weight(<SelfWeightOf<T>>::transfer())]730 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {731 let caller = T::CrossAccountId::from_eth(caller);732 let to = T::CrossAccountId::from_eth(to);733 let token = token_id.try_into()?;734 let budget = self735 .recorder736 .weight_calls_budget(<StructureWeight<T>>::find_parent());737738 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;739 Ok(())740 }741 742 743 744 745 746 747 #[weight(<SelfWeightOf<T>>::transfer())]748 fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {749 let caller = T::CrossAccountId::from_eth(caller);750 let to = to.into_sub_cross_account::<T>()?;751 let token = token_id.try_into()?;752 let budget = self753 .recorder754 .weight_calls_budget(<StructureWeight<T>>::find_parent());755756 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;757 Ok(())758 }759 760 761 762 763 764 765 766 #[weight(<SelfWeightOf<T>>::transfer())]767 fn transfer_from_cross(768 &mut self,769 caller: caller,770 from: EthCrossAccount,771 to: EthCrossAccount,772 token_id: uint256,773 ) -> Result<void> {774 let caller = T::CrossAccountId::from_eth(caller);775 let from = from.into_sub_cross_account::<T>()?;776 let to = to.into_sub_cross_account::<T>()?;777 let token_id = token_id.try_into()?;778 let budget = self779 .recorder780 .weight_calls_budget(<StructureWeight<T>>::find_parent());781 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)782 .map_err(dispatch_to_evm::<T>)?;783 Ok(())784 }785786 787 788 789 790 791 792 #[weight(<SelfWeightOf<T>>::burn_from())]793 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {794 let caller = T::CrossAccountId::from_eth(caller);795 let from = T::CrossAccountId::from_eth(from);796 let token = token_id.try_into()?;797 let budget = self798 .recorder799 .weight_calls_budget(<StructureWeight<T>>::find_parent());800801 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)802 .map_err(dispatch_to_evm::<T>)?;803 Ok(())804 }805806 807 808 809 810 811 812 #[weight(<SelfWeightOf<T>>::burn_from())]813 fn burn_from_cross(814 &mut self,815 caller: caller,816 from: EthCrossAccount,817 token_id: uint256,818 ) -> Result<void> {819 let caller = T::CrossAccountId::from_eth(caller);820 let from = from.into_sub_cross_account::<T>()?;821 let token = token_id.try_into()?;822 let budget = self823 .recorder824 .weight_calls_budget(<StructureWeight<T>>::find_parent());825826 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)827 .map_err(dispatch_to_evm::<T>)?;828 Ok(())829 }830831 832 fn next_token_id(&self) -> Result<uint256> {833 self.consume_store_reads(1)?;834 Ok(<TokensMinted<T>>::get(self.id)835 .checked_add(1)836 .ok_or("item id overflow")?837 .into())838 }839840 841 842 843 844 845 #[solidity(hide)]846 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]847 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {848 let caller = T::CrossAccountId::from_eth(caller);849 let to = T::CrossAccountId::from_eth(to);850 let mut expected_index = <TokensMinted<T>>::get(self.id)851 .checked_add(1)852 .ok_or("item id overflow")?;853 let budget = self854 .recorder855 .weight_calls_budget(<StructureWeight<T>>::find_parent());856857 let total_tokens = token_ids.len();858 for id in token_ids.into_iter() {859 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;860 if id != expected_index {861 return Err("item id should be next".into());862 }863 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;864 }865 let data = (0..total_tokens)866 .map(|_| CreateItemData::<T> {867 properties: BoundedVec::default(),868 owner: to.clone(),869 })870 .collect();871872 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)873 .map_err(dispatch_to_evm::<T>)?;874 Ok(true)875 }876877 878 879 880 881 882 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]883 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]884 fn mint_bulk_with_token_uri(885 &mut self,886 caller: caller,887 to: address,888 tokens: Vec<(uint256, string)>,889 ) -> Result<bool> {890 let key = key::url();891 let caller = T::CrossAccountId::from_eth(caller);892 let to = T::CrossAccountId::from_eth(to);893 let mut expected_index = <TokensMinted<T>>::get(self.id)894 .checked_add(1)895 .ok_or("item id overflow")?;896 let budget = self897 .recorder898 .weight_calls_budget(<StructureWeight<T>>::find_parent());899900 let mut data = Vec::with_capacity(tokens.len());901 for (id, token_uri) in tokens {902 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;903 if id != expected_index {904 return Err("item id should be next".into());905 }906 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;907908 let mut properties = CollectionPropertiesVec::default();909 properties910 .try_push(Property {911 key: key.clone(),912 value: token_uri913 .into_bytes()914 .try_into()915 .map_err(|_| "token uri is too long")?,916 })917 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;918919 data.push(CreateItemData::<T> {920 properties,921 owner: to.clone(),922 });923 }924925 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)926 .map_err(dispatch_to_evm::<T>)?;927 Ok(true)928 }929}930931#[solidity_interface(932 name = UniqueNFT,933 is(934 ERC721,935 ERC721Enumerable,936 ERC721UniqueExtensions,937 ERC721UniqueMintable,938 ERC721Burnable,939 ERC721Metadata(if(this.flags.erc721metadata)),940 Collection(via(common_mut returns CollectionHandle<T>)),941 TokenProperties,942 )943)]944impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}945946947generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);948generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);949950impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>951where952 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,953{954 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");955956 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {957 call::<T, UniqueNFTCall<T>, _, _>(handle, self)958 }959}