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::CommonWeights, weights::WeightInfo, AccountBalance, Config, CreateItemData,53 NonfungibleHandle, Pallet, SelfWeightOf, TokenData, TokenProperties, TokensMinted,54};555657#[derive(ToLog)]58pub enum ERC721TokenEvent {59 60 TokenChanged {61 62 #[indexed]63 token_id: U256,64 },65}666768#[derive(AbiCoder, Default, Debug)]69pub struct MintTokenData {70 71 pub owner: eth::CrossAddress,72 73 pub properties: Vec<eth::Property>,74}7576frontier_contract! {77 macro_rules! NonfungibleHandle_result {...}78 impl<T: Config> Contract for NonfungibleHandle<T> {...}79}8081fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {82 recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())83}848586#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]87impl<T: Config> NonfungibleHandle<T> {88 89 90 91 92 93 94 #[solidity(hide)]95 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]96 fn set_token_property_permission(97 &mut self,98 caller: Caller,99 key: String,100 is_mutable: bool,101 collection_admin: bool,102 token_owner: bool,103 ) -> Result<()> {104 let caller = T::CrossAccountId::from_eth(caller);105 <Pallet<T>>::set_token_property_permissions(106 self,107 &caller,108 vec![PropertyKeyPermission {109 key: <Vec<u8>>::from(key)110 .try_into()111 .map_err(|_| "too long key")?,112 permission: PropertyPermission {113 mutable: is_mutable,114 collection_admin,115 token_owner,116 },117 }],118 )119 .map_err(dispatch_to_evm::<T>)120 }121122 123 124 125 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]126 fn set_token_property_permissions(127 &mut self,128 caller: Caller,129 permissions: Vec<eth::TokenPropertyPermission>,130 ) -> Result<()> {131 let caller = T::CrossAccountId::from_eth(caller);132 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;133134 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)135 .map_err(dispatch_to_evm::<T>)136 }137138 139 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {140 let perms = <Pallet<T>>::token_property_permission(self.id);141 Ok(perms142 .into_iter()143 .map(eth::TokenPropertyPermission::from)144 .collect())145 }146147 148 149 150 151 152 #[solidity(hide)]153 #[weight(<CommonWeights<T>>::set_token_properties(1))]154 fn set_property(155 &mut self,156 caller: Caller,157 token_id: U256,158 key: String,159 value: Bytes,160 ) -> Result<()> {161 let caller = T::CrossAccountId::from_eth(caller);162 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;163 let key = <Vec<u8>>::from(key)164 .try_into()165 .map_err(|_| "key too long")?;166 let value = value.0.try_into().map_err(|_| "value too long")?;167168 <Pallet<T>>::set_token_property(169 self,170 &caller,171 TokenId(token_id),172 Property { key, value },173 &nesting_budget(&self.recorder),174 )175 .map_err(dispatch_to_evm::<T>)176 }177178 179 180 181 182 #[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]183 fn set_properties(184 &mut self,185 caller: Caller,186 token_id: U256,187 properties: Vec<eth::Property>,188 ) -> Result<()> {189 let caller = T::CrossAccountId::from_eth(caller);190 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;191192 let properties = properties193 .into_iter()194 .map(eth::Property::try_into)195 .collect::<Result<Vec<_>>>()?;196197 <Pallet<T>>::set_token_properties(198 self,199 &caller,200 TokenId(token_id),201 properties.into_iter(),202 &nesting_budget(&self.recorder),203 )204 .map_err(dispatch_to_evm::<T>)205 }206207 208 209 210 211 #[solidity(hide)]212 #[weight(<CommonWeights<T>>::delete_token_properties(1))]213 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {214 let caller = T::CrossAccountId::from_eth(caller);215 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;216 let key = <Vec<u8>>::from(key)217 .try_into()218 .map_err(|_| "key too long")?;219220 <Pallet<T>>::delete_token_property(221 self,222 &caller,223 TokenId(token_id),224 key,225 &nesting_budget(&self.recorder),226 )227 .map_err(dispatch_to_evm::<T>)228 }229230 231 232 233 234 #[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]235 fn delete_properties(236 &mut self,237 token_id: U256,238 caller: Caller,239 keys: Vec<String>,240 ) -> Result<()> {241 let caller = T::CrossAccountId::from_eth(caller);242 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;243 let keys = keys244 .into_iter()245 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))246 .collect::<Result<Vec<_>>>()?;247248 <Pallet<T>>::delete_token_properties(249 self,250 &caller,251 TokenId(token_id),252 keys.into_iter(),253 &nesting_budget(&self.recorder),254 )255 .map_err(dispatch_to_evm::<T>)256 }257258 259 260 261 262 263 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {264 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;265 let key = <Vec<u8>>::from(key)266 .try_into()267 .map_err(|_| "key too long")?;268269 let props =270 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;271 let prop = props.get(&key).ok_or("key not found")?;272273 Ok(prop.to_vec().into())274 }275}276277#[derive(ToLog)]278pub enum ERC721Events {279 280 281 282 283 284 Transfer {285 #[indexed]286 from: Address,287 #[indexed]288 to: Address,289 #[indexed]290 token_id: U256,291 },292 293 294 295 296 Approval {297 #[indexed]298 owner: Address,299 #[indexed]300 approved: Address,301 #[indexed]302 token_id: U256,303 },304 305 306 #[allow(dead_code)]307 ApprovalForAll {308 #[indexed]309 owner: Address,310 #[indexed]311 operator: Address,312 approved: bool,313 },314}315316317318#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]319impl<T: Config> NonfungibleHandle<T>320where321 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,322{323 324 325 #[solidity(hide, rename_selector = "name")]326 fn name_proxy(&self) -> String {327 self.name()328 }329330 331 332 #[solidity(hide, rename_selector = "symbol")]333 fn symbol_proxy(&self) -> String {334 self.symbol()335 }336337 338 339 340 341 342 343 344 345 346 #[solidity(rename_selector = "tokenURI")]347 fn token_uri(&self, token_id: U256) -> Result<String> {348 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;349350 match get_token_property(self, token_id_u32, &key::url()).as_deref() {351 Err(_) | Ok("") => (),352 Ok(url) => {353 return Ok(url.into());354 }355 };356357 let base_uri =358 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())359 .map(BoundedVec::into_inner)360 .map(String::from_utf8)361 .transpose()362 .map_err(|e| {363 Error::Revert(alloc::format!(364 "can not convert value \"baseURI\" to string with error \"{e}\""365 ))366 })?;367368 let base_uri = match base_uri.as_deref() {369 None | Some("") => {370 return Ok("".into());371 }372 Some(base_uri) => base_uri.into(),373 };374375 Ok(376 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {377 Err(_) | Ok("") => base_uri,378 Ok(suffix) => base_uri + suffix,379 },380 )381 }382}383384385386#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]387impl<T: Config> NonfungibleHandle<T> {388 389 390 391 392 fn token_by_index(&self, index: U256) -> U256 {393 index394 }395396 397 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {398 399 Err("not implemented".into())400 }401402 403 404 405 fn total_supply(&self) -> Result<U256> {406 self.consume_store_reads(1)?;407 Ok(<Pallet<T>>::total_supply(self).into())408 }409}410411412413#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]414impl<T: Config> NonfungibleHandle<T> {415 416 417 418 419 420 fn balance_of(&self, owner: Address) -> Result<U256> {421 self.consume_store_reads(1)?;422 let owner = T::CrossAccountId::from_eth(owner);423 let balance = <AccountBalance<T>>::get((self.id, owner));424 Ok(balance.into())425 }426 427 428 429 430 431 fn owner_of(&self, token_id: U256) -> Result<Address> {432 self.consume_store_reads(1)?;433 let token: TokenId = token_id.try_into()?;434 Ok(*<TokenData<T>>::get((self.id, token))435 .ok_or("token not found")?436 .owner437 .as_eth())438 }439 440 #[solidity(rename_selector = "safeTransferFrom")]441 fn safe_transfer_from_with_data(442 &mut self,443 _from: Address,444 _to: Address,445 _token_id: U256,446 _data: Bytes,447 ) -> Result<()> {448 449 Err("not implemented".into())450 }451 452 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {453 454 Err("not implemented".into())455 }456457 458 459 460 461 462 463 464 465 466 #[weight(<CommonWeights<T>>::transfer_from())]467 fn transfer_from(468 &mut self,469 caller: Caller,470 from: Address,471 to: Address,472 token_id: U256,473 ) -> Result<()> {474 let caller = T::CrossAccountId::from_eth(caller);475 let from = T::CrossAccountId::from_eth(from);476 let to = T::CrossAccountId::from_eth(to);477 let token = token_id.try_into()?;478479 <Pallet<T>>::transfer_from(480 self,481 &caller,482 &from,483 &to,484 token,485 &nesting_budget(&self.recorder),486 )487 .map_err(|e| dispatch_to_evm::<T>(e.error))?;488 Ok(())489 }490491 492 493 494 495 496 497 #[weight(<SelfWeightOf<T>>::approve())]498 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {499 let caller = T::CrossAccountId::from_eth(caller);500 let approved = T::CrossAccountId::from_eth(approved);501 let token = token_id.try_into()?;502503 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))504 .map_err(dispatch_to_evm::<T>)?;505 Ok(())506 }507508 509 510 511 512 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]513 fn set_approval_for_all(514 &mut self,515 caller: Caller,516 operator: Address,517 approved: bool,518 ) -> Result<()> {519 let caller = T::CrossAccountId::from_eth(caller);520 let operator = T::CrossAccountId::from_eth(operator);521522 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)523 .map_err(dispatch_to_evm::<T>)?;524 Ok(())525 }526527 528 529 530 531 fn get_approved(&self, token_id: U256) -> Result<Address> {532 let token_id = token_id.try_into()?;533 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;534 Ok(if let Some(operator) = operator {535 *operator.as_eth()536 } else {537 Address::zero()538 })539 }540541 542 #[weight(<SelfWeightOf<T>>::allowance_for_all())]543 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {544 let owner = T::CrossAccountId::from_eth(owner);545 let operator = T::CrossAccountId::from_eth(operator);546547 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))548 }549}550551552#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]553impl<T: Config> NonfungibleHandle<T> {554 555 556 557 558 #[weight(<SelfWeightOf<T>>::burn_item())]559 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {560 let caller = T::CrossAccountId::from_eth(caller);561 let token = token_id.try_into()?;562563 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;564 Ok(())565 }566}567568569#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]570impl<T: Config> NonfungibleHandle<T> {571 572 573 574 #[weight(<SelfWeightOf<T>>::create_item())]575 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {576 let token_id: U256 = <TokensMinted<T>>::get(self.id)577 .checked_add(1)578 .ok_or("item id overflow")?579 .into();580 self.mint_check_id(caller, to, token_id)?;581 Ok(token_id)582 }583584 585 586 587 588 589 #[solidity(hide, rename_selector = "mint")]590 #[weight(<SelfWeightOf<T>>::create_item())]591 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {592 let caller = T::CrossAccountId::from_eth(caller);593 let to = T::CrossAccountId::from_eth(to);594 let token_id: u32 = token_id.try_into()?;595596 if <TokensMinted<T>>::get(self.id)597 .checked_add(1)598 .ok_or("item id overflow")?599 != token_id600 {601 return Err("item id should be next".into());602 }603604 <Pallet<T>>::create_item(605 self,606 &caller,607 CreateItemData::<T> {608 properties: BoundedVec::default(),609 owner: to,610 },611 &nesting_budget(&self.recorder),612 )613 .map_err(dispatch_to_evm::<T>)?;614615 Ok(true)616 }617618 619 620 621 622 #[solidity(rename_selector = "mintWithTokenURI")]623 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]624 fn mint_with_token_uri(625 &mut self,626 caller: Caller,627 to: Address,628 token_uri: String,629 ) -> Result<U256> {630 let token_id: U256 = <TokensMinted<T>>::get(self.id)631 .checked_add(1)632 .ok_or("item id overflow")?633 .into();634 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;635 Ok(token_id)636 }637638 639 640 641 642 643 644 #[solidity(hide, rename_selector = "mintWithTokenURI")]645 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]646 fn mint_with_token_uri_check_id(647 &mut self,648 caller: Caller,649 to: Address,650 token_id: U256,651 token_uri: String,652 ) -> Result<bool> {653 let key = key::url();654 let permission = get_token_permission::<T>(self.id, &key)?;655 if !permission.collection_admin {656 return Err("operation is not allowed".into());657 }658659 let caller = T::CrossAccountId::from_eth(caller);660 let to = T::CrossAccountId::from_eth(to);661 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;662663 if <TokensMinted<T>>::get(self.id)664 .checked_add(1)665 .ok_or("item id overflow")?666 != token_id667 {668 return Err("item id should be next".into());669 }670671 let mut properties = CollectionPropertiesVec::default();672 properties673 .try_push(Property {674 key,675 value: token_uri676 .into_bytes()677 .try_into()678 .map_err(|_| "token uri is too long")?,679 })680 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;681682 <Pallet<T>>::create_item(683 self,684 &caller,685 CreateItemData::<T> {686 properties,687 owner: to,688 },689 &nesting_budget(&self.recorder),690 )691 .map_err(dispatch_to_evm::<T>)?;692 Ok(true)693 }694}695696fn get_token_property<T: Config>(697 collection: &CollectionHandle<T>,698 token_id: u32,699 key: &up_data_structs::PropertyKey,700) -> Result<String> {701 collection.consume_store_reads(1)?;702 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))703 .map_err(|_| Error::Revert("token properties not found".into()))?;704 if let Some(property) = properties.get(key) {705 return Ok(String::from_utf8_lossy(property).into());706 }707708 Err("property tokenURI not found".into())709}710711fn get_token_permission<T: Config>(712 collection_id: CollectionId,713 key: &PropertyKey,714) -> Result<PropertyPermission> {715 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)716 .map_err(|_| Error::Revert("no permissions for collection".into()))?;717 let a = token_property_permissions718 .get(key)719 .map(Clone::clone)720 .ok_or_else(|| {721 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();722 Error::Revert(alloc::format!("no permission for key {key}"))723 })?;724 Ok(a)725}726727728#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]729impl<T: Config> NonfungibleHandle<T>730where731 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,732{733 734 fn name(&self) -> String {735 decode_utf16(self.name.iter().copied())736 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))737 .collect::<String>()738 }739740 741 fn symbol(&self) -> String {742 String::from_utf8_lossy(&self.token_prefix).into()743 }744745 746 fn description(&self) -> String {747 decode_utf16(self.description.iter().copied())748 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))749 .collect::<String>()750 }751752 753 754 755 #[solidity(hide)]756 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {757 Self::owner_of_cross(self, token_id)758 }759760 761 762 763 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {764 Self::token_owner(self, token_id.try_into()?)765 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))766 .map_err(|_| Error::Revert("token not found".into()))767 }768769 770 771 772 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {773 self.consume_store_reads(1)?;774 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));775 Ok(balance.into())776 }777778 779 780 781 782 783 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {784 let keys = keys785 .into_iter()786 .map(|key| {787 <Vec<u8>>::from(key)788 .try_into()789 .map_err(|_| Error::Revert("key too large".into()))790 })791 .collect::<Result<Vec<_>>>()?;792793 <Self as CommonCollectionOperations<T>>::token_properties(794 self,795 token_id.try_into()?,796 if keys.is_empty() { None } else { Some(keys) },797 )798 .into_iter()799 .map(eth::Property::try_from)800 .collect::<Result<Vec<_>>>()801 }802803 804 805 806 807 808 809 #[weight(<SelfWeightOf<T>>::approve())]810 fn approve_cross(811 &mut self,812 caller: Caller,813 approved: eth::CrossAddress,814 token_id: U256,815 ) -> Result<()> {816 let caller = T::CrossAccountId::from_eth(caller);817 let approved = approved.into_sub_cross_account::<T>()?;818 let token = token_id.try_into()?;819820 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))821 .map_err(dispatch_to_evm::<T>)?;822 Ok(())823 }824825 826 827 828 829 830 #[weight(<CommonWeights<T>>::transfer())]831 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {832 let caller = T::CrossAccountId::from_eth(caller);833 let to = T::CrossAccountId::from_eth(to);834 let token = token_id.try_into()?;835836 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))837 .map_err(|e| dispatch_to_evm::<T>(e.error))?;838 Ok(())839 }840841 842 843 844 845 846 #[weight(<CommonWeights<T>>::transfer())]847 fn transfer_cross(848 &mut self,849 caller: Caller,850 to: eth::CrossAddress,851 token_id: U256,852 ) -> Result<()> {853 let caller = T::CrossAccountId::from_eth(caller);854 let to = to.into_sub_cross_account::<T>()?;855 let token = token_id.try_into()?;856857 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))858 .map_err(|e| dispatch_to_evm::<T>(e.error))?;859 Ok(())860 }861862 863 864 865 866 867 868 #[weight(<CommonWeights<T>>::transfer_from())]869 fn transfer_from_cross(870 &mut self,871 caller: Caller,872 from: eth::CrossAddress,873 to: eth::CrossAddress,874 token_id: U256,875 ) -> Result<()> {876 let caller = T::CrossAccountId::from_eth(caller);877 let from = from.into_sub_cross_account::<T>()?;878 let to = to.into_sub_cross_account::<T>()?;879 let token_id = token_id.try_into()?;880881 Pallet::<T>::transfer_from(882 self,883 &caller,884 &from,885 &to,886 token_id,887 &nesting_budget(&self.recorder),888 )889 .map_err(|e| dispatch_to_evm::<T>(e.error))?;890 Ok(())891 }892893 894 895 896 897 898 899 #[solidity(hide)]900 #[weight(<SelfWeightOf<T>>::burn_from())]901 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {902 let caller = T::CrossAccountId::from_eth(caller);903 let from = T::CrossAccountId::from_eth(from);904 let token = token_id.try_into()?;905906 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))907 .map_err(dispatch_to_evm::<T>)?;908 Ok(())909 }910911 912 913 914 915 916 917 #[weight(<SelfWeightOf<T>>::burn_from())]918 fn burn_from_cross(919 &mut self,920 caller: Caller,921 from: eth::CrossAddress,922 token_id: U256,923 ) -> Result<()> {924 let caller = T::CrossAccountId::from_eth(caller);925 let from = from.into_sub_cross_account::<T>()?;926 let token = token_id.try_into()?;927928 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))929 .map_err(dispatch_to_evm::<T>)?;930 Ok(())931 }932933 934 fn next_token_id(&self) -> Result<U256> {935 self.consume_store_reads(1)?;936 Ok(<Pallet<T>>::next_token_id(self)937 .map_err(dispatch_to_evm::<T>)?938 .into())939 }940941 942 943 944 945 946 #[solidity(hide)]947 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]948 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {949 let caller = T::CrossAccountId::from_eth(caller);950 let to = T::CrossAccountId::from_eth(to);951 let mut expected_index = <TokensMinted<T>>::get(self.id)952 .checked_add(1)953 .ok_or("item id overflow")?;954955 let total_tokens = token_ids.len();956 for id in token_ids.into_iter() {957 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;958 if id != expected_index {959 return Err("item id should be next".into());960 }961 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;962 }963 let data = (0..total_tokens)964 .map(|_| CreateItemData::<T> {965 properties: BoundedVec::default(),966 owner: to.clone(),967 })968 .collect();969970 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))971 .map_err(dispatch_to_evm::<T>)?;972 Ok(true)973 }974975 976 977 #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]978 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {979 let caller = T::CrossAccountId::from_eth(caller);980981 let mut create_nft_data = Vec::with_capacity(data.len());982 for MintTokenData { owner, properties } in data {983 let owner = owner.into_sub_cross_account::<T>()?;984 create_nft_data.push(CreateItemData::<T> {985 properties: properties986 .into_iter()987 .map(|property| property.try_into())988 .collect::<Result<Vec<_>>>()?989 .try_into()990 .map_err(|_| "too many properties")?,991 owner,992 });993 }994995 <Pallet<T>>::create_multiple_items(996 self,997 &caller,998 create_nft_data,999 &nesting_budget(&self.recorder),1000 )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) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1012 fn mint_bulk_with_token_uri(1013 &mut self,1014 caller: Caller,1015 to: Address,1016 tokens: Vec<TokenUri>,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")?;10241025 let mut data = Vec::with_capacity(tokens.len());1026 for TokenUri { id, uri } in tokens {1027 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1028 if id != expected_index {1029 return Err("item id should be next".into());1030 }1031 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10321033 let mut properties = CollectionPropertiesVec::default();1034 properties1035 .try_push(Property {1036 key: key.clone(),1037 value: uri1038 .into_bytes()1039 .try_into()1040 .map_err(|_| "token uri is too long")?,1041 })1042 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10431044 data.push(CreateItemData::<T> {1045 properties,1046 owner: to.clone(),1047 });1048 }10491050 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1051 .map_err(dispatch_to_evm::<T>)?;1052 Ok(true)1053 }10541055 1056 1057 1058 1059 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1060 fn mint_cross(1061 &mut self,1062 caller: Caller,1063 to: eth::CrossAddress,1064 properties: Vec<eth::Property>,1065 ) -> Result<U256> {1066 let token_id = <TokensMinted<T>>::get(self.id)1067 .checked_add(1)1068 .ok_or("item id overflow")?;10691070 let to = to.into_sub_cross_account::<T>()?;10711072 let properties = properties1073 .into_iter()1074 .map(eth::Property::try_into)1075 .collect::<Result<Vec<_>>>()?1076 .try_into()1077 .map_err(|_| Error::Revert("too many properties".to_string()))?;10781079 let caller = T::CrossAccountId::from_eth(caller);10801081 <Pallet<T>>::create_item(1082 self,1083 &caller,1084 CreateItemData::<T> {1085 properties,1086 owner: to,1087 },1088 &nesting_budget(&self.recorder),1089 )1090 .map_err(dispatch_to_evm::<T>)?;10911092 Ok(token_id.into())1093 }10941095 1096 fn collection_helper_address(&self) -> Address {1097 T::ContractAddress::get()1098 }1099}11001101#[solidity_interface(1102 name = UniqueNFT,1103 is(1104 ERC721,1105 ERC721Enumerable,1106 ERC721UniqueExtensions,1107 ERC721UniqueMintable,1108 ERC721Burnable,1109 ERC721Metadata(if(this.flags.erc721metadata)),1110 Collection(via(common_mut returns CollectionHandle<T>)),1111 TokenProperties,1112 ),1113 enum(derive(PreDispatch)),1114)]1115impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}111611171118generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1119generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11201121impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1122where1123 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1124{1125 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11261127 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1128 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1129 }1130}