12345678910111213141516171819202122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{29 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,30 weight,31};32use frame_support::{BoundedBTreeMap, BoundedVec};33use pallet_common::{34 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,35 erc::{CommonEvmHandler, CollectionCall, static_property::key},36 Error as CommonError,37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::{H160, Get};42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,45 PropertyKeyPermission, PropertyPermission, TokenId,46};4748use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);545556#[solidity_interface(name = TokenProperties)]57impl<T: Config> RefungibleHandle<T> {58 59 60 61 62 63 64 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]65 #[solidity(hide)]66 fn set_token_property_permission(67 &mut self,68 caller: caller,69 key: string,70 is_mutable: bool,71 collection_admin: bool,72 token_owner: bool,73 ) -> Result<()> {74 let caller = T::CrossAccountId::from_eth(caller);75 <Pallet<T>>::set_token_property_permissions(76 self,77 &caller,78 vec![PropertyKeyPermission {79 key: <Vec<u8>>::from(key)80 .try_into()81 .map_err(|_| "too long key")?,82 permission: PropertyPermission {83 mutable: is_mutable,84 collection_admin,85 token_owner,86 },87 }],88 )89 .map_err(dispatch_to_evm::<T>)90 }9192 93 94 95 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]96 fn set_token_property_permissions(97 &mut self,98 caller: caller,99 permissions: Vec<pallet_common::eth::TokenPropertyPermission>,100 ) -> Result<()> {101 let caller = T::CrossAccountId::from_eth(caller);102 let perms = pallet_common::eth::TokenPropertyPermission::into_property_key_permissions(103 permissions,104 )?;105106 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)107 .map_err(dispatch_to_evm::<T>)108 }109110 111 fn token_property_permissions(112 &self,113 ) -> Result<Vec<pallet_common::eth::TokenPropertyPermission>> {114 let perms = <Pallet<T>>::token_property_permission(self.id);115 Ok(perms116 .into_iter()117 .map(pallet_common::eth::TokenPropertyPermission::from)118 .collect())119 }120121 122 123 124 125 126 #[solidity(hide)]127 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]128 fn set_property(129 &mut self,130 caller: caller,131 token_id: uint256,132 key: string,133 value: bytes,134 ) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;137 let key = <Vec<u8>>::from(key)138 .try_into()139 .map_err(|_| "key too long")?;140 let value = value.0.try_into().map_err(|_| "value too long")?;141142 let nesting_budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145146 <Pallet<T>>::set_token_property(147 self,148 &caller,149 TokenId(token_id),150 Property { key, value },151 &nesting_budget,152 )153 .map_err(dispatch_to_evm::<T>)154 }155156 157 158 159 160 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]161 fn set_properties(162 &mut self,163 caller: caller,164 token_id: uint256,165 properties: Vec<pallet_common::eth::Property>,166 ) -> Result<()> {167 let caller = T::CrossAccountId::from_eth(caller);168 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169170 let nesting_budget = self171 .recorder172 .weight_calls_budget(<StructureWeight<T>>::find_parent());173174 let properties = properties175 .into_iter()176 .map(pallet_common::eth::Property::try_into)177 .collect::<Result<Vec<_>>>()?;178179 <Pallet<T>>::set_token_properties(180 self,181 &caller,182 TokenId(token_id),183 properties.into_iter(),184 false,185 &nesting_budget,186 )187 .map_err(dispatch_to_evm::<T>)188 }189190 191 192 193 194 #[solidity(hide)]195 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]196 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {197 let caller = T::CrossAccountId::from_eth(caller);198 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;199 let key = <Vec<u8>>::from(key)200 .try_into()201 .map_err(|_| "key too long")?;202203 let nesting_budget = self204 .recorder205 .weight_calls_budget(<StructureWeight<T>>::find_parent());206207 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)208 .map_err(dispatch_to_evm::<T>)209 }210211 212 213 214 215 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]216 fn delete_properties(217 &mut self,218 token_id: uint256,219 caller: caller,220 keys: Vec<string>,221 ) -> Result<()> {222 let caller = T::CrossAccountId::from_eth(caller);223 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;224 let keys = keys225 .into_iter()226 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))227 .collect::<Result<Vec<_>>>()?;228229 let nesting_budget = self230 .recorder231 .weight_calls_budget(<StructureWeight<T>>::find_parent());232233 <Pallet<T>>::delete_token_properties(234 self,235 &caller,236 TokenId(token_id),237 keys.into_iter(),238 &nesting_budget,239 )240 .map_err(dispatch_to_evm::<T>)241 }242243 244 245 246 247 248 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {249 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;250 let key = <Vec<u8>>::from(key)251 .try_into()252 .map_err(|_| "key too long")?;253254 let props = <TokenProperties<T>>::get((self.id, token_id));255 let prop = props.get(&key).ok_or("key not found")?;256257 Ok(prop.to_vec().into())258 }259}260261#[derive(ToLog)]262pub enum ERC721Events {263 264 265 266 Transfer {267 #[indexed]268 from: address,269 #[indexed]270 to: address,271 #[indexed]272 token_id: uint256,273 },274 275 Approval {276 #[indexed]277 owner: address,278 #[indexed]279 approved: address,280 #[indexed]281 token_id: uint256,282 },283 284 #[allow(dead_code)]285 ApprovalForAll {286 #[indexed]287 owner: address,288 #[indexed]289 operator: address,290 approved: bool,291 },292}293294#[derive(ToLog)]295pub enum ERC721UniqueMintableEvents {296 297 #[allow(dead_code)]298 MintingFinished {},299}300301#[solidity_interface(name = ERC721Metadata)]302impl<T: Config> RefungibleHandle<T>303where304 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,305{306 307 308 #[solidity(hide, rename_selector = "name")]309 fn name_proxy(&self) -> Result<string> {310 self.name()311 }312313 314 315 #[solidity(hide, rename_selector = "symbol")]316 fn symbol_proxy(&self) -> Result<string> {317 self.symbol()318 }319320 321 322 323 324 325 326 327 328 329 #[solidity(rename_selector = "tokenURI")]330 fn token_uri(&self, token_id: uint256) -> Result<string> {331 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;332333 match get_token_property(self, token_id_u32, &key::url()).as_deref() {334 Err(_) | Ok("") => (),335 Ok(url) => {336 return Ok(url.into());337 }338 };339340 let base_uri =341 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())342 .map(BoundedVec::into_inner)343 .map(string::from_utf8)344 .transpose()345 .map_err(|e| {346 Error::Revert(alloc::format!(347 "Can not convert value \"baseURI\" to string with error \"{}\"",348 e349 ))350 })?;351352 let base_uri = match base_uri.as_deref() {353 None | Some("") => {354 return Ok("".into());355 }356 Some(base_uri) => base_uri.into(),357 };358359 Ok(360 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {361 Err(_) | Ok("") => base_uri,362 Ok(suffix) => base_uri + suffix,363 },364 )365 }366}367368369370#[solidity_interface(name = ERC721Enumerable)]371impl<T: Config> RefungibleHandle<T> {372 373 374 375 376 fn token_by_index(&self, index: uint256) -> Result<uint256> {377 Ok(index)378 }379380 381 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {382 383 Err("not implemented".into())384 }385386 387 388 389 fn total_supply(&self) -> Result<uint256> {390 self.consume_store_reads(1)?;391 Ok(<Pallet<T>>::total_supply(self).into())392 }393}394395396397#[solidity_interface(name = ERC721, events(ERC721Events))]398impl<T: Config> RefungibleHandle<T> {399 400 401 402 403 404 fn balance_of(&self, owner: address) -> Result<uint256> {405 self.consume_store_reads(1)?;406 let owner = T::CrossAccountId::from_eth(owner);407 let balance = <AccountBalance<T>>::get((self.id, owner));408 Ok(balance.into())409 }410411 412 413 414 415 416 417 418 fn owner_of(&self, token_id: uint256) -> Result<address> {419 self.consume_store_reads(2)?;420 let token = token_id.try_into()?;421 let owner = <Pallet<T>>::token_owner(self.id, token);422 Ok(owner423 .map(|address| *address.as_eth())424 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))425 }426427 428 fn safe_transfer_from_with_data(429 &mut self,430 _from: address,431 _to: address,432 _token_id: uint256,433 _data: bytes,434 ) -> Result<void> {435 436 Err("not implemented".into())437 }438439 440 fn safe_transfer_from(441 &mut self,442 _from: address,443 _to: address,444 _token_id: uint256,445 ) -> Result<void> {446 447 Err("not implemented".into())448 }449450 451 452 453 454 455 456 457 458 459 460 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]461 fn transfer_from(462 &mut self,463 caller: caller,464 from: address,465 to: address,466 token_id: uint256,467 ) -> Result<void> {468 let caller = T::CrossAccountId::from_eth(caller);469 let from = T::CrossAccountId::from_eth(from);470 let to = T::CrossAccountId::from_eth(to);471 let token = token_id.try_into()?;472 let budget = self473 .recorder474 .weight_calls_budget(<StructureWeight<T>>::find_parent());475476 let balance = balance(&self, token, &from)?;477 ensure_single_owner(&self, token, balance)?;478479 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)480 .map_err(dispatch_to_evm::<T>)?;481482 Ok(())483 }484485 486 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {487 Err("not implemented".into())488 }489490 491 492 493 494 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]495 fn set_approval_for_all(496 &mut self,497 caller: caller,498 operator: address,499 approved: bool,500 ) -> Result<void> {501 let caller = T::CrossAccountId::from_eth(caller);502 let operator = T::CrossAccountId::from_eth(operator);503504 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)505 .map_err(dispatch_to_evm::<T>)?;506 Ok(())507 }508509 510 fn get_approved(&self, _token_id: uint256) -> Result<address> {511 512 Err("not implemented".into())513 }514515 516 #[weight(<SelfWeightOf<T>>::allowance_for_all())]517 fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {518 let owner = T::CrossAccountId::from_eth(owner);519 let operator = T::CrossAccountId::from_eth(operator);520521 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))522 }523524 525 fn collection_helper_address(&self) -> Result<address> {526 Ok(T::ContractAddress::get())527 }528}529530531pub fn balance<T: Config>(532 collection: &RefungibleHandle<T>,533 token: TokenId,534 owner: &T::CrossAccountId,535) -> Result<u128> {536 collection.consume_store_reads(1)?;537 let balance = <Balance<T>>::get((collection.id, token, &owner));538 Ok(balance)539}540541542pub fn ensure_single_owner<T: Config>(543 collection: &RefungibleHandle<T>,544 token: TokenId,545 owner_balance: u128,546) -> Result<()> {547 collection.consume_store_reads(1)?;548 let total_supply = <TotalSupply<T>>::get((collection.id, token));549550 if owner_balance == 0 {551 return Err(dispatch_to_evm::<T>(552 <CommonError<T>>::MustBeTokenOwner.into(),553 ));554 }555556 if total_supply != owner_balance {557 return Err("token has multiple owners".into());558 }559 Ok(())560}561562563#[solidity_interface(name = ERC721Burnable)]564impl<T: Config> RefungibleHandle<T> {565 566 567 568 569 #[weight(<SelfWeightOf<T>>::burn_item_fully())]570 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {571 let caller = T::CrossAccountId::from_eth(caller);572 let token = token_id.try_into()?;573574 let balance = balance(&self, token, &caller)?;575 ensure_single_owner(&self, token, balance)?;576577 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;578 Ok(())579 }580}581582583#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]584impl<T: Config> RefungibleHandle<T> {585 fn minting_finished(&self) -> Result<bool> {586 Ok(false)587 }588589 590 591 592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {594 let token_id: uint256 = <TokensMinted<T>>::get(self.id)595 .checked_add(1)596 .ok_or("item id overflow")?597 .into();598 self.mint_check_id(caller, to, token_id)?;599 Ok(token_id)600 }601602 603 604 605 606 607 #[solidity(hide, rename_selector = "mint")]608 #[weight(<SelfWeightOf<T>>::create_item())]609 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {610 let caller = T::CrossAccountId::from_eth(caller);611 let to = T::CrossAccountId::from_eth(to);612 let token_id: u32 = token_id.try_into()?;613 let budget = self614 .recorder615 .weight_calls_budget(<StructureWeight<T>>::find_parent());616617 if <TokensMinted<T>>::get(self.id)618 .checked_add(1)619 .ok_or("item id overflow")?620 != token_id621 {622 return Err("item id should be next".into());623 }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> {634 users,635 properties: CollectionPropertiesVec::default(),636 },637 &budget,638 )639 .map_err(dispatch_to_evm::<T>)?;640641 Ok(true)642 }643644 645 646 647 648 #[solidity(rename_selector = "mintWithTokenURI")]649 #[weight(<SelfWeightOf<T>>::create_item())]650 fn mint_with_token_uri(651 &mut self,652 caller: caller,653 to: address,654 token_uri: string,655 ) -> Result<uint256> {656 let token_id: uint256 = <TokensMinted<T>>::get(self.id)657 .checked_add(1)658 .ok_or("item id overflow")?659 .into();660 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;661 Ok(token_id)662 }663664 665 666 667 668 669 670 #[solidity(hide, rename_selector = "mintWithTokenURI")]671 #[weight(<SelfWeightOf<T>>::create_item())]672 fn mint_with_token_uri_check_id(673 &mut self,674 caller: caller,675 to: address,676 token_id: uint256,677 token_uri: string,678 ) -> Result<bool> {679 let key = key::url();680 let permission = get_token_permission::<T>(self.id, &key)?;681 if !permission.collection_admin {682 return Err("Operation is not allowed".into());683 }684685 let caller = T::CrossAccountId::from_eth(caller);686 let to = T::CrossAccountId::from_eth(to);687 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;688 let budget = self689 .recorder690 .weight_calls_budget(<StructureWeight<T>>::find_parent());691692 if <TokensMinted<T>>::get(self.id)693 .checked_add(1)694 .ok_or("item id overflow")?695 != token_id696 {697 return Err("item id should be next".into());698 }699700 let mut properties = CollectionPropertiesVec::default();701 properties702 .try_push(Property {703 key,704 value: token_uri705 .into_bytes()706 .try_into()707 .map_err(|_| "token uri is too long")?,708 })709 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;710711 let users = [(to.clone(), 1)]712 .into_iter()713 .collect::<BTreeMap<_, _>>()714 .try_into()715 .unwrap();716 <Pallet<T>>::create_item(717 self,718 &caller,719 CreateItemData::<T> { users, properties },720 &budget,721 )722 .map_err(dispatch_to_evm::<T>)?;723 Ok(true)724 }725726 727 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {728 Err("not implementable".into())729 }730}731732fn get_token_property<T: Config>(733 collection: &CollectionHandle<T>,734 token_id: u32,735 key: &up_data_structs::PropertyKey,736) -> Result<string> {737 collection.consume_store_reads(1)?;738 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))739 .map_err(|_| Error::Revert("Token properties not found".into()))?;740 if let Some(property) = properties.get(key) {741 return Ok(string::from_utf8_lossy(property).into());742 }743744 Err("Property tokenURI not found".into())745}746747fn get_token_permission<T: Config>(748 collection_id: CollectionId,749 key: &PropertyKey,750) -> Result<PropertyPermission> {751 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)752 .map_err(|_| Error::Revert("No permissions for collection".into()))?;753 let a = token_property_permissions754 .get(key)755 .map(Clone::clone)756 .ok_or_else(|| {757 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();758 Error::Revert(alloc::format!("No permission for key {}", key))759 })?;760 Ok(a)761}762763764#[solidity_interface(name = ERC721UniqueExtensions)]765impl<T: Config> RefungibleHandle<T>766where767 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,768{769 770 fn name(&self) -> Result<string> {771 Ok(decode_utf16(self.name.iter().copied())772 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))773 .collect::<string>())774 }775776 777 fn symbol(&self) -> Result<string> {778 Ok(string::from_utf8_lossy(&self.token_prefix).into())779 }780781 782 fn description(&self) -> Result<string> {783 Ok(decode_utf16(self.description.iter().copied())784 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))785 .collect::<string>())786 }787788 789 790 791 fn cross_owner_of(&self, token_id: uint256) -> Result<pallet_common::eth::CrossAddress> {792 Self::token_owner(&self, token_id.try_into()?)793 .map(|o| pallet_common::eth::CrossAddress::from_sub_cross_account::<T>(&o))794 .ok_or(Error::Revert("key too large".into()))795 }796797 798 799 800 801 802 fn properties(803 &self,804 token_id: uint256,805 keys: Vec<string>,806 ) -> Result<Vec<pallet_common::eth::Property>> {807 let keys = keys808 .into_iter()809 .map(|key| {810 <Vec<u8>>::from(key)811 .try_into()812 .map_err(|_| Error::Revert("key too large".into()))813 })814 .collect::<Result<Vec<_>>>()?;815816 <Self as CommonCollectionOperations<T>>::token_properties(817 &self,818 token_id.try_into()?,819 if keys.is_empty() { None } else { Some(keys) },820 )821 .into_iter()822 .map(pallet_common::eth::Property::try_from)823 .collect::<Result<Vec<_>>>()824 }825 826 827 828 829 830 831 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]832 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {833 let caller = T::CrossAccountId::from_eth(caller);834 let to = T::CrossAccountId::from_eth(to);835 let token = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839840 let balance = balance(self, token, &caller)?;841 ensure_single_owner(self, token, balance)?;842843 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)844 .map_err(dispatch_to_evm::<T>)?;845 Ok(())846 }847848 849 850 851 852 853 854 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]855 fn transfer_cross(856 &mut self,857 caller: caller,858 to: pallet_common::eth::CrossAddress,859 token_id: uint256,860 ) -> Result<void> {861 let caller = T::CrossAccountId::from_eth(caller);862 let to = to.into_sub_cross_account::<T>()?;863 let token = token_id.try_into()?;864 let budget = self865 .recorder866 .weight_calls_budget(<StructureWeight<T>>::find_parent());867868 let balance = balance(self, token, &caller)?;869 ensure_single_owner(self, token, balance)?;870871 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)872 .map_err(dispatch_to_evm::<T>)?;873 Ok(())874 }875876 877 878 879 880 881 882 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]883 fn transfer_from_cross(884 &mut self,885 caller: caller,886 from: pallet_common::eth::CrossAddress,887 to: pallet_common::eth::CrossAddress,888 token_id: uint256,889 ) -> Result<void> {890 let caller = T::CrossAccountId::from_eth(caller);891 let from = from.into_sub_cross_account::<T>()?;892 let to = to.into_sub_cross_account::<T>()?;893 let token_id = token_id.try_into()?;894 let budget = self895 .recorder896 .weight_calls_budget(<StructureWeight<T>>::find_parent());897898 let balance = balance(self, token_id, &from)?;899 ensure_single_owner(self, token_id, balance)?;900901 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)902 .map_err(dispatch_to_evm::<T>)?;903 Ok(())904 }905906 907 908 909 910 911 912 913 #[solidity(hide)]914 #[weight(<SelfWeightOf<T>>::burn_from())]915 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {916 let caller = T::CrossAccountId::from_eth(caller);917 let from = T::CrossAccountId::from_eth(from);918 let token = token_id.try_into()?;919 let budget = self920 .recorder921 .weight_calls_budget(<StructureWeight<T>>::find_parent());922923 let balance = balance(self, token, &from)?;924 ensure_single_owner(self, token, balance)?;925926 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)927 .map_err(dispatch_to_evm::<T>)?;928 Ok(())929 }930931 932 933 934 935 936 937 938 #[weight(<SelfWeightOf<T>>::burn_from())]939 fn burn_from_cross(940 &mut self,941 caller: caller,942 from: pallet_common::eth::CrossAddress,943 token_id: uint256,944 ) -> Result<void> {945 let caller = T::CrossAccountId::from_eth(caller);946 let from = from.into_sub_cross_account::<T>()?;947 let token = token_id.try_into()?;948 let budget = self949 .recorder950 .weight_calls_budget(<StructureWeight<T>>::find_parent());951952 let balance = balance(self, token, &from)?;953 ensure_single_owner(self, token, balance)?;954955 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)956 .map_err(dispatch_to_evm::<T>)?;957 Ok(())958 }959960 961 fn next_token_id(&self) -> Result<uint256> {962 self.consume_store_reads(1)?;963 Ok(<TokensMinted<T>>::get(self.id)964 .checked_add(1)965 .ok_or("item id overflow")?966 .into())967 }968969 970 971 972 973 974 #[solidity(hide)]975 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]976 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {977 let caller = T::CrossAccountId::from_eth(caller);978 let to = T::CrossAccountId::from_eth(to);979 let mut expected_index = <TokensMinted<T>>::get(self.id)980 .checked_add(1)981 .ok_or("item id overflow")?;982 let budget = self983 .recorder984 .weight_calls_budget(<StructureWeight<T>>::find_parent());985986 let total_tokens = token_ids.len();987 for id in token_ids.into_iter() {988 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;989 if id != expected_index {990 return Err("item id should be next".into());991 }992 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;993 }994 let users = [(to.clone(), 1)]995 .into_iter()996 .collect::<BTreeMap<_, _>>()997 .try_into()998 .unwrap();999 let create_item_data = CreateItemData::<T> {1000 users,1001 properties: CollectionPropertiesVec::default(),1002 };1003 let data = (0..total_tokens)1004 .map(|_| create_item_data.clone())1005 .collect();10061007 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1008 .map_err(dispatch_to_evm::<T>)?;1009 Ok(true)1010 }10111012 1013 1014 1015 1016 1017 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1018 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]1019 fn mint_bulk_with_token_uri(1020 &mut self,1021 caller: caller,1022 to: address,1023 tokens: Vec<(uint256, string)>,1024 ) -> Result<bool> {1025 let key = key::url();1026 let caller = T::CrossAccountId::from_eth(caller);1027 let to = T::CrossAccountId::from_eth(to);1028 let mut expected_index = <TokensMinted<T>>::get(self.id)1029 .checked_add(1)1030 .ok_or("item id overflow")?;1031 let budget = self1032 .recorder1033 .weight_calls_budget(<StructureWeight<T>>::find_parent());10341035 let mut data = Vec::with_capacity(tokens.len());1036 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1037 .into_iter()1038 .collect::<BTreeMap<_, _>>()1039 .try_into()1040 .unwrap();1041 for (id, token_uri) in tokens {1042 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1043 if id != expected_index {1044 return Err("item id should be next".into());1045 }1046 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10471048 let mut properties = CollectionPropertiesVec::default();1049 properties1050 .try_push(Property {1051 key: key.clone(),1052 value: token_uri1053 .into_bytes()1054 .try_into()1055 .map_err(|_| "token uri is too long")?,1056 })1057 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10581059 let create_item_data = CreateItemData::<T> {1060 users: users.clone(),1061 properties,1062 };1063 data.push(create_item_data);1064 }10651066 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1067 .map_err(dispatch_to_evm::<T>)?;1068 Ok(true)1069 }10701071 1072 1073 1074 1075 #[weight(<SelfWeightOf<T>>::create_item())]1076 fn mint_cross(1077 &mut self,1078 caller: caller,1079 to: pallet_common::eth::CrossAddress,1080 properties: Vec<pallet_common::eth::Property>,1081 ) -> Result<uint256> {1082 let token_id = <TokensMinted<T>>::get(self.id)1083 .checked_add(1)1084 .ok_or("item id overflow")?;10851086 let to = to.into_sub_cross_account::<T>()?;10871088 let properties = properties1089 .into_iter()1090 .map(pallet_common::eth::Property::try_into)1091 .collect::<Result<Vec<_>>>()?1092 .try_into()1093 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10941095 let caller = T::CrossAccountId::from_eth(caller);10961097 let budget = self1098 .recorder1099 .weight_calls_budget(<StructureWeight<T>>::find_parent());11001101 let users = [(to, 1)]1102 .into_iter()1103 .collect::<BTreeMap<_, _>>()1104 .try_into()1105 .unwrap();1106 <Pallet<T>>::create_item(1107 self,1108 &caller,1109 CreateItemData::<T> { users, properties },1110 &budget,1111 )1112 .map_err(dispatch_to_evm::<T>)?;11131114 Ok(token_id.into())1115 }11161117 1118 1119 1120 fn token_contract_address(&self, token: uint256) -> Result<address> {1121 Ok(T::EvmTokenAddressMapping::token_to_address(1122 self.id,1123 token.try_into().map_err(|_| "token id overflow")?,1124 ))1125 }1126}11271128#[solidity_interface(1129 name = UniqueRefungible,1130 is(1131 ERC721,1132 ERC721Enumerable,1133 ERC721UniqueExtensions,1134 ERC721UniqueMintable,1135 ERC721Burnable,1136 ERC721Metadata(if(this.flags.erc721metadata)),1137 Collection(via(common_mut returns CollectionHandle<T>)),1138 TokenProperties,1139 )1140)]1141impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}114211431144generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1145generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11461147impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1148where1149 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1150{1151 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1152 fn call(1153 self,1154 handle: &mut impl PrecompileHandle,1155 ) -> Option<pallet_common::erc::PrecompileResult> {1156 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1157 }1158}