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 eth::convert_tuple_to_cross_account,34};35use pallet_evm::{account::CrossAccountId, PrecompileHandle};36use pallet_evm_coder_substrate::{call, dispatch_to_evm};37use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};38use sp_core::H160;39use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};40use up_data_structs::{41 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,42 PropertyKeyPermission, PropertyPermission, TokenId,43};4445use crate::{46 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,47 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,48};4950pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);515253#[solidity_interface(name = TokenProperties)]54impl<T: Config> RefungibleHandle<T> {55 56 57 58 59 60 61 fn set_token_property_permission(62 &mut self,63 caller: caller,64 key: string,65 is_mutable: bool,66 collection_admin: bool,67 token_owner: bool,68 ) -> Result<()> {69 let caller = T::CrossAccountId::from_eth(caller);70 <Pallet<T>>::set_token_property_permissions(71 self,72 &caller,73 vec![PropertyKeyPermission {74 key: <Vec<u8>>::from(key)75 .try_into()76 .map_err(|_| "too long key")?,77 permission: PropertyPermission {78 mutable: is_mutable,79 collection_admin,80 token_owner,81 },82 }],83 )84 .map_err(dispatch_to_evm::<T>)85 }8687 88 89 90 91 92 fn set_property(93 &mut self,94 caller: caller,95 token_id: uint256,96 key: string,97 value: bytes,98 ) -> Result<()> {99 let caller = T::CrossAccountId::from_eth(caller);100 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;101 let key = <Vec<u8>>::from(key)102 .try_into()103 .map_err(|_| "key too long")?;104 let value = value.0.try_into().map_err(|_| "value too long")?;105106 let nesting_budget = self107 .recorder108 .weight_calls_budget(<StructureWeight<T>>::find_parent());109110 <Pallet<T>>::set_token_property(111 self,112 &caller,113 TokenId(token_id),114 Property { key, value },115 &nesting_budget,116 )117 .map_err(dispatch_to_evm::<T>)118 }119120 121 122 123 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 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {166 let caller = T::CrossAccountId::from_eth(caller);167 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;168 let key = <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| "key too long")?;171172 let nesting_budget = self173 .recorder174 .weight_calls_budget(<StructureWeight<T>>::find_parent());175176 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)177 .map_err(dispatch_to_evm::<T>)178 }179180 181 182 183 184 185 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {186 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;187 let key = <Vec<u8>>::from(key)188 .try_into()189 .map_err(|_| "key too long")?;190191 let props = <TokenProperties<T>>::get((self.id, token_id));192 let prop = props.get(&key).ok_or("key not found")?;193194 Ok(prop.to_vec().into())195 }196}197198#[derive(ToLog)]199pub enum ERC721Events {200 201 202 203 Transfer {204 #[indexed]205 from: address,206 #[indexed]207 to: address,208 #[indexed]209 token_id: uint256,210 },211 212 Approval {213 #[indexed]214 owner: address,215 #[indexed]216 approved: address,217 #[indexed]218 token_id: uint256,219 },220 221 #[allow(dead_code)]222 ApprovalForAll {223 #[indexed]224 owner: address,225 #[indexed]226 operator: address,227 approved: bool,228 },229}230231#[derive(ToLog)]232pub enum ERC721UniqueMintableEvents {233 234 #[allow(dead_code)]235 MintingFinished {},236}237238#[solidity_interface(name = ERC721Metadata)]239impl<T: Config> RefungibleHandle<T>240where241 T::AccountId: From<[u8; 32]>,242{243 244 245 #[solidity(hide, rename_selector = "name")]246 fn name_proxy(&self) -> Result<string> {247 self.name()248 }249250 251 252 #[solidity(hide, rename_selector = "symbol")]253 fn symbol_proxy(&self) -> Result<string> {254 self.symbol()255 }256257 258 259 260 261 262 263 264 265 266 #[solidity(rename_selector = "tokenURI")]267 fn token_uri(&self, token_id: uint256) -> Result<string> {268 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;269270 match get_token_property(self, token_id_u32, &key::url()).as_deref() {271 Err(_) | Ok("") => (),272 Ok(url) => {273 return Ok(url.into());274 }275 };276277 let base_uri =278 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())279 .map(BoundedVec::into_inner)280 .map(string::from_utf8)281 .transpose()282 .map_err(|e| {283 Error::Revert(alloc::format!(284 "Can not convert value \"baseURI\" to string with error \"{}\"",285 e286 ))287 })?;288289 let base_uri = match base_uri.as_deref() {290 None | Some("") => {291 return Ok("".into());292 }293 Some(base_uri) => base_uri.into(),294 };295296 Ok(297 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {298 Err(_) | Ok("") => base_uri,299 Ok(suffix) => base_uri + suffix,300 },301 )302 }303}304305306307#[solidity_interface(name = ERC721Enumerable)]308impl<T: Config> RefungibleHandle<T> {309 310 311 312 313 fn token_by_index(&self, index: uint256) -> Result<uint256> {314 Ok(index)315 }316317 318 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {319 320 Err("not implemented".into())321 }322323 324 325 326 fn total_supply(&self) -> Result<uint256> {327 self.consume_store_reads(1)?;328 Ok(<Pallet<T>>::total_supply(self).into())329 }330}331332333334#[solidity_interface(name = ERC721, events(ERC721Events))]335impl<T: Config> RefungibleHandle<T> {336 337 338 339 340 341 fn balance_of(&self, owner: address) -> Result<uint256> {342 self.consume_store_reads(1)?;343 let owner = T::CrossAccountId::from_eth(owner);344 let balance = <AccountBalance<T>>::get((self.id, owner));345 Ok(balance.into())346 }347348 349 350 351 352 353 354 355 fn owner_of(&self, token_id: uint256) -> Result<address> {356 self.consume_store_reads(2)?;357 let token = token_id.try_into()?;358 let owner = <Pallet<T>>::token_owner(self.id, token);359 Ok(owner360 .map(|address| *address.as_eth())361 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))362 }363364 365 fn safe_transfer_from_with_data(366 &mut self,367 _from: address,368 _to: address,369 _token_id: uint256,370 _data: bytes,371 ) -> Result<void> {372 373 Err("not implemented".into())374 }375376 377 fn safe_transfer_from(378 &mut self,379 _from: address,380 _to: address,381 _token_id: uint256,382 ) -> Result<void> {383 384 Err("not implemented".into())385 }386387 388 389 390 391 392 393 394 395 396 397 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]398 fn transfer_from(399 &mut self,400 caller: caller,401 from: address,402 to: address,403 token_id: uint256,404 ) -> Result<void> {405 let caller = T::CrossAccountId::from_eth(caller);406 let from = T::CrossAccountId::from_eth(from);407 let to = T::CrossAccountId::from_eth(to);408 let token = token_id.try_into()?;409 let budget = self410 .recorder411 .weight_calls_budget(<StructureWeight<T>>::find_parent());412413 let balance = balance(&self, token, &from)?;414 ensure_single_owner(&self, token, balance)?;415416 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)417 .map_err(dispatch_to_evm::<T>)?;418419 Ok(())420 }421422 423 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {424 Err("not implemented".into())425 }426427 428 fn set_approval_for_all(429 &mut self,430 _caller: caller,431 _operator: address,432 _approved: bool,433 ) -> Result<void> {434 435 Err("not implemented".into())436 }437438 439 fn get_approved(&self, _token_id: uint256) -> Result<address> {440 441 Err("not implemented".into())442 }443444 445 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {446 447 Err("not implemented".into())448 }449}450451452pub fn balance<T: Config>(453 collection: &RefungibleHandle<T>,454 token: TokenId,455 owner: &T::CrossAccountId,456) -> Result<u128> {457 collection.consume_store_reads(1)?;458 let balance = <Balance<T>>::get((collection.id, token, &owner));459 Ok(balance)460}461462463pub fn ensure_single_owner<T: Config>(464 collection: &RefungibleHandle<T>,465 token: TokenId,466 owner_balance: u128,467) -> Result<()> {468 collection.consume_store_reads(1)?;469 let total_supply = <TotalSupply<T>>::get((collection.id, token));470 if total_supply != owner_balance {471 return Err("token has multiple owners".into());472 }473 Ok(())474}475476477#[solidity_interface(name = ERC721Burnable)]478impl<T: Config> RefungibleHandle<T> {479 480 481 482 483 #[weight(<SelfWeightOf<T>>::burn_item_fully())]484 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {485 let caller = T::CrossAccountId::from_eth(caller);486 let token = token_id.try_into()?;487488 let balance = balance(&self, token, &caller)?;489 ensure_single_owner(&self, token, balance)?;490491 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;492 Ok(())493 }494}495496497#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]498impl<T: Config> RefungibleHandle<T> {499 fn minting_finished(&self) -> Result<bool> {500 Ok(false)501 }502503 504 505 506 #[weight(<SelfWeightOf<T>>::create_item())]507 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {508 let token_id: uint256 = <TokensMinted<T>>::get(self.id)509 .checked_add(1)510 .ok_or("item id overflow")?511 .into();512 self.mint_check_id(caller, to, token_id)?;513 Ok(token_id)514 }515516 517 518 519 520 521 #[solidity(hide, rename_selector = "mint")]522 #[weight(<SelfWeightOf<T>>::create_item())]523 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {524 let caller = T::CrossAccountId::from_eth(caller);525 let to = T::CrossAccountId::from_eth(to);526 let token_id: u32 = token_id.try_into()?;527 let budget = self528 .recorder529 .weight_calls_budget(<StructureWeight<T>>::find_parent());530531 if <TokensMinted<T>>::get(self.id)532 .checked_add(1)533 .ok_or("item id overflow")?534 != token_id535 {536 return Err("item id should be next".into());537 }538539 let users = [(to.clone(), 1)]540 .into_iter()541 .collect::<BTreeMap<_, _>>()542 .try_into()543 .unwrap();544 <Pallet<T>>::create_item(545 self,546 &caller,547 CreateItemData::<T::CrossAccountId> {548 users,549 properties: CollectionPropertiesVec::default(),550 },551 &budget,552 )553 .map_err(dispatch_to_evm::<T>)?;554555 Ok(true)556 }557558 559 560 561 562 #[solidity(rename_selector = "mintWithTokenURI")]563 #[weight(<SelfWeightOf<T>>::create_item())]564 fn mint_with_token_uri(565 &mut self,566 caller: caller,567 to: address,568 token_uri: string,569 ) -> Result<uint256> {570 let token_id: uint256 = <TokensMinted<T>>::get(self.id)571 .checked_add(1)572 .ok_or("item id overflow")?573 .into();574 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;575 Ok(token_id)576 }577578 579 580 581 582 583 584 #[solidity(hide, rename_selector = "mintWithTokenURI")]585 #[weight(<SelfWeightOf<T>>::create_item())]586 fn mint_with_token_uri_check_id(587 &mut self,588 caller: caller,589 to: address,590 token_id: uint256,591 token_uri: string,592 ) -> Result<bool> {593 let key = key::url();594 let permission = get_token_permission::<T>(self.id, &key)?;595 if !permission.collection_admin {596 return Err("Operation is not allowed".into());597 }598599 let caller = T::CrossAccountId::from_eth(caller);600 let to = T::CrossAccountId::from_eth(to);601 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;602 let budget = self603 .recorder604 .weight_calls_budget(<StructureWeight<T>>::find_parent());605606 if <TokensMinted<T>>::get(self.id)607 .checked_add(1)608 .ok_or("item id overflow")?609 != token_id610 {611 return Err("item id should be next".into());612 }613614 let mut properties = CollectionPropertiesVec::default();615 properties616 .try_push(Property {617 key,618 value: token_uri619 .into_bytes()620 .try_into()621 .map_err(|_| "token uri is too long")?,622 })623 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;624625 let users = [(to.clone(), 1)]626 .into_iter()627 .collect::<BTreeMap<_, _>>()628 .try_into()629 .unwrap();630 <Pallet<T>>::create_item(631 self,632 &caller,633 CreateItemData::<T::CrossAccountId> { users, properties },634 &budget,635 )636 .map_err(dispatch_to_evm::<T>)?;637 Ok(true)638 }639640 641 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {642 Err("not implementable".into())643 }644}645646fn get_token_property<T: Config>(647 collection: &CollectionHandle<T>,648 token_id: u32,649 key: &up_data_structs::PropertyKey,650) -> Result<string> {651 collection.consume_store_reads(1)?;652 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))653 .map_err(|_| Error::Revert("Token properties not found".into()))?;654 if let Some(property) = properties.get(key) {655 return Ok(string::from_utf8_lossy(property).into());656 }657658 Err("Property tokenURI not found".into())659}660661fn get_token_permission<T: Config>(662 collection_id: CollectionId,663 key: &PropertyKey,664) -> Result<PropertyPermission> {665 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)666 .map_err(|_| Error::Revert("No permissions for collection".into()))?;667 let a = token_property_permissions668 .get(key)669 .map(Clone::clone)670 .ok_or_else(|| {671 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();672 Error::Revert(alloc::format!("No permission for key {}", key))673 })?;674 Ok(a)675}676677678#[solidity_interface(name = ERC721UniqueExtensions)]679impl<T: Config> RefungibleHandle<T>680where681 T::AccountId: From<[u8; 32]>,682{683 684 fn name(&self) -> Result<string> {685 Ok(decode_utf16(self.name.iter().copied())686 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))687 .collect::<string>())688 }689690 691 fn symbol(&self) -> Result<string> {692 Ok(string::from_utf8_lossy(&self.token_prefix).into())693 }694695 696 697 698 699 700 701 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]702 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {703 let caller = T::CrossAccountId::from_eth(caller);704 let to = T::CrossAccountId::from_eth(to);705 let token = token_id.try_into()?;706 let budget = self707 .recorder708 .weight_calls_budget(<StructureWeight<T>>::find_parent());709710 let balance = balance(self, token, &caller)?;711 ensure_single_owner(self, token, balance)?;712713 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)714 .map_err(dispatch_to_evm::<T>)?;715 Ok(())716 }717718 719 720 721 722 723 724 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]725 fn transfer_from_cross(726 &mut self,727 caller: caller,728 from: (address, uint256),729 to: (address, uint256),730 token_id: uint256,731 ) -> Result<void> {732 let caller = T::CrossAccountId::from_eth(caller);733 let from = convert_tuple_to_cross_account::<T>(from)?;734 let to = convert_tuple_to_cross_account::<T>(to)?;735 let token_id = token_id.try_into()?;736 let budget = self737 .recorder738 .weight_calls_budget(<StructureWeight<T>>::find_parent());739740 let balance = balance(self, token_id, &from)?;741 ensure_single_owner(self, token_id, balance)?;742743 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)744 .map_err(dispatch_to_evm::<T>)?;745 Ok(())746 }747748 749 750 751 752 753 754 755 #[weight(<SelfWeightOf<T>>::burn_from())]756 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {757 let caller = T::CrossAccountId::from_eth(caller);758 let from = T::CrossAccountId::from_eth(from);759 let token = token_id.try_into()?;760 let budget = self761 .recorder762 .weight_calls_budget(<StructureWeight<T>>::find_parent());763764 let balance = balance(self, token, &from)?;765 ensure_single_owner(self, token, balance)?;766767 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)768 .map_err(dispatch_to_evm::<T>)?;769 Ok(())770 }771772 773 774 775 776 777 778 779 #[weight(<SelfWeightOf<T>>::burn_from())]780 fn burn_from_cross(781 &mut self,782 caller: caller,783 from: (address, uint256),784 token_id: uint256,785 ) -> Result<void> {786 let caller = T::CrossAccountId::from_eth(caller);787 let from = convert_tuple_to_cross_account::<T>(from)?;788 let token = token_id.try_into()?;789 let budget = self790 .recorder791 .weight_calls_budget(<StructureWeight<T>>::find_parent());792793 let balance = balance(self, token, &from)?;794 ensure_single_owner(self, token, balance)?;795796 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)797 .map_err(dispatch_to_evm::<T>)?;798 Ok(())799 }800801 802 fn next_token_id(&self) -> Result<uint256> {803 self.consume_store_reads(1)?;804 Ok(<TokensMinted<T>>::get(self.id)805 .checked_add(1)806 .ok_or("item id overflow")?807 .into())808 }809810 811 812 813 814 815 #[solidity(hide)]816 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]817 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {818 let caller = T::CrossAccountId::from_eth(caller);819 let to = T::CrossAccountId::from_eth(to);820 let mut expected_index = <TokensMinted<T>>::get(self.id)821 .checked_add(1)822 .ok_or("item id overflow")?;823 let budget = self824 .recorder825 .weight_calls_budget(<StructureWeight<T>>::find_parent());826827 let total_tokens = token_ids.len();828 for id in token_ids.into_iter() {829 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;830 if id != expected_index {831 return Err("item id should be next".into());832 }833 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;834 }835 let users = [(to.clone(), 1)]836 .into_iter()837 .collect::<BTreeMap<_, _>>()838 .try_into()839 .unwrap();840 let create_item_data = CreateItemData::<T::CrossAccountId> {841 users,842 properties: CollectionPropertiesVec::default(),843 };844 let data = (0..total_tokens)845 .map(|_| create_item_data.clone())846 .collect();847848 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)849 .map_err(dispatch_to_evm::<T>)?;850 Ok(true)851 }852853 854 855 856 857 858 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]859 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]860 fn mint_bulk_with_token_uri(861 &mut self,862 caller: caller,863 to: address,864 tokens: Vec<(uint256, string)>,865 ) -> Result<bool> {866 let key = key::url();867 let caller = T::CrossAccountId::from_eth(caller);868 let to = T::CrossAccountId::from_eth(to);869 let mut expected_index = <TokensMinted<T>>::get(self.id)870 .checked_add(1)871 .ok_or("item id overflow")?;872 let budget = self873 .recorder874 .weight_calls_budget(<StructureWeight<T>>::find_parent());875876 let mut data = Vec::with_capacity(tokens.len());877 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]878 .into_iter()879 .collect::<BTreeMap<_, _>>()880 .try_into()881 .unwrap();882 for (id, token_uri) in tokens {883 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;884 if id != expected_index {885 return Err("item id should be next".into());886 }887 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;888889 let mut properties = CollectionPropertiesVec::default();890 properties891 .try_push(Property {892 key: key.clone(),893 value: token_uri894 .into_bytes()895 .try_into()896 .map_err(|_| "token uri is too long")?,897 })898 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;899900 let create_item_data = CreateItemData::<T::CrossAccountId> {901 users: users.clone(),902 properties,903 };904 data.push(create_item_data);905 }906907 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)908 .map_err(dispatch_to_evm::<T>)?;909 Ok(true)910 }911912 913 914 915 fn token_contract_address(&self, token: uint256) -> Result<address> {916 Ok(T::EvmTokenAddressMapping::token_to_address(917 self.id,918 token.try_into().map_err(|_| "token id overflow")?,919 ))920 }921}922923#[solidity_interface(924 name = UniqueRefungible,925 is(926 ERC721,927 ERC721Enumerable,928 ERC721UniqueExtensions,929 ERC721UniqueMintable,930 ERC721Burnable,931 ERC721Metadata(if(this.flags.erc721metadata)),932 Collection(via(common_mut returns CollectionHandle<T>)),933 TokenProperties,934 )935)]936impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}937938939generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);940generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);941942impl<T: Config> CommonEvmHandler for RefungibleHandle<T>943where944 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,945{946 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");947 fn call(948 self,949 handle: &mut impl PrecompileHandle,950 ) -> Option<pallet_common::erc::PrecompileResult> {951 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)952 }953}