12345678910111213141516171819202122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{decode_utf16, REPLACEMENT_CHARACTER},27 convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::{BoundedBTreeMap, BoundedVec};32use pallet_common::{33 erc::{static_property::key, CollectionCall, CommonEvmHandler},34 eth::{self, TokenUri},35 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,36 Error as CommonError,37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{40 call, dispatch_to_evm,41 execution::{Error, PreDispatch, Result},42 frontier_contract,43};44use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};45use sp_core::{Get, H160, U256};46use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};47use up_data_structs::{48 mapping::TokenAddressMapping, CollectionId, CollectionPropertiesVec, Property, PropertyKey,49 PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,50};5152use crate::{53 weights::WeightInfo, AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle,54 SelfWeightOf, TokenProperties, TokensMinted, TotalSupply,55};5657frontier_contract! {58 macro_rules! RefungibleHandle_result {...}59 impl<T: Config> Contract for RefungibleHandle<T> {...}60}6162pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);636465#[derive(ToLog)]66pub enum ERC721TokenEvent {67 68 TokenChanged {69 70 #[indexed]71 token_id: U256,72 },73}747576#[derive(AbiCoder, Default, Debug)]77pub struct OwnerPieces {78 79 pub owner: eth::CrossAddress,80 81 pub pieces: u128,82}838485#[derive(AbiCoder, Default, Debug)]86pub struct MintTokenData {87 88 pub owners: Vec<OwnerPieces>,89 90 pub properties: Vec<eth::Property>,91}929394#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]95impl<T: Config> RefungibleHandle<T> {96 97 98 99 100 101 102 #[solidity(hide)]103 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]104 fn set_token_property_permission(105 &mut self,106 caller: Caller,107 key: String,108 is_mutable: bool,109 collection_admin: bool,110 token_owner: bool,111 ) -> Result<()> {112 let caller = T::CrossAccountId::from_eth(caller);113 <Pallet<T>>::set_token_property_permissions(114 self,115 &caller,116 vec![PropertyKeyPermission {117 key: <Vec<u8>>::from(key)118 .try_into()119 .map_err(|_| "too long key")?,120 permission: PropertyPermission {121 mutable: is_mutable,122 collection_admin,123 token_owner,124 },125 }],126 )127 .map_err(dispatch_to_evm::<T>)128 }129130 131 132 133 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]134 fn set_token_property_permissions(135 &mut self,136 caller: Caller,137 permissions: Vec<eth::TokenPropertyPermission>,138 ) -> Result<()> {139 let caller = T::CrossAccountId::from_eth(caller);140 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;141142 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)143 .map_err(dispatch_to_evm::<T>)144 }145146 147 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {148 let perms = <Pallet<T>>::token_property_permission(self.id);149 Ok(perms150 .into_iter()151 .map(eth::TokenPropertyPermission::from)152 .collect())153 }154155 156 157 158 159 160 #[solidity(hide)]161 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]162 fn set_property(163 &mut self,164 caller: Caller,165 token_id: U256,166 key: String,167 value: Bytes,168 ) -> Result<()> {169 let caller = T::CrossAccountId::from_eth(caller);170 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;171 let key = <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| "key too long")?;174 let value = value.0.try_into().map_err(|_| "value too long")?;175176 let nesting_budget = self177 .recorder178 .weight_calls_budget(<StructureWeight<T>>::find_parent());179180 <Pallet<T>>::set_token_property(181 self,182 &caller,183 TokenId(token_id),184 Property { key, value },185 &nesting_budget,186 )187 .map_err(dispatch_to_evm::<T>)188 }189190 191 192 193 194 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]195 fn set_properties(196 &mut self,197 caller: Caller,198 token_id: U256,199 properties: Vec<eth::Property>,200 ) -> Result<()> {201 let caller = T::CrossAccountId::from_eth(caller);202 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;203204 let nesting_budget = self205 .recorder206 .weight_calls_budget(<StructureWeight<T>>::find_parent());207208 let properties = properties209 .into_iter()210 .map(eth::Property::try_into)211 .collect::<Result<Vec<_>>>()?;212213 <Pallet<T>>::set_token_properties(214 self,215 &caller,216 TokenId(token_id),217 properties.into_iter(),218 &nesting_budget,219 )220 .map_err(dispatch_to_evm::<T>)221 }222223 224 225 226 227 #[solidity(hide)]228 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]229 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {230 let caller = T::CrossAccountId::from_eth(caller);231 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;232 let key = <Vec<u8>>::from(key)233 .try_into()234 .map_err(|_| "key too long")?;235236 let nesting_budget = self237 .recorder238 .weight_calls_budget(<StructureWeight<T>>::find_parent());239240 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)241 .map_err(dispatch_to_evm::<T>)242 }243244 245 246 247 248 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]249 fn delete_properties(250 &mut self,251 token_id: U256,252 caller: Caller,253 keys: Vec<String>,254 ) -> Result<()> {255 let caller = T::CrossAccountId::from_eth(caller);256 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;257 let keys = keys258 .into_iter()259 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))260 .collect::<Result<Vec<_>>>()?;261262 let nesting_budget = self263 .recorder264 .weight_calls_budget(<StructureWeight<T>>::find_parent());265266 <Pallet<T>>::delete_token_properties(267 self,268 &caller,269 TokenId(token_id),270 keys.into_iter(),271 &nesting_budget,272 )273 .map_err(dispatch_to_evm::<T>)274 }275276 277 278 279 280 281 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {282 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;283 let key = <Vec<u8>>::from(key)284 .try_into()285 .map_err(|_| "key too long")?;286287 let props =288 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;289 let prop = props.get(&key).ok_or("key not found")?;290291 Ok(prop.to_vec().into())292 }293}294295#[derive(ToLog)]296pub enum ERC721Events {297 298 299 300 Transfer {301 #[indexed]302 from: Address,303 #[indexed]304 to: Address,305 #[indexed]306 token_id: U256,307 },308 309 Approval {310 #[indexed]311 owner: Address,312 #[indexed]313 approved: Address,314 #[indexed]315 token_id: U256,316 },317 318 #[allow(dead_code)]319 ApprovalForAll {320 #[indexed]321 owner: Address,322 #[indexed]323 operator: Address,324 approved: bool,325 },326}327328329330#[solidity_interface(name = ERC721Metadata, enum(derive(PreDispatch)), expect_selector = 0x5b5e139f)]331impl<T: Config> RefungibleHandle<T>332where333 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,334{335 336 337 #[solidity(hide, rename_selector = "name")]338 fn name_proxy(&self) -> Result<String> {339 self.name()340 }341342 343 344 #[solidity(hide, rename_selector = "symbol")]345 fn symbol_proxy(&self) -> Result<String> {346 self.symbol()347 }348349 350 351 352 353 354 355 356 357 358 #[solidity(rename_selector = "tokenURI")]359 fn token_uri(&self, token_id: U256) -> Result<String> {360 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;361362 match get_token_property(self, token_id_u32, &key::url()).as_deref() {363 Err(_) | Ok("") => (),364 Ok(url) => {365 return Ok(url.into());366 }367 };368369 let base_uri =370 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())371 .map(BoundedVec::into_inner)372 .map(String::from_utf8)373 .transpose()374 .map_err(|e| {375 Error::Revert(alloc::format!(376 "can not convert value \"baseURI\" to string with error \"{e}\""377 ))378 })?;379380 let base_uri = match base_uri.as_deref() {381 None | Some("") => {382 return Ok("".into());383 }384 Some(base_uri) => base_uri.into(),385 };386387 Ok(388 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {389 Err(_) | Ok("") => base_uri,390 Ok(suffix) => base_uri + suffix,391 },392 )393 }394}395396397398#[solidity_interface(name = ERC721Enumerable, enum(derive(PreDispatch)), expect_selector = 0x780e9d63)]399impl<T: Config> RefungibleHandle<T> {400 401 402 403 404 fn token_by_index(&self, index: U256) -> U256 {405 index406 }407408 409 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {410 411 Err("not implemented".into())412 }413414 415 416 417 fn total_supply(&self) -> Result<U256> {418 self.consume_store_reads(1)?;419 Ok(<Pallet<T>>::total_supply(self).into())420 }421}422423424425#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]426impl<T: Config> RefungibleHandle<T> {427 428 429 430 431 432 fn balance_of(&self, owner: Address) -> Result<U256> {433 self.consume_store_reads(1)?;434 let owner = T::CrossAccountId::from_eth(owner);435 let balance = <AccountBalance<T>>::get((self.id, owner));436 Ok(balance.into())437 }438439 440 441 442 443 444 445 446 fn owner_of(&self, token_id: U256) -> Result<Address> {447 self.consume_store_reads(2)?;448 let token = token_id.try_into()?;449 let owner = <Pallet<T>>::token_owner(self.id, token);450 owner451 .map(|address| *address.as_eth())452 .or_else(|err| match err {453 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),454 TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),455 })456 }457458 459 #[solidity(rename_selector = "safeTransferFrom")]460 fn safe_transfer_from_with_data(461 &mut self,462 _from: Address,463 _to: Address,464 _token_id: U256,465 _data: Bytes,466 ) -> Result<()> {467 468 Err("not implemented".into())469 }470471 472 #[solidity(rename_selector = "safeTransferFrom")]473 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {474 475 Err("not implemented".into())476 }477478 479 480 481 482 483 484 485 486 487 488 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]489 fn transfer_from(490 &mut self,491 caller: Caller,492 from: Address,493 to: Address,494 token_id: U256,495 ) -> Result<()> {496 let caller = T::CrossAccountId::from_eth(caller);497 let from = T::CrossAccountId::from_eth(from);498 let to = T::CrossAccountId::from_eth(to);499 let token = token_id.try_into()?;500 let budget = self501 .recorder502 .weight_calls_budget(<StructureWeight<T>>::find_parent());503504 let balance = balance(self, token, &from)?;505 ensure_single_owner(self, token, balance)?;506507 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)508 .map_err(dispatch_to_evm::<T>)?;509510 Ok(())511 }512513 514 fn approve(&mut self, _caller: Caller, _approved: Address, _token_id: U256) -> Result<()> {515 Err("not implemented".into())516 }517518 519 520 521 522 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]523 fn set_approval_for_all(524 &mut self,525 caller: Caller,526 operator: Address,527 approved: bool,528 ) -> Result<()> {529 let caller = T::CrossAccountId::from_eth(caller);530 let operator = T::CrossAccountId::from_eth(operator);531532 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)533 .map_err(dispatch_to_evm::<T>)?;534 Ok(())535 }536537 538 fn get_approved(&self, _token_id: U256) -> Result<Address> {539 540 Err("not implemented".into())541 }542543 544 #[weight(<SelfWeightOf<T>>::allowance_for_all())]545 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {546 let owner = T::CrossAccountId::from_eth(owner);547 let operator = T::CrossAccountId::from_eth(operator);548549 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))550 }551}552553554pub fn balance<T: Config>(555 collection: &RefungibleHandle<T>,556 token: TokenId,557 owner: &T::CrossAccountId,558) -> Result<u128> {559 collection.consume_store_reads(1)?;560 let balance = <Balance<T>>::get((collection.id, token, &owner));561 Ok(balance)562}563564565pub fn ensure_single_owner<T: Config>(566 collection: &RefungibleHandle<T>,567 token: TokenId,568 owner_balance: u128,569) -> Result<()> {570 collection.consume_store_reads(1)?;571 let total_supply = <TotalSupply<T>>::get((collection.id, token));572573 if owner_balance == 0 {574 return Err(dispatch_to_evm::<T>(575 <CommonError<T>>::MustBeTokenOwner.into(),576 ));577 }578579 if total_supply != owner_balance {580 return Err("token has multiple owners".into());581 }582 Ok(())583}584585586#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]587impl<T: Config> RefungibleHandle<T> {588 589 590 591 592 #[weight(<SelfWeightOf<T>>::burn_item_fully())]593 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {594 let caller = T::CrossAccountId::from_eth(caller);595 let token = token_id.try_into()?;596597 let balance = balance(self, token, &caller)?;598 ensure_single_owner(self, token, balance)?;599600 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;601 Ok(())602 }603}604605606#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]607impl<T: Config> RefungibleHandle<T> {608 609 610 611 #[weight(<SelfWeightOf<T>>::create_item())]612 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {613 let token_id: U256 = <TokensMinted<T>>::get(self.id)614 .checked_add(1)615 .ok_or("item id overflow")?616 .into();617 self.mint_check_id(caller, to, token_id)?;618 Ok(token_id)619 }620621 622 623 624 625 626 #[solidity(hide, rename_selector = "mint")]627 #[weight(<SelfWeightOf<T>>::create_item())]628 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {629 let caller = T::CrossAccountId::from_eth(caller);630 let to = T::CrossAccountId::from_eth(to);631 let token_id: u32 = token_id.try_into()?;632 let budget = self633 .recorder634 .weight_calls_budget(<StructureWeight<T>>::find_parent());635636 if <TokensMinted<T>>::get(self.id)637 .checked_add(1)638 .ok_or("item id overflow")?639 != token_id640 {641 return Err("item id should be next".into());642 }643644 let users = [(to, 1)]645 .into_iter()646 .collect::<BTreeMap<_, _>>()647 .try_into()648 .unwrap();649 <Pallet<T>>::create_item(650 self,651 &caller,652 CreateItemData::<T> {653 users,654 properties: CollectionPropertiesVec::default(),655 },656 &budget,657 )658 .map_err(dispatch_to_evm::<T>)?;659660 Ok(true)661 }662663 664 665 666 667 #[solidity(rename_selector = "mintWithTokenURI")]668 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]669 fn mint_with_token_uri(670 &mut self,671 caller: Caller,672 to: Address,673 token_uri: String,674 ) -> Result<U256> {675 let token_id: U256 = <TokensMinted<T>>::get(self.id)676 .checked_add(1)677 .ok_or("item id overflow")?678 .into();679 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;680 Ok(token_id)681 }682683 684 685 686 687 688 689 #[solidity(hide, rename_selector = "mintWithTokenURI")]690 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]691 fn mint_with_token_uri_check_id(692 &mut self,693 caller: Caller,694 to: Address,695 token_id: U256,696 token_uri: String,697 ) -> Result<bool> {698 let key = key::url();699 let permission = get_token_permission::<T>(self.id, &key)?;700 if !permission.collection_admin {701 return Err("operation is not allowed".into());702 }703704 let caller = T::CrossAccountId::from_eth(caller);705 let to = T::CrossAccountId::from_eth(to);706 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;707 let budget = self708 .recorder709 .weight_calls_budget(<StructureWeight<T>>::find_parent());710711 if <TokensMinted<T>>::get(self.id)712 .checked_add(1)713 .ok_or("item id overflow")?714 != token_id715 {716 return Err("item id should be next".into());717 }718719 let mut properties = CollectionPropertiesVec::default();720 properties721 .try_push(Property {722 key,723 value: token_uri724 .into_bytes()725 .try_into()726 .map_err(|_| "token uri is too long")?,727 })728 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;729730 let users = [(to, 1)]731 .into_iter()732 .collect::<BTreeMap<_, _>>()733 .try_into()734 .unwrap();735 <Pallet<T>>::create_item(736 self,737 &caller,738 CreateItemData::<T> { users, properties },739 &budget,740 )741 .map_err(dispatch_to_evm::<T>)?;742 Ok(true)743 }744}745746fn get_token_property<T: Config>(747 collection: &CollectionHandle<T>,748 token_id: u32,749 key: &up_data_structs::PropertyKey,750) -> Result<String> {751 collection.consume_store_reads(1)?;752 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))753 .map_err(|_| Error::Revert("token properties not found".into()))?;754 if let Some(property) = properties.get(key) {755 return Ok(String::from_utf8_lossy(property).into());756 }757758 Err("property tokenURI not found".into())759}760761fn get_token_permission<T: Config>(762 collection_id: CollectionId,763 key: &PropertyKey,764) -> Result<PropertyPermission> {765 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)766 .map_err(|_| Error::Revert("no permissions for collection".into()))?;767 let a = token_property_permissions768 .get(key)769 .map(Clone::clone)770 .ok_or_else(|| {771 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();772 Error::Revert(alloc::format!("no permission for key {key}"))773 })?;774 Ok(a)775}776777778#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]779impl<T: Config> RefungibleHandle<T>780where781 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,782{783 784 fn name(&self) -> Result<String> {785 Ok(decode_utf16(self.name.iter().copied())786 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))787 .collect::<String>())788 }789790 791 fn symbol(&self) -> Result<String> {792 Ok(String::from_utf8_lossy(&self.token_prefix).into())793 }794795 796 fn description(&self) -> Result<String> {797 Ok(decode_utf16(self.description.iter().copied())798 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))799 .collect::<String>())800 }801802 803 804 805 #[solidity(hide)]806 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {807 Self::owner_of_cross(self, token_id)808 }809810 811 812 813 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {814 Self::token_owner(self, token_id.try_into()?)815 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))816 .or_else(|err| match err {817 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),818 TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(819 ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,820 )),821 })822 }823824 825 826 827 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {828 self.consume_store_reads(1)?;829 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));830 Ok(balance.into())831 }832833 834 835 836 837 838 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {839 let keys = keys840 .into_iter()841 .map(|key| {842 <Vec<u8>>::from(key)843 .try_into()844 .map_err(|_| Error::Revert("key too large".into()))845 })846 .collect::<Result<Vec<_>>>()?;847848 <Self as CommonCollectionOperations<T>>::token_properties(849 self,850 token_id.try_into()?,851 if keys.is_empty() { None } else { Some(keys) },852 )853 .into_iter()854 .map(eth::Property::try_from)855 .collect::<Result<Vec<_>>>()856 }857 858 859 860 861 862 863 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]864 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {865 let caller = T::CrossAccountId::from_eth(caller);866 let to = T::CrossAccountId::from_eth(to);867 let token = token_id.try_into()?;868 let budget = self869 .recorder870 .weight_calls_budget(<StructureWeight<T>>::find_parent());871872 let balance = balance(self, token, &caller)?;873 ensure_single_owner(self, token, balance)?;874875 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)876 .map_err(dispatch_to_evm::<T>)?;877 Ok(())878 }879880 881 882 883 884 885 886 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]887 fn transfer_cross(888 &mut self,889 caller: Caller,890 to: eth::CrossAddress,891 token_id: U256,892 ) -> Result<()> {893 let caller = T::CrossAccountId::from_eth(caller);894 let to = to.into_sub_cross_account::<T>()?;895 let token = token_id.try_into()?;896 let budget = self897 .recorder898 .weight_calls_budget(<StructureWeight<T>>::find_parent());899900 let balance = balance(self, token, &caller)?;901 ensure_single_owner(self, token, balance)?;902903 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)904 .map_err(dispatch_to_evm::<T>)?;905 Ok(())906 }907908 909 910 911 912 913 914 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]915 fn transfer_from_cross(916 &mut self,917 caller: Caller,918 from: eth::CrossAddress,919 to: eth::CrossAddress,920 token_id: U256,921 ) -> Result<()> {922 let caller = T::CrossAccountId::from_eth(caller);923 let from = from.into_sub_cross_account::<T>()?;924 let to = to.into_sub_cross_account::<T>()?;925 let token_id = token_id.try_into()?;926 let budget = self927 .recorder928 .weight_calls_budget(<StructureWeight<T>>::find_parent());929930 let balance = balance(self, token_id, &from)?;931 ensure_single_owner(self, token_id, balance)?;932933 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)934 .map_err(dispatch_to_evm::<T>)?;935 Ok(())936 }937938 939 940 941 942 943 944 945 #[solidity(hide)]946 #[weight(<SelfWeightOf<T>>::burn_from())]947 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {948 let caller = T::CrossAccountId::from_eth(caller);949 let from = T::CrossAccountId::from_eth(from);950 let token = token_id.try_into()?;951 let budget = self952 .recorder953 .weight_calls_budget(<StructureWeight<T>>::find_parent());954955 let balance = balance(self, token, &from)?;956 ensure_single_owner(self, token, balance)?;957958 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)959 .map_err(dispatch_to_evm::<T>)?;960 Ok(())961 }962963 964 965 966 967 968 969 970 #[weight(<SelfWeightOf<T>>::burn_from())]971 fn burn_from_cross(972 &mut self,973 caller: Caller,974 from: eth::CrossAddress,975 token_id: U256,976 ) -> Result<()> {977 let caller = T::CrossAccountId::from_eth(caller);978 let from = from.into_sub_cross_account::<T>()?;979 let token = token_id.try_into()?;980 let budget = self981 .recorder982 .weight_calls_budget(<StructureWeight<T>>::find_parent());983984 let balance = balance(self, token, &from)?;985 ensure_single_owner(self, token, balance)?;986987 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)988 .map_err(dispatch_to_evm::<T>)?;989 Ok(())990 }991992 993 fn next_token_id(&self) -> Result<U256> {994 self.consume_store_reads(1)?;995 Ok(<Pallet<T>>::next_token_id(self)996 .map_err(dispatch_to_evm::<T>)?997 .into())998 }9991000 1001 1002 1003 1004 1005 #[solidity(hide)]1006 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]1007 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {1008 let caller = T::CrossAccountId::from_eth(caller);1009 let to = T::CrossAccountId::from_eth(to);1010 let mut expected_index = <TokensMinted<T>>::get(self.id)1011 .checked_add(1)1012 .ok_or("item id overflow")?;1013 let budget = self1014 .recorder1015 .weight_calls_budget(<StructureWeight<T>>::find_parent());10161017 let total_tokens = token_ids.len();1018 for id in token_ids.into_iter() {1019 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1020 if id != expected_index {1021 return Err("item id should be next".into());1022 }1023 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;1024 }1025 let users = [(to, 1)]1026 .into_iter()1027 .collect::<BTreeMap<_, _>>()1028 .try_into()1029 .unwrap();1030 let create_item_data = CreateItemData::<T> {1031 users,1032 properties: CollectionPropertiesVec::default(),1033 };1034 let data = (0..total_tokens)1035 .map(|_| create_item_data.clone())1036 .collect();10371038 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1039 .map_err(dispatch_to_evm::<T>)?;1040 Ok(true)1041 }10421043 1044 1045 #[weight(if token_properties.len() == 1 {1046 <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32)1047 } else {1048 <SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(token_properties.len() as u32)1049 } + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]1050 fn mint_bulk_cross(1051 &mut self,1052 caller: Caller,1053 token_properties: Vec<MintTokenData>,1054 ) -> Result<bool> {1055 let caller = T::CrossAccountId::from_eth(caller);1056 let budget = self1057 .recorder1058 .weight_calls_budget(<StructureWeight<T>>::find_parent());1059 let has_multiple_tokens = token_properties.len() > 1;10601061 let mut create_rft_data = Vec::with_capacity(token_properties.len());1062 for MintTokenData { owners, properties } in token_properties {1063 let has_multiple_owners = owners.len() > 1;1064 if has_multiple_tokens & has_multiple_owners {1065 return Err(1066 "creation of multiple tokens supported only if they have single owner each"1067 .into(),1068 );1069 }1070 let users: BoundedBTreeMap<_, _, _> = owners1071 .into_iter()1072 .map(|data| Ok((data.owner.into_sub_cross_account::<T>()?, data.pieces)))1073 .collect::<Result<BTreeMap<_, _>>>()?1074 .try_into()1075 .map_err(|_| "too many users")?;1076 create_rft_data.push(CreateItemData::<T> {1077 properties: properties1078 .into_iter()1079 .map(|property| property.try_into())1080 .collect::<Result<Vec<_>>>()?1081 .try_into()1082 .map_err(|_| "too many properties")?,1083 users,1084 });1085 }10861087 <Pallet<T>>::create_multiple_items(self, &caller, create_rft_data, &budget)1088 .map_err(dispatch_to_evm::<T>)?;1089 Ok(true)1090 }10911092 1093 1094 1095 1096 1097 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1098 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1099 fn mint_bulk_with_token_uri(1100 &mut self,1101 caller: Caller,1102 to: Address,1103 tokens: Vec<TokenUri>,1104 ) -> Result<bool> {1105 let key = key::url();1106 let caller = T::CrossAccountId::from_eth(caller);1107 let to = T::CrossAccountId::from_eth(to);1108 let mut expected_index = <TokensMinted<T>>::get(self.id)1109 .checked_add(1)1110 .ok_or("item id overflow")?;1111 let budget = self1112 .recorder1113 .weight_calls_budget(<StructureWeight<T>>::find_parent());11141115 let mut data = Vec::with_capacity(tokens.len());1116 let users: BoundedBTreeMap<_, _, _> = [(to, 1)]1117 .into_iter()1118 .collect::<BTreeMap<_, _>>()1119 .try_into()1120 .unwrap();1121 for TokenUri { id, uri } in tokens {1122 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1123 if id != expected_index {1124 return Err("item id should be next".into());1125 }1126 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;11271128 let mut properties = CollectionPropertiesVec::default();1129 properties1130 .try_push(Property {1131 key: key.clone(),1132 value: uri1133 .into_bytes()1134 .try_into()1135 .map_err(|_| "token uri is too long")?,1136 })1137 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;11381139 let create_item_data = CreateItemData::<T> {1140 users: users.clone(),1141 properties,1142 };1143 data.push(create_item_data);1144 }11451146 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1147 .map_err(dispatch_to_evm::<T>)?;1148 Ok(true)1149 }11501151 1152 1153 1154 1155 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1156 fn mint_cross(1157 &mut self,1158 caller: Caller,1159 to: eth::CrossAddress,1160 properties: Vec<eth::Property>,1161 ) -> Result<U256> {1162 let token_id = <TokensMinted<T>>::get(self.id)1163 .checked_add(1)1164 .ok_or("item id overflow")?;11651166 let to = to.into_sub_cross_account::<T>()?;11671168 let properties = properties1169 .into_iter()1170 .map(eth::Property::try_into)1171 .collect::<Result<Vec<_>>>()?1172 .try_into()1173 .map_err(|_| Error::Revert("too many properties".to_string()))?;11741175 let caller = T::CrossAccountId::from_eth(caller);11761177 let budget = self1178 .recorder1179 .weight_calls_budget(<StructureWeight<T>>::find_parent());11801181 let users = [(to, 1)]1182 .into_iter()1183 .collect::<BTreeMap<_, _>>()1184 .try_into()1185 .unwrap();1186 <Pallet<T>>::create_item(1187 self,1188 &caller,1189 CreateItemData::<T> { users, properties },1190 &budget,1191 )1192 .map_err(dispatch_to_evm::<T>)?;11931194 Ok(token_id.into())1195 }11961197 1198 1199 1200 fn token_contract_address(&self, token: U256) -> Result<Address> {1201 Ok(T::EvmTokenAddressMapping::token_to_address(1202 self.id,1203 token.try_into().map_err(|_| "token id overflow")?,1204 ))1205 }12061207 1208 fn collection_helper_address(&self) -> Result<Address> {1209 Ok(T::ContractAddress::get())1210 }1211}12121213#[solidity_interface(1214 name = UniqueRefungible,1215 is(1216 ERC721,1217 ERC721Enumerable,1218 ERC721UniqueExtensions,1219 ERC721UniqueMintable,1220 ERC721Burnable,1221 ERC721Metadata(if(this.flags.erc721metadata)),1222 Collection(via(common_mut returns CollectionHandle<T>)),1223 TokenProperties,1224 ),1225 enum(derive(PreDispatch)),1226)]1227impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}122812291230generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1231generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);12321233impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1234where1235 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1236{1237 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1238 fn call(1239 self,1240 handle: &mut impl PrecompileHandle,1241 ) -> Option<pallet_common::erc::PrecompileResult> {1242 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1243 }1244}