12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};45use sp_core::Get;4647use crate::{48 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,49 SelfWeightOf, weights::WeightInfo, TokenProperties,50};515253#[solidity_interface(name = TokenProperties)]54impl<T: Config> NonfungibleHandle<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_property_permission(71 self,72 &caller,73 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 #[solidity(hide)]93 fn set_property(94 &mut self,95 caller: caller,96 token_id: uint256,97 key: string,98 value: bytes,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too long")?;105 let value = value.0.try_into().map_err(|_| "value too long")?;106107 let nesting_budget = self108 .recorder109 .weight_calls_budget(<StructureWeight<T>>::find_parent());110111 <Pallet<T>>::set_token_property(112 self,113 &caller,114 TokenId(token_id),115 Property { key, value },116 &nesting_budget,117 )118 .map_err(dispatch_to_evm::<T>)119 }120121 122 123 124 125 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]126 fn set_properties(127 &mut self,128 caller: caller,129 token_id: uint256,130 properties: Vec<PropertyStruct>,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134135 let nesting_budget = self136 .recorder137 .weight_calls_budget(<StructureWeight<T>>::find_parent());138139 let properties = properties140 .into_iter()141 .map(|PropertyStruct { key, value }| {142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too large")?;145146 let value = value.0.try_into().map_err(|_| "value too large")?;147148 Ok(Property { key, value })149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::set_token_properties(153 self,154 &caller,155 TokenId(token_id),156 properties.into_iter(),157 <Pallet<T>>::token_exists(&self, TokenId(token_id)),158 &nesting_budget,159 )160 .map_err(dispatch_to_evm::<T>)161 }162163 164 165 166 167 #[solidity(hide)]168 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> 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")?;174175 let nesting_budget = self176 .recorder177 .weight_calls_budget(<StructureWeight<T>>::find_parent());178179 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)180 .map_err(dispatch_to_evm::<T>)181 }182183 184 185 186 187 fn delete_properties(188 &mut self,189 token_id: uint256,190 caller: caller,191 keys: Vec<string>,192 ) -> Result<()> {193 let caller = T::CrossAccountId::from_eth(caller);194 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;195 let keys = keys196 .into_iter()197 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))198 .collect::<Result<Vec<_>>>()?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_properties(205 self,206 &caller,207 TokenId(token_id),208 keys.into_iter(),209 &nesting_budget,210 )211 .map_err(dispatch_to_evm::<T>)212 }213214 215 216 217 218 219 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let key = <Vec<u8>>::from(key)222 .try_into()223 .map_err(|_| "key too long")?;224225 let props = <TokenProperties<T>>::get((self.id, token_id));226 let prop = props.get(&key).ok_or("key not found")?;227228 Ok(prop.to_vec().into())229 }230}231232#[derive(ToLog)]233pub enum ERC721Events {234 235 236 237 238 239 Transfer {240 #[indexed]241 from: address,242 #[indexed]243 to: address,244 #[indexed]245 token_id: uint256,246 },247 248 249 250 251 Approval {252 #[indexed]253 owner: address,254 #[indexed]255 approved: address,256 #[indexed]257 token_id: uint256,258 },259 260 261 #[allow(dead_code)]262 ApprovalForAll {263 #[indexed]264 owner: address,265 #[indexed]266 operator: address,267 approved: bool,268 },269}270271#[derive(ToLog)]272pub enum ERC721UniqueMintableEvents {273 #[allow(dead_code)]274 MintingFinished {},275}276277278279#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]280impl<T: Config> NonfungibleHandle<T>281where282 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,283{284 285 286 #[solidity(hide, rename_selector = "name")]287 fn name_proxy(&self) -> Result<string> {288 self.name()289 }290291 292 293 #[solidity(hide, rename_selector = "symbol")]294 fn symbol_proxy(&self) -> Result<string> {295 self.symbol()296 }297298 299 300 301 302 303 304 305 306 307 #[solidity(rename_selector = "tokenURI")]308 fn token_uri(&self, token_id: uint256) -> Result<string> {309 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;310311 match get_token_property(self, token_id_u32, &key::url()).as_deref() {312 Err(_) | Ok("") => (),313 Ok(url) => {314 return Ok(url.into());315 }316 };317318 let base_uri =319 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())320 .map(BoundedVec::into_inner)321 .map(string::from_utf8)322 .transpose()323 .map_err(|e| {324 Error::Revert(alloc::format!(325 "Can not convert value \"baseURI\" to string with error \"{}\"",326 e327 ))328 })?;329330 let base_uri = match base_uri.as_deref() {331 None | Some("") => {332 return Ok("".into());333 }334 Some(base_uri) => base_uri.into(),335 };336337 Ok(338 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {339 Err(_) | Ok("") => base_uri,340 Ok(suffix) => base_uri + suffix,341 },342 )343 }344}345346347348#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]349impl<T: Config> NonfungibleHandle<T> {350 351 352 353 354 fn token_by_index(&self, index: uint256) -> Result<uint256> {355 Ok(index)356 }357358 359 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {360 361 Err("not implemented".into())362 }363364 365 366 367 fn total_supply(&self) -> Result<uint256> {368 self.consume_store_reads(1)?;369 Ok(<Pallet<T>>::total_supply(self).into())370 }371}372373374375#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]376impl<T: Config> NonfungibleHandle<T> {377 378 379 380 381 382 fn balance_of(&self, owner: address) -> Result<uint256> {383 self.consume_store_reads(1)?;384 let owner = T::CrossAccountId::from_eth(owner);385 let balance = <AccountBalance<T>>::get((self.id, owner));386 Ok(balance.into())387 }388 389 390 391 392 393 fn owner_of(&self, token_id: uint256) -> Result<address> {394 self.consume_store_reads(1)?;395 let token: TokenId = token_id.try_into()?;396 Ok(*<TokenData<T>>::get((self.id, token))397 .ok_or("token not found")?398 .owner399 .as_eth())400 }401 402 #[solidity(rename_selector = "safeTransferFrom")]403 fn safe_transfer_from_with_data(404 &mut self,405 _from: address,406 _to: address,407 _token_id: uint256,408 _data: bytes,409 ) -> Result<void> {410 411 Err("not implemented".into())412 }413 414 fn safe_transfer_from(415 &mut self,416 _from: address,417 _to: address,418 _token_id: uint256,419 ) -> Result<void> {420 421 Err("not implemented".into())422 }423424 425 426 427 428 429 430 431 432 433 #[weight(<SelfWeightOf<T>>::transfer_from())]434 fn transfer_from(435 &mut self,436 caller: caller,437 from: address,438 to: address,439 token_id: uint256,440 ) -> Result<void> {441 let caller = T::CrossAccountId::from_eth(caller);442 let from = T::CrossAccountId::from_eth(from);443 let to = T::CrossAccountId::from_eth(to);444 let token = token_id.try_into()?;445 let budget = self446 .recorder447 .weight_calls_budget(<StructureWeight<T>>::find_parent());448449 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)450 .map_err(dispatch_to_evm::<T>)?;451 Ok(())452 }453454 455 456 457 458 459 460 #[weight(<SelfWeightOf<T>>::approve())]461 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {462 let caller = T::CrossAccountId::from_eth(caller);463 let approved = T::CrossAccountId::from_eth(approved);464 let token = token_id.try_into()?;465466 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))467 .map_err(dispatch_to_evm::<T>)?;468 Ok(())469 }470471 472 fn set_approval_for_all(473 &mut self,474 _caller: caller,475 _operator: address,476 _approved: bool,477 ) -> Result<void> {478 479 Err("not implemented".into())480 }481482 483 fn get_approved(&self, _token_id: uint256) -> Result<address> {484 485 Err("not implemented".into())486 }487488 489 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {490 491 Err("not implemented".into())492 }493494 495 fn collection_helper_address(&self) -> Result<address> {496 Ok(T::ContractAddress::get())497 }498}499500501#[solidity_interface(name = ERC721Burnable)]502impl<T: Config> NonfungibleHandle<T> {503 504 505 506 507 #[weight(<SelfWeightOf<T>>::burn_item())]508 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {509 let caller = T::CrossAccountId::from_eth(caller);510 let token = token_id.try_into()?;511512 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;513 Ok(())514 }515}516517518#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]519impl<T: Config> NonfungibleHandle<T> {520 fn minting_finished(&self) -> Result<bool> {521 Ok(false)522 }523524 525 526 527 #[weight(<SelfWeightOf<T>>::create_item())]528 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {529 let token_id: uint256 = <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 .into();533 self.mint_check_id(caller, to, token_id)?;534 Ok(token_id)535 }536537 538 539 540 541 542 #[solidity(hide, rename_selector = "mint")]543 #[weight(<SelfWeightOf<T>>::create_item())]544 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {545 let caller = T::CrossAccountId::from_eth(caller);546 let to = T::CrossAccountId::from_eth(to);547 let token_id: u32 = token_id.try_into()?;548 let budget = self549 .recorder550 .weight_calls_budget(<StructureWeight<T>>::find_parent());551552 if <TokensMinted<T>>::get(self.id)553 .checked_add(1)554 .ok_or("item id overflow")?555 != token_id556 {557 return Err("item id should be next".into());558 }559560 <Pallet<T>>::create_item(561 self,562 &caller,563 CreateItemData::<T> {564 properties: BoundedVec::default(),565 owner: to,566 },567 &budget,568 )569 .map_err(dispatch_to_evm::<T>)?;570571 Ok(true)572 }573574 575 576 577 578 #[solidity(rename_selector = "mintWithTokenURI")]579 #[weight(<SelfWeightOf<T>>::create_item())]580 fn mint_with_token_uri(581 &mut self,582 caller: caller,583 to: address,584 token_uri: string,585 ) -> Result<uint256> {586 let token_id: uint256 = <TokensMinted<T>>::get(self.id)587 .checked_add(1)588 .ok_or("item id overflow")?589 .into();590 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;591 Ok(token_id)592 }593594 595 596 597 598 599 600 #[solidity(hide, rename_selector = "mintWithTokenURI")]601 #[weight(<SelfWeightOf<T>>::create_item())]602 fn mint_with_token_uri_check_id(603 &mut self,604 caller: caller,605 to: address,606 token_id: uint256,607 token_uri: string,608 ) -> Result<bool> {609 let key = key::url();610 let permission = get_token_permission::<T>(self.id, &key)?;611 if !permission.collection_admin {612 return Err("Operation is not allowed".into());613 }614615 let caller = T::CrossAccountId::from_eth(caller);616 let to = T::CrossAccountId::from_eth(to);617 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;618 let budget = self619 .recorder620 .weight_calls_budget(<StructureWeight<T>>::find_parent());621622 if <TokensMinted<T>>::get(self.id)623 .checked_add(1)624 .ok_or("item id overflow")?625 != token_id626 {627 return Err("item id should be next".into());628 }629630 let mut properties = CollectionPropertiesVec::default();631 properties632 .try_push(Property {633 key,634 value: token_uri635 .into_bytes()636 .try_into()637 .map_err(|_| "token uri is too long")?,638 })639 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;640641 <Pallet<T>>::create_item(642 self,643 &caller,644 CreateItemData::<T> {645 properties,646 owner: to,647 },648 &budget,649 )650 .map_err(dispatch_to_evm::<T>)?;651 Ok(true)652 }653654 655 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {656 Err("not implementable".into())657 }658}659660fn get_token_property<T: Config>(661 collection: &CollectionHandle<T>,662 token_id: u32,663 key: &up_data_structs::PropertyKey,664) -> Result<string> {665 collection.consume_store_reads(1)?;666 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))667 .map_err(|_| Error::Revert("Token properties not found".into()))?;668 if let Some(property) = properties.get(key) {669 return Ok(string::from_utf8_lossy(property).into());670 }671672 Err("Property tokenURI not found".into())673}674675fn get_token_permission<T: Config>(676 collection_id: CollectionId,677 key: &PropertyKey,678) -> Result<PropertyPermission> {679 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)680 .map_err(|_| Error::Revert("No permissions for collection".into()))?;681 let a = token_property_permissions682 .get(key)683 .map(Clone::clone)684 .ok_or_else(|| {685 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();686 Error::Revert(alloc::format!("No permission for key {}", key))687 })?;688 Ok(a)689}690691692#[solidity_interface(name = ERC721UniqueExtensions)]693impl<T: Config> NonfungibleHandle<T>694where695 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,696{697 698 fn name(&self) -> Result<string> {699 Ok(decode_utf16(self.name.iter().copied())700 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))701 .collect::<string>())702 }703704 705 fn symbol(&self) -> Result<string> {706 Ok(string::from_utf8_lossy(&self.token_prefix).into())707 }708709 710 fn description(&self) -> Result<string> {711 Ok(decode_utf16(self.description.iter().copied())712 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))713 .collect::<string>())714 }715716 717 718 719 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {720 Self::token_owner(&self, token_id.try_into()?)721 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))722 .ok_or(Error::Revert("key too large".into()))723 }724725 726 727 728 729 730 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {731 let keys = keys732 .into_iter()733 .map(|key| {734 <Vec<u8>>::from(key)735 .try_into()736 .map_err(|_| Error::Revert("key too large".into()))737 })738 .collect::<Result<Vec<_>>>()?;739740 <Self as CommonCollectionOperations<T>>::token_properties(741 &self,742 token_id.try_into()?,743 if keys.is_empty() { None } else { Some(keys) },744 )745 .into_iter()746 .map(|p| {747 let key = string::from_utf8(p.key.to_vec())748 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;749 let value = bytes(p.value.to_vec());750 Ok(PropertyStruct { key, value })751 })752 .collect::<Result<Vec<_>>>()753 }754755 756 757 758 759 760 761 #[weight(<SelfWeightOf<T>>::approve())]762 fn approve_cross(763 &mut self,764 caller: caller,765 approved: EthCrossAccount,766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let approved = approved.into_sub_cross_account::<T>()?;770 let token = token_id.try_into()?;771772 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))773 .map_err(dispatch_to_evm::<T>)?;774 Ok(())775 }776777 778 779 780 781 782 #[weight(<SelfWeightOf<T>>::transfer())]783 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {784 let caller = T::CrossAccountId::from_eth(caller);785 let to = T::CrossAccountId::from_eth(to);786 let token = token_id.try_into()?;787 let budget = self788 .recorder789 .weight_calls_budget(<StructureWeight<T>>::find_parent());790791 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;792 Ok(())793 }794795 796 797 798 799 800 #[weight(<SelfWeightOf<T>>::transfer())]801 fn transfer_cross(802 &mut self,803 caller: caller,804 to: EthCrossAccount,805 token_id: uint256,806 ) -> Result<void> {807 let caller = T::CrossAccountId::from_eth(caller);808 let to = to.into_sub_cross_account::<T>()?;809 let token = token_id.try_into()?;810 let budget = self811 .recorder812 .weight_calls_budget(<StructureWeight<T>>::find_parent());813814 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;815 Ok(())816 }817818 819 820 821 822 823 824 #[weight(<SelfWeightOf<T>>::transfer())]825 fn transfer_from_cross(826 &mut self,827 caller: caller,828 from: EthCrossAccount,829 to: EthCrossAccount,830 token_id: uint256,831 ) -> Result<void> {832 let caller = T::CrossAccountId::from_eth(caller);833 let from = from.into_sub_cross_account::<T>()?;834 let to = to.into_sub_cross_account::<T>()?;835 let token_id = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)840 .map_err(dispatch_to_evm::<T>)?;841 Ok(())842 }843844 845 846 847 848 849 850 #[solidity(hide)]851 #[weight(<SelfWeightOf<T>>::burn_from())]852 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {853 let caller = T::CrossAccountId::from_eth(caller);854 let from = T::CrossAccountId::from_eth(from);855 let token = token_id.try_into()?;856 let budget = self857 .recorder858 .weight_calls_budget(<StructureWeight<T>>::find_parent());859860 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)861 .map_err(dispatch_to_evm::<T>)?;862 Ok(())863 }864865 866 867 868 869 870 871 #[weight(<SelfWeightOf<T>>::burn_from())]872 fn burn_from_cross(873 &mut self,874 caller: caller,875 from: EthCrossAccount,876 token_id: uint256,877 ) -> Result<void> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let token = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)886 .map_err(dispatch_to_evm::<T>)?;887 Ok(())888 }889890 891 fn next_token_id(&self) -> Result<uint256> {892 self.consume_store_reads(1)?;893 Ok(<TokensMinted<T>>::get(self.id)894 .checked_add(1)895 .ok_or("item id overflow")?896 .into())897 }898899 900 901 902 903 904 #[solidity(hide)]905 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]906 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {907 let caller = T::CrossAccountId::from_eth(caller);908 let to = T::CrossAccountId::from_eth(to);909 let mut expected_index = <TokensMinted<T>>::get(self.id)910 .checked_add(1)911 .ok_or("item id overflow")?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let total_tokens = token_ids.len();917 for id in token_ids.into_iter() {918 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;919 if id != expected_index {920 return Err("item id should be next".into());921 }922 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;923 }924 let data = (0..total_tokens)925 .map(|_| CreateItemData::<T> {926 properties: BoundedVec::default(),927 owner: to.clone(),928 })929 .collect();930931 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)932 .map_err(dispatch_to_evm::<T>)?;933 Ok(true)934 }935936 937 938 939 940 941 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]942 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]943 fn mint_bulk_with_token_uri(944 &mut self,945 caller: caller,946 to: address,947 tokens: Vec<(uint256, string)>,948 ) -> Result<bool> {949 let key = key::url();950 let caller = T::CrossAccountId::from_eth(caller);951 let to = T::CrossAccountId::from_eth(to);952 let mut expected_index = <TokensMinted<T>>::get(self.id)953 .checked_add(1)954 .ok_or("item id overflow")?;955 let budget = self956 .recorder957 .weight_calls_budget(<StructureWeight<T>>::find_parent());958959 let mut data = Vec::with_capacity(tokens.len());960 for (id, token_uri) in tokens {961 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;962 if id != expected_index {963 return Err("item id should be next".into());964 }965 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;966967 let mut properties = CollectionPropertiesVec::default();968 properties969 .try_push(Property {970 key: key.clone(),971 value: token_uri972 .into_bytes()973 .try_into()974 .map_err(|_| "token uri is too long")?,975 })976 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;977978 data.push(CreateItemData::<T> {979 properties,980 owner: to.clone(),981 });982 }983984 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)985 .map_err(dispatch_to_evm::<T>)?;986 Ok(true)987 }988}989990#[solidity_interface(991 name = UniqueNFT,992 is(993 ERC721,994 ERC721Enumerable,995 ERC721UniqueExtensions,996 ERC721UniqueMintable,997 ERC721Burnable,998 ERC721Metadata(if(this.flags.erc721metadata)),999 Collection(via(common_mut returns CollectionHandle<T>)),1000 TokenProperties,1001 )1002)]1003impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}100410051006generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1007generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10081009impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1010where1011 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1012{1013 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10141015 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1016 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1017 }1018}