12345678910111213141516171819202122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{30 ToLog,31 execution::*,32 generate_stubgen, solidity, solidity_interface,33 types::*,34 weight,35 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},36 make_signature,37};38use frame_support::{BoundedBTreeMap, BoundedVec};39use pallet_common::{40 CollectionHandle, CollectionPropertyPermissions,41 erc::{CommonEvmHandler, CollectionCall, static_property::key},42 eth::convert_tuple_to_cross_account,43};44use pallet_evm::{account::CrossAccountId, PrecompileHandle};45use pallet_evm_coder_substrate::{call, dispatch_to_evm};46use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};47use sp_core::H160;48use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};49use up_data_structs::{50 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,51 PropertyKeyPermission, PropertyPermission, TokenId,52};5354use crate::{55 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,56 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,57};5859pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);606162#[solidity_interface(name = TokenProperties)]63impl<T: Config> RefungibleHandle<T> {64 65 66 67 68 69 70 fn set_token_property_permission(71 &mut self,72 caller: caller,73 key: string,74 is_mutable: bool,75 collection_admin: bool,76 token_owner: bool,77 ) -> Result<()> {78 let caller = T::CrossAccountId::from_eth(caller);79 <Pallet<T>>::set_token_property_permissions(80 self,81 &caller,82 vec![PropertyKeyPermission {83 key: <Vec<u8>>::from(key)84 .try_into()85 .map_err(|_| "too long key")?,86 permission: PropertyPermission {87 mutable: is_mutable,88 collection_admin,89 token_owner,90 },91 }],92 )93 .map_err(dispatch_to_evm::<T>)94 }9596 97 98 99 100 101 fn set_property(102 &mut self,103 caller: caller,104 token_id: uint256,105 key: string,106 value: bytes,107 ) -> Result<()> {108 let caller = T::CrossAccountId::from_eth(caller);109 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110 let key = <Vec<u8>>::from(key)111 .try_into()112 .map_err(|_| "key too long")?;113 let value = value.0.try_into().map_err(|_| "value too long")?;114115 let nesting_budget = self116 .recorder117 .weight_calls_budget(<StructureWeight<T>>::find_parent());118119 <Pallet<T>>::set_token_property(120 self,121 &caller,122 TokenId(token_id),123 Property { key, value },124 &nesting_budget,125 )126 .map_err(dispatch_to_evm::<T>)127 }128129 130 131 132 133 fn set_properties(134 &mut self,135 caller: caller,136 token_id: uint256,137 properties: Vec<(string, bytes)>,138 ) -> Result<()> {139 let caller = T::CrossAccountId::from_eth(caller);140 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;141142 let nesting_budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145146 let properties = properties147 .into_iter()148 .map(|(key, value)| {149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too large")?;152153 let value = value.0.try_into().map_err(|_| "value too large")?;154155 Ok(Property { key, value })156 })157 .collect::<Result<Vec<_>>>()?;158159 <Pallet<T>>::set_token_properties(160 self,161 &caller,162 TokenId(token_id),163 properties.into_iter(),164 <Pallet<T>>::token_exists(&self, TokenId(token_id)),165 &nesting_budget,166 )167 .map_err(dispatch_to_evm::<T>)168 }169170 171 172 173 174 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {175 let caller = T::CrossAccountId::from_eth(caller);176 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;177 let key = <Vec<u8>>::from(key)178 .try_into()179 .map_err(|_| "key too long")?;180181 let nesting_budget = self182 .recorder183 .weight_calls_budget(<StructureWeight<T>>::find_parent());184185 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)186 .map_err(dispatch_to_evm::<T>)187 }188189 190 191 192 193 194 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {195 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;196 let key = <Vec<u8>>::from(key)197 .try_into()198 .map_err(|_| "key too long")?;199200 let props = <TokenProperties<T>>::get((self.id, token_id));201 let prop = props.get(&key).ok_or("key not found")?;202203 Ok(prop.to_vec().into())204 }205}206207#[derive(ToLog)]208pub enum ERC721Events {209 210 211 212 Transfer {213 #[indexed]214 from: address,215 #[indexed]216 to: address,217 #[indexed]218 token_id: uint256,219 },220 221 Approval {222 #[indexed]223 owner: address,224 #[indexed]225 approved: address,226 #[indexed]227 token_id: uint256,228 },229 230 #[allow(dead_code)]231 ApprovalForAll {232 #[indexed]233 owner: address,234 #[indexed]235 operator: address,236 approved: bool,237 },238}239240#[derive(ToLog)]241pub enum ERC721UniqueMintableEvents {242 243 #[allow(dead_code)]244 MintingFinished {},245}246247#[solidity_interface(name = ERC721Metadata)]248impl<T: Config> RefungibleHandle<T>249where250 T::AccountId: From<[u8; 32]>,251{252 253 254 #[solidity(hide, rename_selector = "name")]255 fn name_proxy(&self) -> Result<string> {256 self.name()257 }258259 260 261 #[solidity(hide, rename_selector = "symbol")]262 fn symbol_proxy(&self) -> Result<string> {263 self.symbol()264 }265266 267 268 269 270 271 272 273 274 275 #[solidity(rename_selector = "tokenURI")]276 fn token_uri(&self, token_id: uint256) -> Result<string> {277 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;278279 match get_token_property(self, token_id_u32, &key::url()).as_deref() {280 Err(_) | Ok("") => (),281 Ok(url) => {282 return Ok(url.into());283 }284 };285286 let base_uri =287 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())288 .map(BoundedVec::into_inner)289 .map(string::from_utf8)290 .transpose()291 .map_err(|e| {292 Error::Revert(alloc::format!(293 "Can not convert value \"baseURI\" to string with error \"{}\"",294 e295 ))296 })?;297298 let base_uri = match base_uri.as_deref() {299 None | Some("") => {300 return Ok("".into());301 }302 Some(base_uri) => base_uri.into(),303 };304305 Ok(306 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {307 Err(_) | Ok("") => base_uri,308 Ok(suffix) => base_uri + suffix,309 },310 )311 }312}313314315316#[solidity_interface(name = ERC721Enumerable)]317impl<T: Config> RefungibleHandle<T> {318 319 320 321 322 fn token_by_index(&self, index: uint256) -> Result<uint256> {323 Ok(index)324 }325326 327 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {328 329 Err("not implemented".into())330 }331332 333 334 335 fn total_supply(&self) -> Result<uint256> {336 self.consume_store_reads(1)?;337 Ok(<Pallet<T>>::total_supply(self).into())338 }339}340341342343#[solidity_interface(name = ERC721, events(ERC721Events))]344impl<T: Config> RefungibleHandle<T> {345 346 347 348 349 350 fn balance_of(&self, owner: address) -> Result<uint256> {351 self.consume_store_reads(1)?;352 let owner = T::CrossAccountId::from_eth(owner);353 let balance = <AccountBalance<T>>::get((self.id, owner));354 Ok(balance.into())355 }356357 358 359 360 361 362 363 364 fn owner_of(&self, token_id: uint256) -> Result<address> {365 self.consume_store_reads(2)?;366 let token = token_id.try_into()?;367 let owner = <Pallet<T>>::token_owner(self.id, token);368 Ok(owner369 .map(|address| *address.as_eth())370 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))371 }372373 374 fn safe_transfer_from_with_data(375 &mut self,376 _from: address,377 _to: address,378 _token_id: uint256,379 _data: bytes,380 ) -> Result<void> {381 382 Err("not implemented".into())383 }384385 386 fn safe_transfer_from(387 &mut self,388 _from: address,389 _to: address,390 _token_id: uint256,391 ) -> Result<void> {392 393 Err("not implemented".into())394 }395396 397 398 399 400 401 402 403 404 405 406 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]407 fn transfer_from(408 &mut self,409 caller: caller,410 from: address,411 to: address,412 token_id: uint256,413 ) -> Result<void> {414 let caller = T::CrossAccountId::from_eth(caller);415 let from = T::CrossAccountId::from_eth(from);416 let to = T::CrossAccountId::from_eth(to);417 let token = token_id.try_into()?;418 let budget = self419 .recorder420 .weight_calls_budget(<StructureWeight<T>>::find_parent());421422 let balance = balance(&self, token, &from)?;423 ensure_single_owner(&self, token, balance)?;424425 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)426 .map_err(dispatch_to_evm::<T>)?;427428 Ok(())429 }430431 432 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {433 Err("not implemented".into())434 }435436 437 fn set_approval_for_all(438 &mut self,439 _caller: caller,440 _operator: address,441 _approved: bool,442 ) -> Result<void> {443 444 Err("not implemented".into())445 }446447 448 fn get_approved(&self, _token_id: uint256) -> Result<address> {449 450 Err("not implemented".into())451 }452453 454 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455 456 Err("not implemented".into())457 }458}459460461pub fn balance<T: Config>(462 collection: &RefungibleHandle<T>,463 token: TokenId,464 owner: &T::CrossAccountId,465) -> Result<u128> {466 collection.consume_store_reads(1)?;467 let balance = <Balance<T>>::get((collection.id, token, &owner));468 Ok(balance)469}470471472pub fn ensure_single_owner<T: Config>(473 collection: &RefungibleHandle<T>,474 token: TokenId,475 owner_balance: u128,476) -> Result<()> {477 collection.consume_store_reads(1)?;478 let total_supply = <TotalSupply<T>>::get((collection.id, token));479 if total_supply != owner_balance {480 return Err("token has multiple owners".into());481 }482 Ok(())483}484485486#[solidity_interface(name = ERC721Burnable)]487impl<T: Config> RefungibleHandle<T> {488 489 490 491 492 #[weight(<SelfWeightOf<T>>::burn_item_fully())]493 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {494 let caller = T::CrossAccountId::from_eth(caller);495 let token = token_id.try_into()?;496497 let balance = balance(&self, token, &caller)?;498 ensure_single_owner(&self, token, balance)?;499500 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;501 Ok(())502 }503}504505506#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]507impl<T: Config> RefungibleHandle<T> {508 fn minting_finished(&self) -> Result<bool> {509 Ok(false)510 }511512 513 514 515 #[weight(<SelfWeightOf<T>>::create_item())]516 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {517 let token_id: uint256 = <TokensMinted<T>>::get(self.id)518 .checked_add(1)519 .ok_or("item id overflow")?520 .into();521 self.mint_check_id(caller, to, token_id)?;522 Ok(token_id)523 }524525 526 527 528 529 530 #[solidity(hide, rename_selector = "mint")]531 #[weight(<SelfWeightOf<T>>::create_item())]532 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {533 let caller = T::CrossAccountId::from_eth(caller);534 let to = T::CrossAccountId::from_eth(to);535 let token_id: u32 = token_id.try_into()?;536 let budget = self537 .recorder538 .weight_calls_budget(<StructureWeight<T>>::find_parent());539540 if <TokensMinted<T>>::get(self.id)541 .checked_add(1)542 .ok_or("item id overflow")?543 != token_id544 {545 return Err("item id should be next".into());546 }547548 let users = [(to.clone(), 1)]549 .into_iter()550 .collect::<BTreeMap<_, _>>()551 .try_into()552 .unwrap();553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T::CrossAccountId> {557 users,558 properties: CollectionPropertiesVec::default(),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 let users = [(to.clone(), 1)]635 .into_iter()636 .collect::<BTreeMap<_, _>>()637 .try_into()638 .unwrap();639 <Pallet<T>>::create_item(640 self,641 &caller,642 CreateItemData::<T::CrossAccountId> { users, properties },643 &budget,644 )645 .map_err(dispatch_to_evm::<T>)?;646 Ok(true)647 }648649 650 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {651 Err("not implementable".into())652 }653}654655fn get_token_property<T: Config>(656 collection: &CollectionHandle<T>,657 token_id: u32,658 key: &up_data_structs::PropertyKey,659) -> Result<string> {660 collection.consume_store_reads(1)?;661 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))662 .map_err(|_| Error::Revert("Token properties not found".into()))?;663 if let Some(property) = properties.get(key) {664 return Ok(string::from_utf8_lossy(property).into());665 }666667 Err("Property tokenURI not found".into())668}669670fn get_token_permission<T: Config>(671 collection_id: CollectionId,672 key: &PropertyKey,673) -> Result<PropertyPermission> {674 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)675 .map_err(|_| Error::Revert("No permissions for collection".into()))?;676 let a = token_property_permissions677 .get(key)678 .map(Clone::clone)679 .ok_or_else(|| {680 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();681 Error::Revert(alloc::format!("No permission for key {}", key))682 })?;683 Ok(a)684}685686687#[solidity_interface(name = ERC721UniqueExtensions)]688impl<T: Config> RefungibleHandle<T>689where690 T::AccountId: From<[u8; 32]>,691{692 693 fn name(&self) -> Result<string> {694 Ok(decode_utf16(self.name.iter().copied())695 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))696 .collect::<string>())697 }698699 700 fn symbol(&self) -> Result<string> {701 Ok(string::from_utf8_lossy(&self.token_prefix).into())702 }703704 705 706 707 708 709 710 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]711 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {712 let caller = T::CrossAccountId::from_eth(caller);713 let to = T::CrossAccountId::from_eth(to);714 let token = token_id.try_into()?;715 let budget = self716 .recorder717 .weight_calls_budget(<StructureWeight<T>>::find_parent());718719 let balance = balance(self, token, &caller)?;720 ensure_single_owner(self, token, balance)?;721722 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)723 .map_err(dispatch_to_evm::<T>)?;724 Ok(())725 }726727 728 729 730 731 732 733 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]734 fn transfer_from_cross(735 &mut self,736 caller: caller,737 from: EthCrossAccount,738 to: EthCrossAccount,739 token_id: uint256,740 ) -> Result<void> {741 let caller = T::CrossAccountId::from_eth(caller);742 let from = from.into_sub_cross_account::<T>()?;743 let to = to.into_sub_cross_account::<T>()?;744 let token_id = token_id.try_into()?;745 let budget = self746 .recorder747 .weight_calls_budget(<StructureWeight<T>>::find_parent());748749 let balance = balance(self, token_id, &from)?;750 ensure_single_owner(self, token_id, balance)?;751752 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)753 .map_err(dispatch_to_evm::<T>)?;754 Ok(())755 }756757 758 759 760 761 762 763 764 #[weight(<SelfWeightOf<T>>::burn_from())]765 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {766 let caller = T::CrossAccountId::from_eth(caller);767 let from = T::CrossAccountId::from_eth(from);768 let token = token_id.try_into()?;769 let budget = self770 .recorder771 .weight_calls_budget(<StructureWeight<T>>::find_parent());772773 let balance = balance(self, token, &from)?;774 ensure_single_owner(self, token, balance)?;775776 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)777 .map_err(dispatch_to_evm::<T>)?;778 Ok(())779 }780781 782 783 784 785 786 787 788 #[weight(<SelfWeightOf<T>>::burn_from())]789 fn burn_from_cross(790 &mut self,791 caller: caller,792 from: EthCrossAccount,793 token_id: uint256,794 ) -> Result<void> {795 let caller = T::CrossAccountId::from_eth(caller);796 let from = from.into_sub_cross_account::<T>()?;797 let token = token_id.try_into()?;798 let budget = self799 .recorder800 .weight_calls_budget(<StructureWeight<T>>::find_parent());801802 let balance = balance(self, token, &from)?;803 ensure_single_owner(self, token, balance)?;804805 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)806 .map_err(dispatch_to_evm::<T>)?;807 Ok(())808 }809810 811 fn next_token_id(&self) -> Result<uint256> {812 self.consume_store_reads(1)?;813 Ok(<TokensMinted<T>>::get(self.id)814 .checked_add(1)815 .ok_or("item id overflow")?816 .into())817 }818819 820 821 822 823 824 #[solidity(hide)]825 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]826 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {827 let caller = T::CrossAccountId::from_eth(caller);828 let to = T::CrossAccountId::from_eth(to);829 let mut expected_index = <TokensMinted<T>>::get(self.id)830 .checked_add(1)831 .ok_or("item id overflow")?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 let total_tokens = token_ids.len();837 for id in token_ids.into_iter() {838 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;839 if id != expected_index {840 return Err("item id should be next".into());841 }842 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;843 }844 let users = [(to.clone(), 1)]845 .into_iter()846 .collect::<BTreeMap<_, _>>()847 .try_into()848 .unwrap();849 let create_item_data = CreateItemData::<T::CrossAccountId> {850 users,851 properties: CollectionPropertiesVec::default(),852 };853 let data = (0..total_tokens)854 .map(|_| create_item_data.clone())855 .collect();856857 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)858 .map_err(dispatch_to_evm::<T>)?;859 Ok(true)860 }861862 863 864 865 866 867 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]868 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]869 fn mint_bulk_with_token_uri(870 &mut self,871 caller: caller,872 to: address,873 tokens: Vec<(uint256, string)>,874 ) -> Result<bool> {875 let key = key::url();876 let caller = T::CrossAccountId::from_eth(caller);877 let to = T::CrossAccountId::from_eth(to);878 let mut expected_index = <TokensMinted<T>>::get(self.id)879 .checked_add(1)880 .ok_or("item id overflow")?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 let mut data = Vec::with_capacity(tokens.len());886 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]887 .into_iter()888 .collect::<BTreeMap<_, _>>()889 .try_into()890 .unwrap();891 for (id, token_uri) in tokens {892 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;893 if id != expected_index {894 return Err("item id should be next".into());895 }896 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;897898 let mut properties = CollectionPropertiesVec::default();899 properties900 .try_push(Property {901 key: key.clone(),902 value: token_uri903 .into_bytes()904 .try_into()905 .map_err(|_| "token uri is too long")?,906 })907 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;908909 let create_item_data = CreateItemData::<T::CrossAccountId> {910 users: users.clone(),911 properties,912 };913 data.push(create_item_data);914 }915916 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)917 .map_err(dispatch_to_evm::<T>)?;918 Ok(true)919 }920921 922 923 924 fn token_contract_address(&self, token: uint256) -> Result<address> {925 Ok(T::EvmTokenAddressMapping::token_to_address(926 self.id,927 token.try_into().map_err(|_| "token id overflow")?,928 ))929 }930}931932#[solidity_interface(933 name = UniqueRefungible,934 is(935 ERC721,936 ERC721Enumerable,937 ERC721UniqueExtensions,938 ERC721UniqueMintable,939 ERC721Burnable,940 ERC721Metadata(if(this.flags.erc721metadata)),941 Collection(via(common_mut returns CollectionHandle<T>)),942 TokenProperties,943 )944)]945impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}946947948generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);949generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);950951impl<T: Config> CommonEvmHandler for RefungibleHandle<T>952where953 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,954{955 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");956 fn call(957 self,958 handle: &mut impl PrecompileHandle,959 ) -> Option<pallet_common::erc::PrecompileResult> {960 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)961 }962}