12345678910111213141516171819202122extern 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};34use pallet_evm::{account::CrossAccountId, PrecompileHandle};35use pallet_evm_coder_substrate::{call, dispatch_to_evm};36use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};37use sp_core::H160;38use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};39use up_data_structs::{40 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,41 PropertyKeyPermission, PropertyPermission, TokenId,42};4344use crate::{45 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,46 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,47};4849pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);505152#[solidity_interface(name = TokenProperties)]53impl<T: Config> RefungibleHandle<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_token_property_permissions(70 self,71 &caller,72 vec![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 fn set_properties(124 &mut self,125 caller: caller,126 token_id: uint256,127 properties: Vec<(string, bytes)>,128 ) -> Result<()> {129 let caller = T::CrossAccountId::from_eth(caller);130 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;131132 let nesting_budget = self133 .recorder134 .weight_calls_budget(<StructureWeight<T>>::find_parent());135136 let properties = properties137 .into_iter()138 .map(|(key, value)| {139 let key = <Vec<u8>>::from(key)140 .try_into()141 .map_err(|_| "key too large")?;142143 let value = value.0.try_into().map_err(|_| "value too large")?;144145 Ok(Property { key, value })146 })147 .collect::<Result<Vec<_>>>()?;148149 <Pallet<T>>::set_token_properties(150 self,151 &caller,152 TokenId(token_id),153 properties.into_iter(),154 <Pallet<T>>::token_exists(&self, TokenId(token_id)),155 &nesting_budget,156 )157 .map_err(dispatch_to_evm::<T>)158 }159160 161 162 163 164 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {165 let caller = T::CrossAccountId::from_eth(caller);166 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too long")?;170171 let nesting_budget = self172 .recorder173 .weight_calls_budget(<StructureWeight<T>>::find_parent());174175 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)176 .map_err(dispatch_to_evm::<T>)177 }178179 180 181 182 183 184 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {185 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;186 let key = <Vec<u8>>::from(key)187 .try_into()188 .map_err(|_| "key too long")?;189190 let props = <TokenProperties<T>>::get((self.id, token_id));191 let prop = props.get(&key).ok_or("key not found")?;192193 Ok(prop.to_vec().into())194 }195}196197#[derive(ToLog)]198pub enum ERC721Events {199 200 201 202 Transfer {203 #[indexed]204 from: address,205 #[indexed]206 to: address,207 #[indexed]208 token_id: uint256,209 },210 211 Approval {212 #[indexed]213 owner: address,214 #[indexed]215 approved: address,216 #[indexed]217 token_id: uint256,218 },219 220 #[allow(dead_code)]221 ApprovalForAll {222 #[indexed]223 owner: address,224 #[indexed]225 operator: address,226 approved: bool,227 },228}229230#[derive(ToLog)]231pub enum ERC721UniqueMintableEvents {232 233 #[allow(dead_code)]234 MintingFinished {},235}236237#[solidity_interface(name = ERC721Metadata)]238impl<T: Config> RefungibleHandle<T>239where240 T::AccountId: From<[u8; 32]>,241{242 243 244 #[solidity(hide, rename_selector = "name")]245 fn name_proxy(&self) -> Result<string> {246 self.name()247 }248249 250 251 #[solidity(hide, rename_selector = "symbol")]252 fn symbol_proxy(&self) -> Result<string> {253 self.symbol()254 }255256 257 258 259 260 261 262 263 264 265 #[solidity(rename_selector = "tokenURI")]266 fn token_uri(&self, token_id: uint256) -> Result<string> {267 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;268269 match get_token_property(self, token_id_u32, &key::url()).as_deref() {270 Err(_) | Ok("") => (),271 Ok(url) => {272 return Ok(url.into());273 }274 };275276 let base_uri =277 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())278 .map(BoundedVec::into_inner)279 .map(string::from_utf8)280 .transpose()281 .map_err(|e| {282 Error::Revert(alloc::format!(283 "Can not convert value \"baseURI\" to string with error \"{}\"",284 e285 ))286 })?;287288 let base_uri = match base_uri.as_deref() {289 None | Some("") => {290 return Ok("".into());291 }292 Some(base_uri) => base_uri.into(),293 };294295 Ok(296 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {297 Err(_) | Ok("") => base_uri,298 Ok(suffix) => base_uri + suffix,299 },300 )301 }302}303304305306#[solidity_interface(name = ERC721Enumerable)]307impl<T: Config> RefungibleHandle<T> {308 309 310 311 312 fn token_by_index(&self, index: uint256) -> Result<uint256> {313 Ok(index)314 }315316 317 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {318 319 Err("not implemented".into())320 }321322 323 324 325 fn total_supply(&self) -> Result<uint256> {326 self.consume_store_reads(1)?;327 Ok(<Pallet<T>>::total_supply(self).into())328 }329}330331332333#[solidity_interface(name = ERC721, events(ERC721Events))]334impl<T: Config> RefungibleHandle<T> {335 336 337 338 339 340 fn balance_of(&self, owner: address) -> Result<uint256> {341 self.consume_store_reads(1)?;342 let owner = T::CrossAccountId::from_eth(owner);343 let balance = <AccountBalance<T>>::get((self.id, owner));344 Ok(balance.into())345 }346347 348 349 350 351 352 353 354 fn owner_of(&self, token_id: uint256) -> Result<address> {355 self.consume_store_reads(2)?;356 let token = token_id.try_into()?;357 let owner = <Pallet<T>>::token_owner(self.id, token);358 Ok(owner359 .map(|address| *address.as_eth())360 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))361 }362363 364 fn safe_transfer_from_with_data(365 &mut self,366 _from: address,367 _to: address,368 _token_id: uint256,369 _data: bytes,370 ) -> Result<void> {371 372 Err("not implemented".into())373 }374375 376 fn safe_transfer_from(377 &mut self,378 _from: address,379 _to: address,380 _token_id: uint256,381 ) -> Result<void> {382 383 Err("not implemented".into())384 }385386 387 388 389 390 391 392 393 394 395 396 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]397 fn transfer_from(398 &mut self,399 caller: caller,400 from: address,401 to: address,402 token_id: uint256,403 ) -> Result<void> {404 let caller = T::CrossAccountId::from_eth(caller);405 let from = T::CrossAccountId::from_eth(from);406 let to = T::CrossAccountId::from_eth(to);407 let token = token_id.try_into()?;408 let budget = self409 .recorder410 .weight_calls_budget(<StructureWeight<T>>::find_parent());411412 let balance = balance(&self, token, &from)?;413 ensure_single_owner(&self, token, balance)?;414415 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)416 .map_err(dispatch_to_evm::<T>)?;417418 Ok(())419 }420421 422 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {423 Err("not implemented".into())424 }425426 427 fn set_approval_for_all(428 &mut self,429 _caller: caller,430 _operator: address,431 _approved: bool,432 ) -> Result<void> {433 434 Err("not implemented".into())435 }436437 438 fn get_approved(&self, _token_id: uint256) -> Result<address> {439 440 Err("not implemented".into())441 }442443 444 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {445 446 Err("not implemented".into())447 }448}449450451pub fn balance<T: Config>(452 collection: &RefungibleHandle<T>,453 token: TokenId,454 owner: &T::CrossAccountId,455) -> Result<u128> {456 collection.consume_store_reads(1)?;457 let balance = <Balance<T>>::get((collection.id, token, &owner));458 Ok(balance)459}460461462pub fn ensure_single_owner<T: Config>(463 collection: &RefungibleHandle<T>,464 token: TokenId,465 owner_balance: u128,466) -> Result<()> {467 collection.consume_store_reads(1)?;468 let total_supply = <TotalSupply<T>>::get((collection.id, token));469 if total_supply != owner_balance {470 return Err("token has multiple owners".into());471 }472 Ok(())473}474475476#[solidity_interface(name = ERC721Burnable)]477impl<T: Config> RefungibleHandle<T> {478 479 480 481 482 #[weight(<SelfWeightOf<T>>::burn_item_fully())]483 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {484 let caller = T::CrossAccountId::from_eth(caller);485 let token = token_id.try_into()?;486487 let balance = balance(&self, token, &caller)?;488 ensure_single_owner(&self, token, balance)?;489490 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;491 Ok(())492 }493}494495496#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]497impl<T: Config> RefungibleHandle<T> {498 fn minting_finished(&self) -> Result<bool> {499 Ok(false)500 }501502 503 504 505 #[weight(<SelfWeightOf<T>>::create_item())]506 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {507 let token_id: uint256 = <TokensMinted<T>>::get(self.id)508 .checked_add(1)509 .ok_or("item id overflow")?510 .into();511 self.mint_check_id(caller, to, token_id)?;512 Ok(token_id)513 }514515 516 517 518 519 520 #[solidity(hide, rename_selector = "mint")]521 #[weight(<SelfWeightOf<T>>::create_item())]522 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {523 let caller = T::CrossAccountId::from_eth(caller);524 let to = T::CrossAccountId::from_eth(to);525 let token_id: u32 = token_id.try_into()?;526 let budget = self527 .recorder528 .weight_calls_budget(<StructureWeight<T>>::find_parent());529530 if <TokensMinted<T>>::get(self.id)531 .checked_add(1)532 .ok_or("item id overflow")?533 != token_id534 {535 return Err("item id should be next".into());536 }537538 let users = [(to.clone(), 1)]539 .into_iter()540 .collect::<BTreeMap<_, _>>()541 .try_into()542 .unwrap();543 <Pallet<T>>::create_item(544 self,545 &caller,546 CreateItemData::<T::CrossAccountId> {547 users,548 properties: CollectionPropertiesVec::default(),549 },550 &budget,551 )552 .map_err(dispatch_to_evm::<T>)?;553554 Ok(true)555 }556557 558 559 560 561 #[solidity(rename_selector = "mintWithTokenURI")]562 #[weight(<SelfWeightOf<T>>::create_item())]563 fn mint_with_token_uri(564 &mut self,565 caller: caller,566 to: address,567 token_uri: string,568 ) -> Result<uint256> {569 let token_id: uint256 = <TokensMinted<T>>::get(self.id)570 .checked_add(1)571 .ok_or("item id overflow")?572 .into();573 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;574 Ok(token_id)575 }576577 578 579 580 581 582 583 #[solidity(hide, rename_selector = "mintWithTokenURI")]584 #[weight(<SelfWeightOf<T>>::create_item())]585 fn mint_with_token_uri_check_id(586 &mut self,587 caller: caller,588 to: address,589 token_id: uint256,590 token_uri: string,591 ) -> Result<bool> {592 let key = key::url();593 let permission = get_token_permission::<T>(self.id, &key)?;594 if !permission.collection_admin {595 return Err("Operation is not allowed".into());596 }597598 let caller = T::CrossAccountId::from_eth(caller);599 let to = T::CrossAccountId::from_eth(to);600 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;601 let budget = self602 .recorder603 .weight_calls_budget(<StructureWeight<T>>::find_parent());604605 if <TokensMinted<T>>::get(self.id)606 .checked_add(1)607 .ok_or("item id overflow")?608 != token_id609 {610 return Err("item id should be next".into());611 }612613 let mut properties = CollectionPropertiesVec::default();614 properties615 .try_push(Property {616 key,617 value: token_uri618 .into_bytes()619 .try_into()620 .map_err(|_| "token uri is too long")?,621 })622 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;623624 let users = [(to.clone(), 1)]625 .into_iter()626 .collect::<BTreeMap<_, _>>()627 .try_into()628 .unwrap();629 <Pallet<T>>::create_item(630 self,631 &caller,632 CreateItemData::<T::CrossAccountId> { users, properties },633 &budget,634 )635 .map_err(dispatch_to_evm::<T>)?;636 Ok(true)637 }638639 640 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {641 Err("not implementable".into())642 }643}644645fn get_token_property<T: Config>(646 collection: &CollectionHandle<T>,647 token_id: u32,648 key: &up_data_structs::PropertyKey,649) -> Result<string> {650 collection.consume_store_reads(1)?;651 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))652 .map_err(|_| Error::Revert("Token properties not found".into()))?;653 if let Some(property) = properties.get(key) {654 return Ok(string::from_utf8_lossy(property).into());655 }656657 Err("Property tokenURI not found".into())658}659660fn get_token_permission<T: Config>(661 collection_id: CollectionId,662 key: &PropertyKey,663) -> Result<PropertyPermission> {664 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)665 .map_err(|_| Error::Revert("No permissions for collection".into()))?;666 let a = token_property_permissions667 .get(key)668 .map(Clone::clone)669 .ok_or_else(|| {670 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();671 Error::Revert(alloc::format!("No permission for key {}", key))672 })?;673 Ok(a)674}675676677#[solidity_interface(name = ERC721UniqueExtensions)]678impl<T: Config> RefungibleHandle<T>679where680 T::AccountId: From<[u8; 32]>,681{682 683 fn name(&self) -> Result<string> {684 Ok(decode_utf16(self.name.iter().copied())685 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))686 .collect::<string>())687 }688689 690 fn symbol(&self) -> Result<string> {691 Ok(string::from_utf8_lossy(&self.token_prefix).into())692 }693694 695 696 697 698 699 700 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]701 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {702 let caller = T::CrossAccountId::from_eth(caller);703 let to = T::CrossAccountId::from_eth(to);704 let token = token_id.try_into()?;705 let budget = self706 .recorder707 .weight_calls_budget(<StructureWeight<T>>::find_parent());708709 let balance = balance(self, token, &caller)?;710 ensure_single_owner(self, token, balance)?;711712 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)713 .map_err(dispatch_to_evm::<T>)?;714 Ok(())715 }716717 718 719 720 721 722 723 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]724 fn transfer_from_cross(725 &mut self,726 caller: caller,727 from: EthCrossAccount,728 to: EthCrossAccount,729 token_id: uint256,730 ) -> Result<void> {731 let caller = T::CrossAccountId::from_eth(caller);732 let from = from.into_sub_cross_account::<T>()?;733 let to = to.into_sub_cross_account::<T>()?;734 let token_id = token_id.try_into()?;735 let budget = self736 .recorder737 .weight_calls_budget(<StructureWeight<T>>::find_parent());738739 let balance = balance(self, token_id, &from)?;740 ensure_single_owner(self, token_id, balance)?;741742 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)743 .map_err(dispatch_to_evm::<T>)?;744 Ok(())745 }746747 748 749 750 751 752 753 754 #[weight(<SelfWeightOf<T>>::burn_from())]755 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {756 let caller = T::CrossAccountId::from_eth(caller);757 let from = T::CrossAccountId::from_eth(from);758 let token = token_id.try_into()?;759 let budget = self760 .recorder761 .weight_calls_budget(<StructureWeight<T>>::find_parent());762763 let balance = balance(self, token, &from)?;764 ensure_single_owner(self, token, balance)?;765766 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)767 .map_err(dispatch_to_evm::<T>)?;768 Ok(())769 }770771 772 773 774 775 776 777 778 #[weight(<SelfWeightOf<T>>::burn_from())]779 fn burn_from_cross(780 &mut self,781 caller: caller,782 from: EthCrossAccount,783 token_id: uint256,784 ) -> Result<void> {785 let caller = T::CrossAccountId::from_eth(caller);786 let from = from.into_sub_cross_account::<T>()?;787 let token = token_id.try_into()?;788 let budget = self789 .recorder790 .weight_calls_budget(<StructureWeight<T>>::find_parent());791792 let balance = balance(self, token, &from)?;793 ensure_single_owner(self, token, balance)?;794795 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)796 .map_err(dispatch_to_evm::<T>)?;797 Ok(())798 }799800 801 fn next_token_id(&self) -> Result<uint256> {802 self.consume_store_reads(1)?;803 Ok(<TokensMinted<T>>::get(self.id)804 .checked_add(1)805 .ok_or("item id overflow")?806 .into())807 }808809 810 811 812 813 814 #[solidity(hide)]815 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]816 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {817 let caller = T::CrossAccountId::from_eth(caller);818 let to = T::CrossAccountId::from_eth(to);819 let mut expected_index = <TokensMinted<T>>::get(self.id)820 .checked_add(1)821 .ok_or("item id overflow")?;822 let budget = self823 .recorder824 .weight_calls_budget(<StructureWeight<T>>::find_parent());825826 let total_tokens = token_ids.len();827 for id in token_ids.into_iter() {828 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;829 if id != expected_index {830 return Err("item id should be next".into());831 }832 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;833 }834 let users = [(to.clone(), 1)]835 .into_iter()836 .collect::<BTreeMap<_, _>>()837 .try_into()838 .unwrap();839 let create_item_data = CreateItemData::<T::CrossAccountId> {840 users,841 properties: CollectionPropertiesVec::default(),842 };843 let data = (0..total_tokens)844 .map(|_| create_item_data.clone())845 .collect();846847 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)848 .map_err(dispatch_to_evm::<T>)?;849 Ok(true)850 }851852 853 854 855 856 857 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]858 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]859 fn mint_bulk_with_token_uri(860 &mut self,861 caller: caller,862 to: address,863 tokens: Vec<(uint256, string)>,864 ) -> Result<bool> {865 let key = key::url();866 let caller = T::CrossAccountId::from_eth(caller);867 let to = T::CrossAccountId::from_eth(to);868 let mut expected_index = <TokensMinted<T>>::get(self.id)869 .checked_add(1)870 .ok_or("item id overflow")?;871 let budget = self872 .recorder873 .weight_calls_budget(<StructureWeight<T>>::find_parent());874875 let mut data = Vec::with_capacity(tokens.len());876 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]877 .into_iter()878 .collect::<BTreeMap<_, _>>()879 .try_into()880 .unwrap();881 for (id, token_uri) in tokens {882 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;883 if id != expected_index {884 return Err("item id should be next".into());885 }886 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;887888 let mut properties = CollectionPropertiesVec::default();889 properties890 .try_push(Property {891 key: key.clone(),892 value: token_uri893 .into_bytes()894 .try_into()895 .map_err(|_| "token uri is too long")?,896 })897 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;898899 let create_item_data = CreateItemData::<T::CrossAccountId> {900 users: users.clone(),901 properties,902 };903 data.push(create_item_data);904 }905906 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)907 .map_err(dispatch_to_evm::<T>)?;908 Ok(true)909 }910911 912 913 914 fn token_contract_address(&self, token: uint256) -> Result<address> {915 Ok(T::EvmTokenAddressMapping::token_to_address(916 self.id,917 token.try_into().map_err(|_| "token id overflow")?,918 ))919 }920}921922#[solidity_interface(923 name = UniqueRefungible,924 is(925 ERC721,926 ERC721Enumerable,927 ERC721UniqueExtensions,928 ERC721UniqueMintable,929 ERC721Burnable,930 ERC721Metadata(if(this.flags.erc721metadata)),931 Collection(via(common_mut returns CollectionHandle<T>)),932 TokenProperties,933 )934)]935impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}936937938generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);939generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);940941impl<T: Config> CommonEvmHandler for RefungibleHandle<T>942where943 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,944{945 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");946 fn call(947 self,948 handle: &mut impl PrecompileHandle,949 ) -> Option<pallet_common::erc::PrecompileResult> {950 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)951 }952}