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::BoundedVec;32use pallet_common::{33 erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34 eth::{self, TokenUri},35 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39 call, dispatch_to_evm,40 execution::{Error, PreDispatch, Result},41 frontier_contract, SubstrateRecorder,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47 budget::Budget, CollectionId, CollectionPropertiesVec, Property, PropertyKey,48 PropertyKeyPermission, PropertyPermission, TokenId,49};5051use crate::{52 common::{mint_with_props_weight, CommonWeights},53 weights::WeightInfo,54 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, SelfWeightOf, TokenData,55 TokenProperties, TokensMinted,56};575859#[derive(ToLog)]60pub enum ERC721TokenEvent {61 62 TokenChanged {63 64 #[indexed]65 token_id: U256,66 },67}686970#[derive(AbiCoder, Default, Debug)]71pub struct MintTokenData {72 73 pub owner: eth::CrossAddress,74 75 pub properties: Vec<eth::Property>,76}7778frontier_contract! {79 macro_rules! NonfungibleHandle_result {...}80 impl<T: Config> Contract for NonfungibleHandle<T> {...}81}8283fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {84 recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())85}868788#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]89impl<T: Config> NonfungibleHandle<T> {90 91 92 93 94 95 96 #[solidity(hide)]97 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]98 fn set_token_property_permission(99 &mut self,100 caller: Caller,101 key: String,102 is_mutable: bool,103 collection_admin: bool,104 token_owner: bool,105 ) -> Result<()> {106 let caller = T::CrossAccountId::from_eth(caller);107 <Pallet<T>>::set_token_property_permissions(108 self,109 &caller,110 vec![PropertyKeyPermission {111 key: <Vec<u8>>::from(key)112 .try_into()113 .map_err(|_| "too long key")?,114 permission: PropertyPermission {115 mutable: is_mutable,116 collection_admin,117 token_owner,118 },119 }],120 )121 .map_err(dispatch_to_evm::<T>)122 }123124 125 126 127 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]128 fn set_token_property_permissions(129 &mut self,130 caller: Caller,131 permissions: Vec<eth::TokenPropertyPermission>,132 ) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;135136 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)137 .map_err(dispatch_to_evm::<T>)138 }139140 141 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {142 let perms = <Pallet<T>>::token_property_permission(self.id);143 Ok(perms144 .into_iter()145 .map(eth::TokenPropertyPermission::from)146 .collect())147 }148149 150 151 152 153 154 #[solidity(hide)]155 #[weight(<CommonWeights<T>>::set_token_properties(1))]156 fn set_property(157 &mut self,158 caller: Caller,159 token_id: U256,160 key: String,161 value: Bytes,162 ) -> Result<()> {163 let caller = T::CrossAccountId::from_eth(caller);164 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too long")?;168 let value = value.0.try_into().map_err(|_| "value too long")?;169170 <Pallet<T>>::set_token_property(171 self,172 &caller,173 TokenId(token_id),174 Property { key, value },175 &nesting_budget(&self.recorder),176 )177 .map_err(dispatch_to_evm::<T>)178 }179180 181 182 183 184 #[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]185 fn set_properties(186 &mut self,187 caller: Caller,188 token_id: U256,189 properties: Vec<eth::Property>,190 ) -> Result<()> {191 let caller = T::CrossAccountId::from_eth(caller);192 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193194 let properties = properties195 .into_iter()196 .map(eth::Property::try_into)197 .collect::<Result<Vec<_>>>()?;198199 <Pallet<T>>::set_token_properties(200 self,201 &caller,202 TokenId(token_id),203 properties.into_iter(),204 &nesting_budget(&self.recorder),205 )206 .map_err(dispatch_to_evm::<T>)207 }208209 210 211 212 213 #[solidity(hide)]214 #[weight(<CommonWeights<T>>::delete_token_properties(1))]215 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {216 let caller = T::CrossAccountId::from_eth(caller);217 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;218 let key = <Vec<u8>>::from(key)219 .try_into()220 .map_err(|_| "key too long")?;221222 <Pallet<T>>::delete_token_property(223 self,224 &caller,225 TokenId(token_id),226 key,227 &nesting_budget(&self.recorder),228 )229 .map_err(dispatch_to_evm::<T>)230 }231232 233 234 235 236 #[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]237 fn delete_properties(238 &mut self,239 token_id: U256,240 caller: Caller,241 keys: Vec<String>,242 ) -> Result<()> {243 let caller = T::CrossAccountId::from_eth(caller);244 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;245 let keys = keys246 .into_iter()247 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))248 .collect::<Result<Vec<_>>>()?;249250 <Pallet<T>>::delete_token_properties(251 self,252 &caller,253 TokenId(token_id),254 keys.into_iter(),255 &nesting_budget(&self.recorder),256 )257 .map_err(dispatch_to_evm::<T>)258 }259260 261 262 263 264 265 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {266 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;267 let key = <Vec<u8>>::from(key)268 .try_into()269 .map_err(|_| "key too long")?;270271 let props =272 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;273 let prop = props.get(&key).ok_or("key not found")?;274275 Ok(prop.to_vec().into())276 }277}278279#[derive(ToLog)]280pub enum ERC721Events {281 282 283 284 285 286 Transfer {287 #[indexed]288 from: Address,289 #[indexed]290 to: Address,291 #[indexed]292 token_id: U256,293 },294 295 296 297 298 Approval {299 #[indexed]300 owner: Address,301 #[indexed]302 approved: Address,303 #[indexed]304 token_id: U256,305 },306 307 308 #[allow(dead_code)]309 ApprovalForAll {310 #[indexed]311 owner: Address,312 #[indexed]313 operator: Address,314 approved: bool,315 },316}317318319320#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]321impl<T: Config> NonfungibleHandle<T>322where323 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,324{325 326 327 #[solidity(hide, rename_selector = "name")]328 fn name_proxy(&self) -> String {329 self.name()330 }331332 333 334 #[solidity(hide, rename_selector = "symbol")]335 fn symbol_proxy(&self) -> String {336 self.symbol()337 }338339 340 341 342 343 344 345 346 347 348 #[solidity(rename_selector = "tokenURI")]349 fn token_uri(&self, token_id: U256) -> Result<String> {350 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;351352 match get_token_property(self, token_id_u32, &key::url()).as_deref() {353 Err(_) | Ok("") => (),354 Ok(url) => {355 return Ok(url.into());356 }357 };358359 let base_uri =360 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())361 .map(BoundedVec::into_inner)362 .map(String::from_utf8)363 .transpose()364 .map_err(|e| {365 Error::Revert(alloc::format!(366 "can not convert value \"baseURI\" to string with error \"{e}\""367 ))368 })?;369370 let base_uri = match base_uri.as_deref() {371 None | Some("") => {372 return Ok("".into());373 }374 Some(base_uri) => base_uri.into(),375 };376377 Ok(378 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {379 Err(_) | Ok("") => base_uri,380 Ok(suffix) => base_uri + suffix,381 },382 )383 }384}385386387388#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]389impl<T: Config> NonfungibleHandle<T> {390 391 392 393 394 fn token_by_index(&self, index: U256) -> U256 {395 index396 }397398 399 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {400 401 Err("not implemented".into())402 }403404 405 406 407 fn total_supply(&self) -> Result<U256> {408 self.consume_store_reads(1)?;409 Ok(<Pallet<T>>::total_supply(self).into())410 }411}412413414415#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]416impl<T: Config> NonfungibleHandle<T> {417 418 419 420 421 422 fn balance_of(&self, owner: Address) -> Result<U256> {423 self.consume_store_reads(1)?;424 let owner = T::CrossAccountId::from_eth(owner);425 let balance = <AccountBalance<T>>::get((self.id, owner));426 Ok(balance.into())427 }428 429 430 431 432 433 fn owner_of(&self, token_id: U256) -> Result<Address> {434 self.consume_store_reads(1)?;435 let token: TokenId = token_id.try_into()?;436 Ok(*<TokenData<T>>::get((self.id, token))437 .ok_or("token not found")?438 .owner439 .as_eth())440 }441 442 #[solidity(rename_selector = "safeTransferFrom")]443 fn safe_transfer_from_with_data(444 &mut self,445 _from: Address,446 _to: Address,447 _token_id: U256,448 _data: Bytes,449 ) -> Result<()> {450 451 Err("not implemented".into())452 }453 454 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {455 456 Err("not implemented".into())457 }458459 460 461 462 463 464 465 466 467 468 #[weight(<CommonWeights<T>>::transfer_from())]469 fn transfer_from(470 &mut self,471 caller: Caller,472 from: Address,473 to: Address,474 token_id: U256,475 ) -> Result<()> {476 let caller = T::CrossAccountId::from_eth(caller);477 let from = T::CrossAccountId::from_eth(from);478 let to = T::CrossAccountId::from_eth(to);479 let token = token_id.try_into()?;480481 <Pallet<T>>::transfer_from(482 self,483 &caller,484 &from,485 &to,486 token,487 &nesting_budget(&self.recorder),488 )489 .map_err(|e| dispatch_to_evm::<T>(e.error))?;490 Ok(())491 }492493 494 495 496 497 498 499 #[weight(<SelfWeightOf<T>>::approve())]500 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {501 let caller = T::CrossAccountId::from_eth(caller);502 let approved = T::CrossAccountId::from_eth(approved);503 let token = token_id.try_into()?;504505 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))506 .map_err(dispatch_to_evm::<T>)?;507 Ok(())508 }509510 511 512 513 514 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]515 fn set_approval_for_all(516 &mut self,517 caller: Caller,518 operator: Address,519 approved: bool,520 ) -> Result<()> {521 let caller = T::CrossAccountId::from_eth(caller);522 let operator = T::CrossAccountId::from_eth(operator);523524 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)525 .map_err(dispatch_to_evm::<T>)?;526 Ok(())527 }528529 530 531 532 533 fn get_approved(&self, token_id: U256) -> Result<Address> {534 let token_id = token_id.try_into()?;535 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;536 Ok(if let Some(operator) = operator {537 *operator.as_eth()538 } else {539 Address::zero()540 })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}552553554#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556 557 558 559 560 #[weight(<SelfWeightOf<T>>::burn_item())]561 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {562 let caller = T::CrossAccountId::from_eth(caller);563 let token = token_id.try_into()?;564565 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;566 Ok(())567 }568}569570571#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]572impl<T: Config> NonfungibleHandle<T> {573 574 575 576 #[weight(<SelfWeightOf<T>>::create_item())]577 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {578 let token_id: U256 = <TokensMinted<T>>::get(self.id)579 .checked_add(1)580 .ok_or("item id overflow")?581 .into();582 self.mint_check_id(caller, to, token_id)?;583 Ok(token_id)584 }585586 587 588 589 590 591 #[solidity(hide, rename_selector = "mint")]592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {594 let caller = T::CrossAccountId::from_eth(caller);595 let to = T::CrossAccountId::from_eth(to);596 let token_id: u32 = token_id.try_into()?;597598 if <TokensMinted<T>>::get(self.id)599 .checked_add(1)600 .ok_or("item id overflow")?601 != token_id602 {603 return Err("item id should be next".into());604 }605606 <Pallet<T>>::create_item(607 self,608 &caller,609 CreateItemData::<T> {610 properties: BoundedVec::default(),611 owner: to,612 },613 &nesting_budget(&self.recorder),614 )615 .map_err(dispatch_to_evm::<T>)?;616617 Ok(true)618 }619620 621 622 623 624 #[solidity(rename_selector = "mintWithTokenURI")]625 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]626 fn mint_with_token_uri(627 &mut self,628 caller: Caller,629 to: Address,630 token_uri: String,631 ) -> Result<U256> {632 let token_id: U256 = <TokensMinted<T>>::get(self.id)633 .checked_add(1)634 .ok_or("item id overflow")?635 .into();636 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;637 Ok(token_id)638 }639640 641 642 643 644 645 646 #[solidity(hide, rename_selector = "mintWithTokenURI")]647 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]648 fn mint_with_token_uri_check_id(649 &mut self,650 caller: Caller,651 to: Address,652 token_id: U256,653 token_uri: String,654 ) -> Result<bool> {655 let key = key::url();656 let permission = get_token_permission::<T>(self.id, &key)?;657 if !permission.collection_admin {658 return Err("operation is not allowed".into());659 }660661 let caller = T::CrossAccountId::from_eth(caller);662 let to = T::CrossAccountId::from_eth(to);663 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;664665 if <TokensMinted<T>>::get(self.id)666 .checked_add(1)667 .ok_or("item id overflow")?668 != token_id669 {670 return Err("item id should be next".into());671 }672673 let mut properties = CollectionPropertiesVec::default();674 properties675 .try_push(Property {676 key,677 value: token_uri678 .into_bytes()679 .try_into()680 .map_err(|_| "token uri is too long")?,681 })682 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;683684 <Pallet<T>>::create_item(685 self,686 &caller,687 CreateItemData::<T> {688 properties,689 owner: to,690 },691 &nesting_budget(&self.recorder),692 )693 .map_err(dispatch_to_evm::<T>)?;694 Ok(true)695 }696}697698fn get_token_property<T: Config>(699 collection: &CollectionHandle<T>,700 token_id: u32,701 key: &up_data_structs::PropertyKey,702) -> Result<String> {703 collection.consume_store_reads(1)?;704 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))705 .map_err(|_| Error::Revert("token properties not found".into()))?;706 if let Some(property) = properties.get(key) {707 return Ok(String::from_utf8_lossy(property).into());708 }709710 Err("property tokenURI not found".into())711}712713fn get_token_permission<T: Config>(714 collection_id: CollectionId,715 key: &PropertyKey,716) -> Result<PropertyPermission> {717 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)718 .map_err(|_| Error::Revert("no permissions for collection".into()))?;719 let a = token_property_permissions720 .get(key)721 .map(Clone::clone)722 .ok_or_else(|| {723 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();724 Error::Revert(alloc::format!("no permission for key {key}"))725 })?;726 Ok(a)727}728729730#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]731impl<T: Config> NonfungibleHandle<T>732where733 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,734{735 736 fn name(&self) -> String {737 decode_utf16(self.name.iter().copied())738 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))739 .collect::<String>()740 }741742 743 fn symbol(&self) -> String {744 String::from_utf8_lossy(&self.token_prefix).into()745 }746747 748 fn description(&self) -> String {749 decode_utf16(self.description.iter().copied())750 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))751 .collect::<String>()752 }753754 755 756 757 #[solidity(hide)]758 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {759 Self::owner_of_cross(self, token_id)760 }761762 763 764 765 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {766 Self::token_owner(self, token_id.try_into()?)767 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))768 .map_err(|_| Error::Revert("token not found".into()))769 }770771 772 773 774 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {775 self.consume_store_reads(1)?;776 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));777 Ok(balance.into())778 }779780 781 782 783 784 785 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {786 let keys = keys787 .into_iter()788 .map(|key| {789 <Vec<u8>>::from(key)790 .try_into()791 .map_err(|_| Error::Revert("key too large".into()))792 })793 .collect::<Result<Vec<_>>>()?;794795 <Self as CommonCollectionOperations<T>>::token_properties(796 self,797 token_id.try_into()?,798 if keys.is_empty() { None } else { Some(keys) },799 )800 .into_iter()801 .map(eth::Property::try_from)802 .collect::<Result<Vec<_>>>()803 }804805 806 807 808 809 810 811 #[weight(<SelfWeightOf<T>>::approve())]812 fn approve_cross(813 &mut self,814 caller: Caller,815 approved: eth::CrossAddress,816 token_id: U256,817 ) -> Result<()> {818 let caller = T::CrossAccountId::from_eth(caller);819 let approved = approved.into_sub_cross_account::<T>()?;820 let token = token_id.try_into()?;821822 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))823 .map_err(dispatch_to_evm::<T>)?;824 Ok(())825 }826827 828 829 830 831 832 #[weight(<CommonWeights<T>>::transfer())]833 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {834 let caller = T::CrossAccountId::from_eth(caller);835 let to = T::CrossAccountId::from_eth(to);836 let token = token_id.try_into()?;837838 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))839 .map_err(|e| dispatch_to_evm::<T>(e.error))?;840 Ok(())841 }842843 844 845 846 847 848 #[weight(<CommonWeights<T>>::transfer())]849 fn transfer_cross(850 &mut self,851 caller: Caller,852 to: eth::CrossAddress,853 token_id: U256,854 ) -> Result<()> {855 let caller = T::CrossAccountId::from_eth(caller);856 let to = to.into_sub_cross_account::<T>()?;857 let token = token_id.try_into()?;858859 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))860 .map_err(|e| dispatch_to_evm::<T>(e.error))?;861 Ok(())862 }863864 865 866 867 868 869 870 #[weight(<CommonWeights<T>>::transfer_from())]871 fn transfer_from_cross(872 &mut self,873 caller: Caller,874 from: eth::CrossAddress,875 to: eth::CrossAddress,876 token_id: U256,877 ) -> Result<()> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let to = to.into_sub_cross_account::<T>()?;881 let token_id = token_id.try_into()?;882883 Pallet::<T>::transfer_from(884 self,885 &caller,886 &from,887 &to,888 token_id,889 &nesting_budget(&self.recorder),890 )891 .map_err(|e| dispatch_to_evm::<T>(e.error))?;892 Ok(())893 }894895 896 897 898 899 900 901 #[solidity(hide)]902 #[weight(<SelfWeightOf<T>>::burn_from())]903 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {904 let caller = T::CrossAccountId::from_eth(caller);905 let from = T::CrossAccountId::from_eth(from);906 let token = token_id.try_into()?;907908 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))909 .map_err(dispatch_to_evm::<T>)?;910 Ok(())911 }912913 914 915 916 917 918 919 #[weight(<SelfWeightOf<T>>::burn_from())]920 fn burn_from_cross(921 &mut self,922 caller: Caller,923 from: eth::CrossAddress,924 token_id: U256,925 ) -> Result<()> {926 let caller = T::CrossAccountId::from_eth(caller);927 let from = from.into_sub_cross_account::<T>()?;928 let token = token_id.try_into()?;929930 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))931 .map_err(dispatch_to_evm::<T>)?;932 Ok(())933 }934935 936 fn next_token_id(&self) -> Result<U256> {937 self.consume_store_reads(1)?;938 Ok(<Pallet<T>>::next_token_id(self)939 .map_err(dispatch_to_evm::<T>)?940 .into())941 }942943 944 945 946 947 948 #[solidity(hide)]949 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]950 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {951 let caller = T::CrossAccountId::from_eth(caller);952 let to = T::CrossAccountId::from_eth(to);953 let mut expected_index = <TokensMinted<T>>::get(self.id)954 .checked_add(1)955 .ok_or("item id overflow")?;956957 let total_tokens = token_ids.len();958 for id in token_ids.into_iter() {959 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;960 if id != expected_index {961 return Err("item id should be next".into());962 }963 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;964 }965 let data = (0..total_tokens)966 .map(|_| CreateItemData::<T> {967 properties: BoundedVec::default(),968 owner: to.clone(),969 })970 .collect();971972 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))973 .map_err(dispatch_to_evm::<T>)?;974 Ok(true)975 }976977 978 979 #[weight(980 mint_with_props_weight::<T>(981 <SelfWeightOf<T>>::create_multiple_items_ex(data.len() as u32),982 data.iter().map(|d| d.properties.len() as u32),983 )984 )]985 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {986 let caller = T::CrossAccountId::from_eth(caller);987988 let mut create_nft_data = Vec::with_capacity(data.len());989 for MintTokenData { owner, properties } in data {990 let owner = owner.into_sub_cross_account::<T>()?;991 create_nft_data.push(CreateItemData::<T> {992 properties: properties993 .into_iter()994 .map(|property| property.try_into())995 .collect::<Result<Vec<_>>>()?996 .try_into()997 .map_err(|_| "too many properties")?,998 owner,999 });1000 }10011002 <Pallet<T>>::create_multiple_items(1003 self,1004 &caller,1005 create_nft_data,1006 &nesting_budget(&self.recorder),1007 )1008 .map_err(dispatch_to_evm::<T>)?;1009 Ok(true)1010 }10111012 1013 1014 1015 1016 1017 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1018 #[weight(1019 mint_with_props_weight::<T>(1020 <SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32),1021 tokens.iter().map(|_| 1),1022 )1023 )]1024 fn mint_bulk_with_token_uri(1025 &mut self,1026 caller: Caller,1027 to: Address,1028 tokens: Vec<TokenUri>,1029 ) -> Result<bool> {1030 let key = key::url();1031 let caller = T::CrossAccountId::from_eth(caller);1032 let to = T::CrossAccountId::from_eth(to);1033 let mut expected_index = <TokensMinted<T>>::get(self.id)1034 .checked_add(1)1035 .ok_or("item id overflow")?;10361037 let mut data = Vec::with_capacity(tokens.len());1038 for TokenUri { id, uri } in tokens {1039 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1040 if id != expected_index {1041 return Err("item id should be next".into());1042 }1043 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10441045 let mut properties = CollectionPropertiesVec::default();1046 properties1047 .try_push(Property {1048 key: key.clone(),1049 value: uri1050 .into_bytes()1051 .try_into()1052 .map_err(|_| "token uri is too long")?,1053 })1054 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10551056 data.push(CreateItemData::<T> {1057 properties,1058 owner: to.clone(),1059 });1060 }10611062 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1063 .map_err(dispatch_to_evm::<T>)?;1064 Ok(true)1065 }10661067 1068 1069 1070 1071 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [properties.len() as u32].into_iter()))]1072 fn mint_cross(1073 &mut self,1074 caller: Caller,1075 to: eth::CrossAddress,1076 properties: Vec<eth::Property>,1077 ) -> Result<U256> {1078 let token_id = <TokensMinted<T>>::get(self.id)1079 .checked_add(1)1080 .ok_or("item id overflow")?;10811082 let to = to.into_sub_cross_account::<T>()?;10831084 let properties = properties1085 .into_iter()1086 .map(eth::Property::try_into)1087 .collect::<Result<Vec<_>>>()?1088 .try_into()1089 .map_err(|_| Error::Revert("too many properties".to_string()))?;10901091 let caller = T::CrossAccountId::from_eth(caller);10921093 <Pallet<T>>::create_item(1094 self,1095 &caller,1096 CreateItemData::<T> {1097 properties,1098 owner: to,1099 },1100 &nesting_budget(&self.recorder),1101 )1102 .map_err(dispatch_to_evm::<T>)?;11031104 Ok(token_id.into())1105 }11061107 1108 fn collection_helper_address(&self) -> Address {1109 T::ContractAddress::get()1110 }1111}11121113#[solidity_interface(1114 name = UniqueNFT,1115 is(1116 ERC721,1117 ERC721Enumerable,1118 ERC721UniqueExtensions,1119 ERC721UniqueMintable,1120 ERC721Burnable,1121 ERC721Metadata(if(this.flags.erc721metadata)),1122 Collection(via(common_mut returns CollectionHandle<T>)),1123 TokenProperties,1124 ),1125 enum(derive(PreDispatch)),1126)]1127impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}112811291130generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1131generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11321133impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1134where1135 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1136{1137 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11381139 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1140 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1141 }1142}