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 Error as CommonError,36 erc::{CommonEvmHandler, CollectionCall, static_property::key},37 eth,38};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::{call, dispatch_to_evm};41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};42use sp_core::{H160, Get};43use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};44use up_data_structs::{45 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,46 PropertyKeyPermission, PropertyPermission, TokenId,47};4849use crate::{50 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,51 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,52};5354pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);555657#[solidity_interface(name = TokenProperties)]58impl<T: Config> RefungibleHandle<T> {59 60 61 62 63 64 65 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]66 #[solidity(hide)]67 fn set_token_property_permission(68 &mut self,69 caller: caller,70 key: string,71 is_mutable: bool,72 collection_admin: bool,73 token_owner: bool,74 ) -> Result<()> {75 let caller = T::CrossAccountId::from_eth(caller);76 <Pallet<T>>::set_token_property_permissions(77 self,78 &caller,79 vec![PropertyKeyPermission {80 key: <Vec<u8>>::from(key)81 .try_into()82 .map_err(|_| "too long key")?,83 permission: PropertyPermission {84 mutable: is_mutable,85 collection_admin,86 token_owner,87 },88 }],89 )90 .map_err(dispatch_to_evm::<T>)91 }9293 94 95 96 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]97 fn set_token_property_permissions(98 &mut self,99 caller: caller,100 permissions: Vec<eth::TokenPropertyPermission>,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;104105 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)106 .map_err(dispatch_to_evm::<T>)107 }108109 110 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {111 let perms = <Pallet<T>>::token_property_permission(self.id);112 Ok(perms113 .into_iter()114 .map(eth::TokenPropertyPermission::from)115 .collect())116 }117118 119 120 121 122 123 #[solidity(hide)]124 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]125 fn set_property(126 &mut self,127 caller: caller,128 token_id: uint256,129 key: string,130 value: bytes,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134 let key = <Vec<u8>>::from(key)135 .try_into()136 .map_err(|_| "key too long")?;137 let value = value.0.try_into().map_err(|_| "value too long")?;138139 let nesting_budget = self140 .recorder141 .weight_calls_budget(<StructureWeight<T>>::find_parent());142143 <Pallet<T>>::set_token_property(144 self,145 &caller,146 TokenId(token_id),147 Property { key, value },148 &nesting_budget,149 )150 .map_err(dispatch_to_evm::<T>)151 }152153 154 155 156 157 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]158 fn set_properties(159 &mut self,160 caller: caller,161 token_id: uint256,162 properties: Vec<eth::Property>,163 ) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;166167 let nesting_budget = self168 .recorder169 .weight_calls_budget(<StructureWeight<T>>::find_parent());170171 let properties = properties172 .into_iter()173 .map(eth::Property::try_into)174 .collect::<Result<Vec<_>>>()?;175176 <Pallet<T>>::set_token_properties(177 self,178 &caller,179 TokenId(token_id),180 properties.into_iter(),181 false,182 &nesting_budget,183 )184 .map_err(dispatch_to_evm::<T>)185 }186187 188 189 190 191 #[solidity(hide)]192 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]193 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {194 let caller = T::CrossAccountId::from_eth(caller);195 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;196 let key = <Vec<u8>>::from(key)197 .try_into()198 .map_err(|_| "key too long")?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)205 .map_err(dispatch_to_evm::<T>)206 }207208 209 210 211 212 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]213 fn delete_properties(214 &mut self,215 token_id: uint256,216 caller: caller,217 keys: Vec<string>,218 ) -> Result<()> {219 let caller = T::CrossAccountId::from_eth(caller);220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let keys = keys222 .into_iter()223 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))224 .collect::<Result<Vec<_>>>()?;225226 let nesting_budget = self227 .recorder228 .weight_calls_budget(<StructureWeight<T>>::find_parent());229230 <Pallet<T>>::delete_token_properties(231 self,232 &caller,233 TokenId(token_id),234 keys.into_iter(),235 &nesting_budget,236 )237 .map_err(dispatch_to_evm::<T>)238 }239240 241 242 243 244 245 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {246 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;247 let key = <Vec<u8>>::from(key)248 .try_into()249 .map_err(|_| "key too long")?;250251 let props = <TokenProperties<T>>::get((self.id, token_id));252 let prop = props.get(&key).ok_or("key not found")?;253254 Ok(prop.to_vec().into())255 }256}257258#[derive(ToLog)]259pub enum ERC721Events {260 261 262 263 Transfer {264 #[indexed]265 from: address,266 #[indexed]267 to: address,268 #[indexed]269 token_id: uint256,270 },271 272 Approval {273 #[indexed]274 owner: address,275 #[indexed]276 approved: address,277 #[indexed]278 token_id: uint256,279 },280 281 #[allow(dead_code)]282 ApprovalForAll {283 #[indexed]284 owner: address,285 #[indexed]286 operator: address,287 approved: bool,288 },289}290291#[derive(ToLog)]292pub enum ERC721UniqueMintableEvents {293 294 #[allow(dead_code)]295 MintingFinished {},296}297298#[solidity_interface(name = ERC721Metadata)]299impl<T: Config> RefungibleHandle<T>300where301 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,302{303 304 305 #[solidity(hide, rename_selector = "name")]306 fn name_proxy(&self) -> Result<string> {307 self.name()308 }309310 311 312 #[solidity(hide, rename_selector = "symbol")]313 fn symbol_proxy(&self) -> Result<string> {314 self.symbol()315 }316317 318 319 320 321 322 323 324 325 326 #[solidity(rename_selector = "tokenURI")]327 fn token_uri(&self, token_id: uint256) -> Result<string> {328 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;329330 match get_token_property(self, token_id_u32, &key::url()).as_deref() {331 Err(_) | Ok("") => (),332 Ok(url) => {333 return Ok(url.into());334 }335 };336337 let base_uri =338 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())339 .map(BoundedVec::into_inner)340 .map(string::from_utf8)341 .transpose()342 .map_err(|e| {343 Error::Revert(alloc::format!(344 "Can not convert value \"baseURI\" to string with error \"{}\"",345 e346 ))347 })?;348349 let base_uri = match base_uri.as_deref() {350 None | Some("") => {351 return Ok("".into());352 }353 Some(base_uri) => base_uri.into(),354 };355356 Ok(357 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {358 Err(_) | Ok("") => base_uri,359 Ok(suffix) => base_uri + suffix,360 },361 )362 }363}364365366367#[solidity_interface(name = ERC721Enumerable)]368impl<T: Config> RefungibleHandle<T> {369 370 371 372 373 fn token_by_index(&self, index: uint256) -> Result<uint256> {374 Ok(index)375 }376377 378 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {379 380 Err("not implemented".into())381 }382383 384 385 386 fn total_supply(&self) -> Result<uint256> {387 self.consume_store_reads(1)?;388 Ok(<Pallet<T>>::total_supply(self).into())389 }390}391392393394#[solidity_interface(name = ERC721, events(ERC721Events))]395impl<T: Config> RefungibleHandle<T> {396 397 398 399 400 401 fn balance_of(&self, owner: address) -> Result<uint256> {402 self.consume_store_reads(1)?;403 let owner = T::CrossAccountId::from_eth(owner);404 let balance = <AccountBalance<T>>::get((self.id, owner));405 Ok(balance.into())406 }407408 409 410 411 412 413 414 415 fn owner_of(&self, token_id: uint256) -> Result<address> {416 self.consume_store_reads(2)?;417 let token = token_id.try_into()?;418 let owner = <Pallet<T>>::token_owner(self.id, token);419 Ok(owner420 .map(|address| *address.as_eth())421 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))422 }423424 425 fn safe_transfer_from_with_data(426 &mut self,427 _from: address,428 _to: address,429 _token_id: uint256,430 _data: bytes,431 ) -> Result<void> {432 433 Err("not implemented".into())434 }435436 437 fn safe_transfer_from(438 &mut self,439 _from: address,440 _to: address,441 _token_id: uint256,442 ) -> Result<void> {443 444 Err("not implemented".into())445 }446447 448 449 450 451 452 453 454 455 456 457 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]458 fn transfer_from(459 &mut self,460 caller: caller,461 from: address,462 to: address,463 token_id: uint256,464 ) -> Result<void> {465 let caller = T::CrossAccountId::from_eth(caller);466 let from = T::CrossAccountId::from_eth(from);467 let to = T::CrossAccountId::from_eth(to);468 let token = token_id.try_into()?;469 let budget = self470 .recorder471 .weight_calls_budget(<StructureWeight<T>>::find_parent());472473 let balance = balance(&self, token, &from)?;474 ensure_single_owner(&self, token, balance)?;475476 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)477 .map_err(dispatch_to_evm::<T>)?;478479 Ok(())480 }481482 483 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {484 Err("not implemented".into())485 }486487 488 489 490 491 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]492 fn set_approval_for_all(493 &mut self,494 caller: caller,495 operator: address,496 approved: bool,497 ) -> Result<void> {498 let caller = T::CrossAccountId::from_eth(caller);499 let operator = T::CrossAccountId::from_eth(operator);500501 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)502 .map_err(dispatch_to_evm::<T>)?;503 Ok(())504 }505506 507 fn get_approved(&self, _token_id: uint256) -> Result<address> {508 509 Err("not implemented".into())510 }511512 513 #[weight(<SelfWeightOf<T>>::allowance_for_all())]514 fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {515 let owner = T::CrossAccountId::from_eth(owner);516 let operator = T::CrossAccountId::from_eth(operator);517518 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))519 }520521 522 fn collection_helper_address(&self) -> Result<address> {523 Ok(T::ContractAddress::get())524 }525}526527528pub fn balance<T: Config>(529 collection: &RefungibleHandle<T>,530 token: TokenId,531 owner: &T::CrossAccountId,532) -> Result<u128> {533 collection.consume_store_reads(1)?;534 let balance = <Balance<T>>::get((collection.id, token, &owner));535 Ok(balance)536}537538539pub fn ensure_single_owner<T: Config>(540 collection: &RefungibleHandle<T>,541 token: TokenId,542 owner_balance: u128,543) -> Result<()> {544 collection.consume_store_reads(1)?;545 let total_supply = <TotalSupply<T>>::get((collection.id, token));546547 if owner_balance == 0 {548 return Err(dispatch_to_evm::<T>(549 <CommonError<T>>::MustBeTokenOwner.into(),550 ));551 }552553 if total_supply != owner_balance {554 return Err("token has multiple owners".into());555 }556 Ok(())557}558559560#[solidity_interface(name = ERC721Burnable)]561impl<T: Config> RefungibleHandle<T> {562 563 564 565 566 #[weight(<SelfWeightOf<T>>::burn_item_fully())]567 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {568 let caller = T::CrossAccountId::from_eth(caller);569 let token = token_id.try_into()?;570571 let balance = balance(&self, token, &caller)?;572 ensure_single_owner(&self, token, balance)?;573574 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;575 Ok(())576 }577}578579580#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]581impl<T: Config> RefungibleHandle<T> {582 fn minting_finished(&self) -> Result<bool> {583 Ok(false)584 }585586 587 588 589 #[weight(<SelfWeightOf<T>>::create_item())]590 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {591 let token_id: uint256 = <TokensMinted<T>>::get(self.id)592 .checked_add(1)593 .ok_or("item id overflow")?594 .into();595 self.mint_check_id(caller, to, token_id)?;596 Ok(token_id)597 }598599 600 601 602 603 604 #[solidity(hide, rename_selector = "mint")]605 #[weight(<SelfWeightOf<T>>::create_item())]606 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {607 let caller = T::CrossAccountId::from_eth(caller);608 let to = T::CrossAccountId::from_eth(to);609 let token_id: u32 = token_id.try_into()?;610 let budget = self611 .recorder612 .weight_calls_budget(<StructureWeight<T>>::find_parent());613614 if <TokensMinted<T>>::get(self.id)615 .checked_add(1)616 .ok_or("item id overflow")?617 != token_id618 {619 return Err("item id should be next".into());620 }621622 let users = [(to.clone(), 1)]623 .into_iter()624 .collect::<BTreeMap<_, _>>()625 .try_into()626 .unwrap();627 <Pallet<T>>::create_item(628 self,629 &caller,630 CreateItemData::<T> {631 users,632 properties: CollectionPropertiesVec::default(),633 },634 &budget,635 )636 .map_err(dispatch_to_evm::<T>)?;637638 Ok(true)639 }640641 642 643 644 645 #[solidity(rename_selector = "mintWithTokenURI")]646 #[weight(<SelfWeightOf<T>>::create_item())]647 fn mint_with_token_uri(648 &mut self,649 caller: caller,650 to: address,651 token_uri: string,652 ) -> Result<uint256> {653 let token_id: uint256 = <TokensMinted<T>>::get(self.id)654 .checked_add(1)655 .ok_or("item id overflow")?656 .into();657 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;658 Ok(token_id)659 }660661 662 663 664 665 666 667 #[solidity(hide, rename_selector = "mintWithTokenURI")]668 #[weight(<SelfWeightOf<T>>::create_item())]669 fn mint_with_token_uri_check_id(670 &mut self,671 caller: caller,672 to: address,673 token_id: uint256,674 token_uri: string,675 ) -> Result<bool> {676 let key = key::url();677 let permission = get_token_permission::<T>(self.id, &key)?;678 if !permission.collection_admin {679 return Err("Operation is not allowed".into());680 }681682 let caller = T::CrossAccountId::from_eth(caller);683 let to = T::CrossAccountId::from_eth(to);684 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;685 let budget = self686 .recorder687 .weight_calls_budget(<StructureWeight<T>>::find_parent());688689 if <TokensMinted<T>>::get(self.id)690 .checked_add(1)691 .ok_or("item id overflow")?692 != token_id693 {694 return Err("item id should be next".into());695 }696697 let mut properties = CollectionPropertiesVec::default();698 properties699 .try_push(Property {700 key,701 value: token_uri702 .into_bytes()703 .try_into()704 .map_err(|_| "token uri is too long")?,705 })706 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;707708 let users = [(to.clone(), 1)]709 .into_iter()710 .collect::<BTreeMap<_, _>>()711 .try_into()712 .unwrap();713 <Pallet<T>>::create_item(714 self,715 &caller,716 CreateItemData::<T> { users, properties },717 &budget,718 )719 .map_err(dispatch_to_evm::<T>)?;720 Ok(true)721 }722723 724 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {725 Err("not implementable".into())726 }727}728729fn get_token_property<T: Config>(730 collection: &CollectionHandle<T>,731 token_id: u32,732 key: &up_data_structs::PropertyKey,733) -> Result<string> {734 collection.consume_store_reads(1)?;735 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))736 .map_err(|_| Error::Revert("Token properties not found".into()))?;737 if let Some(property) = properties.get(key) {738 return Ok(string::from_utf8_lossy(property).into());739 }740741 Err("Property tokenURI not found".into())742}743744fn get_token_permission<T: Config>(745 collection_id: CollectionId,746 key: &PropertyKey,747) -> Result<PropertyPermission> {748 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)749 .map_err(|_| Error::Revert("No permissions for collection".into()))?;750 let a = token_property_permissions751 .get(key)752 .map(Clone::clone)753 .ok_or_else(|| {754 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();755 Error::Revert(alloc::format!("No permission for key {}", key))756 })?;757 Ok(a)758}759760761#[solidity_interface(name = ERC721UniqueExtensions)]762impl<T: Config> RefungibleHandle<T>763where764 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,765{766 767 fn name(&self) -> Result<string> {768 Ok(decode_utf16(self.name.iter().copied())769 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))770 .collect::<string>())771 }772773 774 fn symbol(&self) -> Result<string> {775 Ok(string::from_utf8_lossy(&self.token_prefix).into())776 }777778 779 fn description(&self) -> Result<string> {780 Ok(decode_utf16(self.description.iter().copied())781 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))782 .collect::<string>())783 }784785 786 787 788 fn cross_owner_of(&self, token_id: uint256) -> Result<eth::CrossAddress> {789 Self::token_owner(&self, token_id.try_into()?)790 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))791 .ok_or(Error::Revert("key too large".into()))792 }793794 795 796 797 798 799 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<eth::Property>> {800 let keys = keys801 .into_iter()802 .map(|key| {803 <Vec<u8>>::from(key)804 .try_into()805 .map_err(|_| Error::Revert("key too large".into()))806 })807 .collect::<Result<Vec<_>>>()?;808809 <Self as CommonCollectionOperations<T>>::token_properties(810 &self,811 token_id.try_into()?,812 if keys.is_empty() { None } else { Some(keys) },813 )814 .into_iter()815 .map(eth::Property::try_from)816 .collect::<Result<Vec<_>>>()817 }818 819 820 821 822 823 824 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]825 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {826 let caller = T::CrossAccountId::from_eth(caller);827 let to = T::CrossAccountId::from_eth(to);828 let token = token_id.try_into()?;829 let budget = self830 .recorder831 .weight_calls_budget(<StructureWeight<T>>::find_parent());832833 let balance = balance(self, token, &caller)?;834 ensure_single_owner(self, token, balance)?;835836 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)837 .map_err(dispatch_to_evm::<T>)?;838 Ok(())839 }840841 842 843 844 845 846 847 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]848 fn transfer_cross(849 &mut self,850 caller: caller,851 to: eth::CrossAddress,852 token_id: uint256,853 ) -> Result<void> {854 let caller = T::CrossAccountId::from_eth(caller);855 let to = to.into_sub_cross_account::<T>()?;856 let token = token_id.try_into()?;857 let budget = self858 .recorder859 .weight_calls_budget(<StructureWeight<T>>::find_parent());860861 let balance = balance(self, token, &caller)?;862 ensure_single_owner(self, token, balance)?;863864 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)865 .map_err(dispatch_to_evm::<T>)?;866 Ok(())867 }868869 870 871 872 873 874 875 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]876 fn transfer_from_cross(877 &mut self,878 caller: caller,879 from: eth::CrossAddress,880 to: eth::CrossAddress,881 token_id: uint256,882 ) -> Result<void> {883 let caller = T::CrossAccountId::from_eth(caller);884 let from = from.into_sub_cross_account::<T>()?;885 let to = to.into_sub_cross_account::<T>()?;886 let token_id = token_id.try_into()?;887 let budget = self888 .recorder889 .weight_calls_budget(<StructureWeight<T>>::find_parent());890891 let balance = balance(self, token_id, &from)?;892 ensure_single_owner(self, token_id, balance)?;893894 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)895 .map_err(dispatch_to_evm::<T>)?;896 Ok(())897 }898899 900 901 902 903 904 905 906 #[solidity(hide)]907 #[weight(<SelfWeightOf<T>>::burn_from())]908 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {909 let caller = T::CrossAccountId::from_eth(caller);910 let from = T::CrossAccountId::from_eth(from);911 let token = token_id.try_into()?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let balance = balance(self, token, &from)?;917 ensure_single_owner(self, token, balance)?;918919 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)920 .map_err(dispatch_to_evm::<T>)?;921 Ok(())922 }923924 925 926 927 928 929 930 931 #[weight(<SelfWeightOf<T>>::burn_from())]932 fn burn_from_cross(933 &mut self,934 caller: caller,935 from: eth::CrossAddress,936 token_id: uint256,937 ) -> Result<void> {938 let caller = T::CrossAccountId::from_eth(caller);939 let from = from.into_sub_cross_account::<T>()?;940 let token = token_id.try_into()?;941 let budget = self942 .recorder943 .weight_calls_budget(<StructureWeight<T>>::find_parent());944945 let balance = balance(self, token, &from)?;946 ensure_single_owner(self, token, balance)?;947948 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)949 .map_err(dispatch_to_evm::<T>)?;950 Ok(())951 }952953 954 fn next_token_id(&self) -> Result<uint256> {955 self.consume_store_reads(1)?;956 Ok(<TokensMinted<T>>::get(self.id)957 .checked_add(1)958 .ok_or("item id overflow")?959 .into())960 }961962 963 964 965 966 967 #[solidity(hide)]968 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]969 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {970 let caller = T::CrossAccountId::from_eth(caller);971 let to = T::CrossAccountId::from_eth(to);972 let mut expected_index = <TokensMinted<T>>::get(self.id)973 .checked_add(1)974 .ok_or("item id overflow")?;975 let budget = self976 .recorder977 .weight_calls_budget(<StructureWeight<T>>::find_parent());978979 let total_tokens = token_ids.len();980 for id in token_ids.into_iter() {981 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;982 if id != expected_index {983 return Err("item id should be next".into());984 }985 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;986 }987 let users = [(to.clone(), 1)]988 .into_iter()989 .collect::<BTreeMap<_, _>>()990 .try_into()991 .unwrap();992 let create_item_data = CreateItemData::<T> {993 users,994 properties: CollectionPropertiesVec::default(),995 };996 let data = (0..total_tokens)997 .map(|_| create_item_data.clone())998 .collect();9991000 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1001 .map_err(dispatch_to_evm::<T>)?;1002 Ok(true)1003 }10041005 1006 1007 1008 1009 1010 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1011 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]1012 fn mint_bulk_with_token_uri(1013 &mut self,1014 caller: caller,1015 to: address,1016 tokens: Vec<(uint256, string)>,1017 ) -> Result<bool> {1018 let key = key::url();1019 let caller = T::CrossAccountId::from_eth(caller);1020 let to = T::CrossAccountId::from_eth(to);1021 let mut expected_index = <TokensMinted<T>>::get(self.id)1022 .checked_add(1)1023 .ok_or("item id overflow")?;1024 let budget = self1025 .recorder1026 .weight_calls_budget(<StructureWeight<T>>::find_parent());10271028 let mut data = Vec::with_capacity(tokens.len());1029 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1030 .into_iter()1031 .collect::<BTreeMap<_, _>>()1032 .try_into()1033 .unwrap();1034 for (id, token_uri) in tokens {1035 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1036 if id != expected_index {1037 return Err("item id should be next".into());1038 }1039 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10401041 let mut properties = CollectionPropertiesVec::default();1042 properties1043 .try_push(Property {1044 key: key.clone(),1045 value: token_uri1046 .into_bytes()1047 .try_into()1048 .map_err(|_| "token uri is too long")?,1049 })1050 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10511052 let create_item_data = CreateItemData::<T> {1053 users: users.clone(),1054 properties,1055 };1056 data.push(create_item_data);1057 }10581059 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1060 .map_err(dispatch_to_evm::<T>)?;1061 Ok(true)1062 }10631064 1065 1066 1067 1068 #[weight(<SelfWeightOf<T>>::create_item())]1069 fn mint_cross(1070 &mut self,1071 caller: caller,1072 to: eth::CrossAddress,1073 properties: Vec<eth::Property>,1074 ) -> Result<uint256> {1075 let token_id = <TokensMinted<T>>::get(self.id)1076 .checked_add(1)1077 .ok_or("item id overflow")?;10781079 let to = to.into_sub_cross_account::<T>()?;10801081 let properties = properties1082 .into_iter()1083 .map(eth::Property::try_into)1084 .collect::<Result<Vec<_>>>()?1085 .try_into()1086 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10871088 let caller = T::CrossAccountId::from_eth(caller);10891090 let budget = self1091 .recorder1092 .weight_calls_budget(<StructureWeight<T>>::find_parent());10931094 let users = [(to, 1)]1095 .into_iter()1096 .collect::<BTreeMap<_, _>>()1097 .try_into()1098 .unwrap();1099 <Pallet<T>>::create_item(1100 self,1101 &caller,1102 CreateItemData::<T> { users, properties },1103 &budget,1104 )1105 .map_err(dispatch_to_evm::<T>)?;11061107 Ok(token_id.into())1108 }11091110 1111 1112 1113 fn token_contract_address(&self, token: uint256) -> Result<address> {1114 Ok(T::EvmTokenAddressMapping::token_to_address(1115 self.id,1116 token.try_into().map_err(|_| "token id overflow")?,1117 ))1118 }1119}11201121#[solidity_interface(1122 name = UniqueRefungible,1123 is(1124 ERC721,1125 ERC721Enumerable,1126 ERC721UniqueExtensions,1127 ERC721UniqueMintable,1128 ERC721Burnable,1129 ERC721Metadata(if(this.flags.erc721metadata)),1130 Collection(via(common_mut returns CollectionHandle<T>)),1131 TokenProperties,1132 )1133)]1134impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}113511361137generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1138generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11391140impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1141where1142 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1143{1144 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1145 fn call(1146 self,1147 handle: &mut impl PrecompileHandle,1148 ) -> Option<pallet_common::erc::PrecompileResult> {1149 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1150 }1151}