12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::{U256, Get};4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 TokenProperties, SelfWeightOf, weights::WeightInfo,51};525354#[derive(ToLog)]55pub enum ERC721TokenEvent {56 57 TokenChanged {58 59 #[indexed]60 collection_id: Address,61 62 token_id: U256,63 },64}6566frontier_contract! {67 macro_rules! NonfungibleHandle_result {...}68 impl<T: Config> Contract for NonfungibleHandle<T> {...}69}707172#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]73impl<T: Config> NonfungibleHandle<T> {74 75 76 77 78 79 80 #[solidity(hide)]81 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]82 fn set_token_property_permission(83 &mut self,84 caller: Caller,85 key: String,86 is_mutable: bool,87 collection_admin: bool,88 token_owner: bool,89 ) -> Result<()> {90 let caller = T::CrossAccountId::from_eth(caller);91 <Pallet<T>>::set_token_property_permissions(92 self,93 &caller,94 vec![PropertyKeyPermission {95 key: <Vec<u8>>::from(key)96 .try_into()97 .map_err(|_| "too long key")?,98 permission: PropertyPermission {99 mutable: is_mutable,100 collection_admin,101 token_owner,102 },103 }],104 )105 .map_err(dispatch_to_evm::<T>)106 }107108 109 110 111 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]112 fn set_token_property_permissions(113 &mut self,114 caller: Caller,115 permissions: Vec<eth::TokenPropertyPermission>,116 ) -> Result<()> {117 let caller = T::CrossAccountId::from_eth(caller);118 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;119120 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)121 .map_err(dispatch_to_evm::<T>)122 }123124 125 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {126 let perms = <Pallet<T>>::token_property_permission(self.id);127 Ok(perms128 .into_iter()129 .map(eth::TokenPropertyPermission::from)130 .collect())131 }132133 134 135 136 137 138 #[solidity(hide)]139 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]140 fn set_property(141 &mut self,142 caller: Caller,143 token_id: U256,144 key: String,145 value: Bytes,146 ) -> Result<()> {147 let caller = T::CrossAccountId::from_eth(caller);148 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too long")?;152 let value = value.0.try_into().map_err(|_| "value too long")?;153154 let nesting_budget = self155 .recorder156 .weight_calls_budget(<StructureWeight<T>>::find_parent());157158 <Pallet<T>>::set_token_property(159 self,160 &caller,161 TokenId(token_id),162 Property { key, value },163 &nesting_budget,164 )165 .map_err(dispatch_to_evm::<T>)166 }167168 169 170 171 172 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]173 fn set_properties(174 &mut self,175 caller: Caller,176 token_id: U256,177 properties: Vec<eth::Property>,178 ) -> Result<()> {179 let caller = T::CrossAccountId::from_eth(caller);180 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;181182 let nesting_budget = self183 .recorder184 .weight_calls_budget(<StructureWeight<T>>::find_parent());185186 let properties = properties187 .into_iter()188 .map(eth::Property::try_into)189 .collect::<Result<Vec<_>>>()?;190191 <Pallet<T>>::set_token_properties(192 self,193 &caller,194 TokenId(token_id),195 properties.into_iter(),196 false,197 &nesting_budget,198 )199 .map_err(dispatch_to_evm::<T>)200 }201202 203 204 205 206 #[solidity(hide)]207 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]208 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {209 let caller = T::CrossAccountId::from_eth(caller);210 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;211 let key = <Vec<u8>>::from(key)212 .try_into()213 .map_err(|_| "key too long")?;214215 let nesting_budget = self216 .recorder217 .weight_calls_budget(<StructureWeight<T>>::find_parent());218219 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)220 .map_err(dispatch_to_evm::<T>)221 }222223 224 225 226 227 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]228 fn delete_properties(229 &mut self,230 token_id: U256,231 caller: Caller,232 keys: Vec<String>,233 ) -> Result<()> {234 let caller = T::CrossAccountId::from_eth(caller);235 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;236 let keys = keys237 .into_iter()238 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))239 .collect::<Result<Vec<_>>>()?;240241 let nesting_budget = self242 .recorder243 .weight_calls_budget(<StructureWeight<T>>::find_parent());244245 <Pallet<T>>::delete_token_properties(246 self,247 &caller,248 TokenId(token_id),249 keys.into_iter(),250 &nesting_budget,251 )252 .map_err(dispatch_to_evm::<T>)253 }254255 256 257 258 259 260 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {261 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;262 let key = <Vec<u8>>::from(key)263 .try_into()264 .map_err(|_| "key too long")?;265266 let props = <TokenProperties<T>>::get((self.id, token_id));267 let prop = props.get(&key).ok_or("key not found")?;268269 Ok(prop.to_vec().into())270 }271}272273#[derive(ToLog)]274pub enum ERC721Events {275 276 277 278 279 280 Transfer {281 #[indexed]282 from: Address,283 #[indexed]284 to: Address,285 #[indexed]286 token_id: U256,287 },288 289 290 291 292 Approval {293 #[indexed]294 owner: Address,295 #[indexed]296 approved: Address,297 #[indexed]298 token_id: U256,299 },300 301 302 #[allow(dead_code)]303 ApprovalForAll {304 #[indexed]305 owner: Address,306 #[indexed]307 operator: Address,308 approved: bool,309 },310}311312313314#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]315impl<T: Config> NonfungibleHandle<T>316where317 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,318{319 320 321 #[solidity(hide, rename_selector = "name")]322 fn name_proxy(&self) -> String {323 self.name()324 }325326 327 328 #[solidity(hide, rename_selector = "symbol")]329 fn symbol_proxy(&self) -> String {330 self.symbol()331 }332333 334 335 336 337 338 339 340 341 342 #[solidity(rename_selector = "tokenURI")]343 fn token_uri(&self, token_id: U256) -> Result<String> {344 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;345346 match get_token_property(self, token_id_u32, &key::url()).as_deref() {347 Err(_) | Ok("") => (),348 Ok(url) => {349 return Ok(url.into());350 }351 };352353 let base_uri =354 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())355 .map(BoundedVec::into_inner)356 .map(String::from_utf8)357 .transpose()358 .map_err(|e| {359 Error::Revert(alloc::format!(360 "Can not convert value \"baseURI\" to string with error \"{}\"",361 e362 ))363 })?;364365 let base_uri = match base_uri.as_deref() {366 None | Some("") => {367 return Ok("".into());368 }369 Some(base_uri) => base_uri.into(),370 };371372 Ok(373 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {374 Err(_) | Ok("") => base_uri,375 Ok(suffix) => base_uri + suffix,376 },377 )378 }379}380381382383#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]384impl<T: Config> NonfungibleHandle<T> {385 386 387 388 389 fn token_by_index(&self, index: U256) -> U256 {390 index391 }392393 394 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {395 396 Err("not implemented".into())397 }398399 400 401 402 fn total_supply(&self) -> Result<U256> {403 self.consume_store_reads(1)?;404 Ok(<Pallet<T>>::total_supply(self).into())405 }406}407408409410#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]411impl<T: Config> NonfungibleHandle<T> {412 413 414 415 416 417 fn balance_of(&self, owner: Address) -> Result<U256> {418 self.consume_store_reads(1)?;419 let owner = T::CrossAccountId::from_eth(owner);420 let balance = <AccountBalance<T>>::get((self.id, owner));421 Ok(balance.into())422 }423 424 425 426 427 428 fn owner_of(&self, token_id: U256) -> Result<Address> {429 self.consume_store_reads(1)?;430 let token: TokenId = token_id.try_into()?;431 Ok(*<TokenData<T>>::get((self.id, token))432 .ok_or("token not found")?433 .owner434 .as_eth())435 }436 437 #[solidity(rename_selector = "safeTransferFrom")]438 fn safe_transfer_from_with_data(439 &mut self,440 _from: Address,441 _to: Address,442 _token_id: U256,443 _data: Bytes,444 ) -> Result<()> {445 446 Err("not implemented".into())447 }448 449 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {450 451 Err("not implemented".into())452 }453454 455 456 457 458 459 460 461 462 463 #[weight(<SelfWeightOf<T>>::transfer_from())]464 fn transfer_from(465 &mut self,466 caller: Caller,467 from: Address,468 to: Address,469 token_id: U256,470 ) -> Result<()> {471 let caller = T::CrossAccountId::from_eth(caller);472 let from = T::CrossAccountId::from_eth(from);473 let to = T::CrossAccountId::from_eth(to);474 let token = token_id.try_into()?;475 let budget = self476 .recorder477 .weight_calls_budget(<StructureWeight<T>>::find_parent());478479 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)480 .map_err(dispatch_to_evm::<T>)?;481 Ok(())482 }483484 485 486 487 488 489 490 #[weight(<SelfWeightOf<T>>::approve())]491 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {492 let caller = T::CrossAccountId::from_eth(caller);493 let approved = T::CrossAccountId::from_eth(approved);494 let token = token_id.try_into()?;495496 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))497 .map_err(dispatch_to_evm::<T>)?;498 Ok(())499 }500501 502 503 504 505 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]506 fn set_approval_for_all(507 &mut self,508 caller: Caller,509 operator: Address,510 approved: bool,511 ) -> Result<()> {512 let caller = T::CrossAccountId::from_eth(caller);513 let operator = T::CrossAccountId::from_eth(operator);514515 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)516 .map_err(dispatch_to_evm::<T>)?;517 Ok(())518 }519520 521 fn get_approved(&self, _token_id: U256) -> Result<Address> {522 523 Err("not implemented".into())524 }525526 527 #[weight(<SelfWeightOf<T>>::allowance_for_all())]528 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {529 let owner = T::CrossAccountId::from_eth(owner);530 let operator = T::CrossAccountId::from_eth(operator);531532 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))533 }534}535536537#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]538impl<T: Config> NonfungibleHandle<T> {539 540 541 542 543 #[weight(<SelfWeightOf<T>>::burn_item())]544 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {545 let caller = T::CrossAccountId::from_eth(caller);546 let token = token_id.try_into()?;547548 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;549 Ok(())550 }551}552553554#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556 557 558 559 #[weight(<SelfWeightOf<T>>::create_item())]560 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {561 let token_id: U256 = <TokensMinted<T>>::get(self.id)562 .checked_add(1)563 .ok_or("item id overflow")?564 .into();565 self.mint_check_id(caller, to, token_id)?;566 Ok(token_id)567 }568569 570 571 572 573 574 #[solidity(hide, rename_selector = "mint")]575 #[weight(<SelfWeightOf<T>>::create_item())]576 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {577 let caller = T::CrossAccountId::from_eth(caller);578 let to = T::CrossAccountId::from_eth(to);579 let token_id: u32 = token_id.try_into()?;580 let budget = self581 .recorder582 .weight_calls_budget(<StructureWeight<T>>::find_parent());583584 if <TokensMinted<T>>::get(self.id)585 .checked_add(1)586 .ok_or("item id overflow")?587 != token_id588 {589 return Err("item id should be next".into());590 }591592 <Pallet<T>>::create_item(593 self,594 &caller,595 CreateItemData::<T> {596 properties: BoundedVec::default(),597 owner: to,598 },599 &budget,600 )601 .map_err(dispatch_to_evm::<T>)?;602603 Ok(true)604 }605606 607 608 609 610 #[solidity(rename_selector = "mintWithTokenURI")]611 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]612 fn mint_with_token_uri(613 &mut self,614 caller: Caller,615 to: Address,616 token_uri: String,617 ) -> Result<U256> {618 let token_id: U256 = <TokensMinted<T>>::get(self.id)619 .checked_add(1)620 .ok_or("item id overflow")?621 .into();622 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;623 Ok(token_id)624 }625626 627 628 629 630 631 632 #[solidity(hide, rename_selector = "mintWithTokenURI")]633 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]634 fn mint_with_token_uri_check_id(635 &mut self,636 caller: Caller,637 to: Address,638 token_id: U256,639 token_uri: String,640 ) -> Result<bool> {641 let key = key::url();642 let permission = get_token_permission::<T>(self.id, &key)?;643 if !permission.collection_admin {644 return Err("Operation is not allowed".into());645 }646647 let caller = T::CrossAccountId::from_eth(caller);648 let to = T::CrossAccountId::from_eth(to);649 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;650 let budget = self651 .recorder652 .weight_calls_budget(<StructureWeight<T>>::find_parent());653654 if <TokensMinted<T>>::get(self.id)655 .checked_add(1)656 .ok_or("item id overflow")?657 != token_id658 {659 return Err("item id should be next".into());660 }661662 let mut properties = CollectionPropertiesVec::default();663 properties664 .try_push(Property {665 key,666 value: token_uri667 .into_bytes()668 .try_into()669 .map_err(|_| "token uri is too long")?,670 })671 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;672673 <Pallet<T>>::create_item(674 self,675 &caller,676 CreateItemData::<T> {677 properties,678 owner: to,679 },680 &budget,681 )682 .map_err(dispatch_to_evm::<T>)?;683 Ok(true)684 }685}686687fn get_token_property<T: Config>(688 collection: &CollectionHandle<T>,689 token_id: u32,690 key: &up_data_structs::PropertyKey,691) -> Result<String> {692 collection.consume_store_reads(1)?;693 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))694 .map_err(|_| Error::Revert("Token properties not found".into()))?;695 if let Some(property) = properties.get(key) {696 return Ok(String::from_utf8_lossy(property).into());697 }698699 Err("Property tokenURI not found".into())700}701702fn get_token_permission<T: Config>(703 collection_id: CollectionId,704 key: &PropertyKey,705) -> Result<PropertyPermission> {706 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)707 .map_err(|_| Error::Revert("No permissions for collection".into()))?;708 let a = token_property_permissions709 .get(key)710 .map(Clone::clone)711 .ok_or_else(|| {712 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();713 Error::Revert(alloc::format!("No permission for key {}", key))714 })?;715 Ok(a)716}717718719#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]720impl<T: Config> NonfungibleHandle<T>721where722 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,723{724 725 fn name(&self) -> String {726 decode_utf16(self.name.iter().copied())727 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))728 .collect::<String>()729 }730731 732 fn symbol(&self) -> String {733 String::from_utf8_lossy(&self.token_prefix).into()734 }735736 737 fn description(&self) -> String {738 decode_utf16(self.description.iter().copied())739 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))740 .collect::<String>()741 }742743 744 745 746 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {747 Self::token_owner(&self, token_id.try_into()?)748 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))749 .map_err(|_| Error::Revert("token not found".into()))750 }751752 753 754 755 756 757 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {758 let keys = keys759 .into_iter()760 .map(|key| {761 <Vec<u8>>::from(key)762 .try_into()763 .map_err(|_| Error::Revert("key too large".into()))764 })765 .collect::<Result<Vec<_>>>()?;766767 <Self as CommonCollectionOperations<T>>::token_properties(768 &self,769 token_id.try_into()?,770 if keys.is_empty() { None } else { Some(keys) },771 )772 .into_iter()773 .map(eth::Property::try_from)774 .collect::<Result<Vec<_>>>()775 }776777 778 779 780 781 782 783 #[weight(<SelfWeightOf<T>>::approve())]784 fn approve_cross(785 &mut self,786 caller: Caller,787 approved: eth::CrossAddress,788 token_id: U256,789 ) -> Result<()> {790 let caller = T::CrossAccountId::from_eth(caller);791 let approved = approved.into_sub_cross_account::<T>()?;792 let token = token_id.try_into()?;793794 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))795 .map_err(dispatch_to_evm::<T>)?;796 Ok(())797 }798799 800 801 802 803 804 #[weight(<SelfWeightOf<T>>::transfer())]805 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {806 let caller = T::CrossAccountId::from_eth(caller);807 let to = T::CrossAccountId::from_eth(to);808 let token = token_id.try_into()?;809 let budget = self810 .recorder811 .weight_calls_budget(<StructureWeight<T>>::find_parent());812813 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;814 Ok(())815 }816817 818 819 820 821 822 #[weight(<SelfWeightOf<T>>::transfer())]823 fn transfer_cross(824 &mut self,825 caller: Caller,826 to: eth::CrossAddress,827 token_id: U256,828 ) -> Result<()> {829 let caller = T::CrossAccountId::from_eth(caller);830 let to = to.into_sub_cross_account::<T>()?;831 let token = token_id.try_into()?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;837 Ok(())838 }839840 841 842 843 844 845 846 #[weight(<SelfWeightOf<T>>::transfer())]847 fn transfer_from_cross(848 &mut self,849 caller: Caller,850 from: eth::CrossAddress,851 to: eth::CrossAddress,852 token_id: U256,853 ) -> Result<()> {854 let caller = T::CrossAccountId::from_eth(caller);855 let from = from.into_sub_cross_account::<T>()?;856 let to = to.into_sub_cross_account::<T>()?;857 let token_id = token_id.try_into()?;858 let budget = self859 .recorder860 .weight_calls_budget(<StructureWeight<T>>::find_parent());861 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)862 .map_err(dispatch_to_evm::<T>)?;863 Ok(())864 }865866 867 868 869 870 871 872 #[solidity(hide)]873 #[weight(<SelfWeightOf<T>>::burn_from())]874 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {875 let caller = T::CrossAccountId::from_eth(caller);876 let from = T::CrossAccountId::from_eth(from);877 let token = token_id.try_into()?;878 let budget = self879 .recorder880 .weight_calls_budget(<StructureWeight<T>>::find_parent());881882 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)883 .map_err(dispatch_to_evm::<T>)?;884 Ok(())885 }886887 888 889 890 891 892 893 #[weight(<SelfWeightOf<T>>::burn_from())]894 fn burn_from_cross(895 &mut self,896 caller: Caller,897 from: eth::CrossAddress,898 token_id: U256,899 ) -> Result<()> {900 let caller = T::CrossAccountId::from_eth(caller);901 let from = from.into_sub_cross_account::<T>()?;902 let token = token_id.try_into()?;903 let budget = self904 .recorder905 .weight_calls_budget(<StructureWeight<T>>::find_parent());906907 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)908 .map_err(dispatch_to_evm::<T>)?;909 Ok(())910 }911912 913 fn next_token_id(&self) -> Result<U256> {914 self.consume_store_reads(1)?;915 Ok(<TokensMinted<T>>::get(self.id)916 .checked_add(1)917 .ok_or("item id overflow")?918 .into())919 }920921 922 923 924 925 926 #[solidity(hide)]927 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]928 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {929 let caller = T::CrossAccountId::from_eth(caller);930 let to = T::CrossAccountId::from_eth(to);931 let mut expected_index = <TokensMinted<T>>::get(self.id)932 .checked_add(1)933 .ok_or("item id overflow")?;934 let budget = self935 .recorder936 .weight_calls_budget(<StructureWeight<T>>::find_parent());937938 let total_tokens = token_ids.len();939 for id in token_ids.into_iter() {940 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;941 if id != expected_index {942 return Err("item id should be next".into());943 }944 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;945 }946 let data = (0..total_tokens)947 .map(|_| CreateItemData::<T> {948 properties: BoundedVec::default(),949 owner: to.clone(),950 })951 .collect();952953 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)954 .map_err(dispatch_to_evm::<T>)?;955 Ok(true)956 }957958 959 960 961 962 963 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]964 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]965 fn mint_bulk_with_token_uri(966 &mut self,967 caller: Caller,968 to: Address,969 tokens: Vec<TokenUri>,970 ) -> Result<bool> {971 let key = key::url();972 let caller = T::CrossAccountId::from_eth(caller);973 let to = T::CrossAccountId::from_eth(to);974 let mut expected_index = <TokensMinted<T>>::get(self.id)975 .checked_add(1)976 .ok_or("item id overflow")?;977 let budget = self978 .recorder979 .weight_calls_budget(<StructureWeight<T>>::find_parent());980981 let mut data = Vec::with_capacity(tokens.len());982 for TokenUri { id, uri } in tokens {983 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;984 if id != expected_index {985 return Err("item id should be next".into());986 }987 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;988989 let mut properties = CollectionPropertiesVec::default();990 properties991 .try_push(Property {992 key: key.clone(),993 value: uri994 .into_bytes()995 .try_into()996 .map_err(|_| "token uri is too long")?,997 })998 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;9991000 data.push(CreateItemData::<T> {1001 properties,1002 owner: to.clone(),1003 });1004 }10051006 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1007 .map_err(dispatch_to_evm::<T>)?;1008 Ok(true)1009 }10101011 1012 1013 1014 1015 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1016 fn mint_cross(1017 &mut self,1018 caller: Caller,1019 to: eth::CrossAddress,1020 properties: Vec<eth::Property>,1021 ) -> Result<U256> {1022 let token_id = <TokensMinted<T>>::get(self.id)1023 .checked_add(1)1024 .ok_or("item id overflow")?;10251026 let to = to.into_sub_cross_account::<T>()?;10271028 let properties = properties1029 .into_iter()1030 .map(eth::Property::try_into)1031 .collect::<Result<Vec<_>>>()?1032 .try_into()1033 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10341035 let caller = T::CrossAccountId::from_eth(caller);10361037 let budget = self1038 .recorder1039 .weight_calls_budget(<StructureWeight<T>>::find_parent());10401041 <Pallet<T>>::create_item(1042 self,1043 &caller,1044 CreateItemData::<T> {1045 properties,1046 owner: to,1047 },1048 &budget,1049 )1050 .map_err(dispatch_to_evm::<T>)?;10511052 Ok(token_id.into())1053 }10541055 1056 fn collection_helper_address(&self) -> Address {1057 T::ContractAddress::get()1058 }1059}10601061#[solidity_interface(1062 name = UniqueNFT,1063 is(1064 ERC721,1065 ERC721Enumerable,1066 ERC721UniqueExtensions,1067 ERC721UniqueMintable,1068 ERC721Burnable,1069 ERC721Metadata(if(this.flags.erc721metadata)),1070 Collection(via(common_mut returns CollectionHandle<T>)),1071 TokenProperties,1072 ),1073 enum(derive(PreDispatch)),1074)]1075impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}107610771078generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1079generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10801081impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1082where1083 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1084{1085 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10861087 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1088 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1089 }1090}