12345678910111213141516171819202122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};30use frame_support::BoundedVec;31use up_data_structs::{32 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,33 CollectionPropertiesVec,34};35use pallet_evm_coder_substrate::{36 dispatch_to_evm, frontier_contract,37 execution::{Result, PreDispatch, Error},38};39use sp_std::{vec::Vec, vec};40use pallet_common::{41 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,42 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},43 eth::{self, TokenUri},44 CommonWeightInfo,45};46use pallet_evm::{account::CrossAccountId, PrecompileHandle};47use pallet_evm_coder_substrate::call;48use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};49use sp_core::{U256, Get};5051use crate::{52 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,53 TokenProperties, SelfWeightOf, weights::WeightInfo, common::CommonWeights,54};555657#[derive(ToLog)]58pub enum ERC721TokenEvent {59 60 TokenChanged {61 62 #[indexed]63 token_id: U256,64 },65}6667frontier_contract! {68 macro_rules! NonfungibleHandle_result {...}69 impl<T: Config> Contract for NonfungibleHandle<T> {...}70}717273#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]74impl<T: Config> NonfungibleHandle<T> {75 76 77 78 79 80 81 #[solidity(hide)]82 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]83 fn set_token_property_permission(84 &mut self,85 caller: Caller,86 key: String,87 is_mutable: bool,88 collection_admin: bool,89 token_owner: bool,90 ) -> Result<()> {91 let caller = T::CrossAccountId::from_eth(caller);92 <Pallet<T>>::set_token_property_permissions(93 self,94 &caller,95 vec![PropertyKeyPermission {96 key: <Vec<u8>>::from(key)97 .try_into()98 .map_err(|_| "too long key")?,99 permission: PropertyPermission {100 mutable: is_mutable,101 collection_admin,102 token_owner,103 },104 }],105 )106 .map_err(dispatch_to_evm::<T>)107 }108109 110 111 112 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]113 fn set_token_property_permissions(114 &mut self,115 caller: Caller,116 permissions: Vec<eth::TokenPropertyPermission>,117 ) -> Result<()> {118 let caller = T::CrossAccountId::from_eth(caller);119 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;120121 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)122 .map_err(dispatch_to_evm::<T>)123 }124125 126 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {127 let perms = <Pallet<T>>::token_property_permission(self.id);128 Ok(perms129 .into_iter()130 .map(eth::TokenPropertyPermission::from)131 .collect())132 }133134 135 136 137 138 139 #[solidity(hide)]140 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]141 fn set_property(142 &mut self,143 caller: Caller,144 token_id: U256,145 key: String,146 value: Bytes,147 ) -> Result<()> {148 let caller = T::CrossAccountId::from_eth(caller);149 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;150 let key = <Vec<u8>>::from(key)151 .try_into()152 .map_err(|_| "key too long")?;153 let value = value.0.try_into().map_err(|_| "value too long")?;154155 let nesting_budget = self156 .recorder157 .weight_calls_budget(<StructureWeight<T>>::find_parent());158159 <Pallet<T>>::set_token_property(160 self,161 &caller,162 TokenId(token_id),163 Property { key, value },164 &nesting_budget,165 )166 .map_err(dispatch_to_evm::<T>)167 }168169 170 171 172 173 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]174 fn set_properties(175 &mut self,176 caller: Caller,177 token_id: U256,178 properties: Vec<eth::Property>,179 ) -> Result<()> {180 let caller = T::CrossAccountId::from_eth(caller);181 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;182183 let nesting_budget = self184 .recorder185 .weight_calls_budget(<StructureWeight<T>>::find_parent());186187 let properties = properties188 .into_iter()189 .map(eth::Property::try_into)190 .collect::<Result<Vec<_>>>()?;191192 <Pallet<T>>::set_token_properties(193 self,194 &caller,195 TokenId(token_id),196 properties.into_iter(),197 pallet_common::SetPropertyMode::ExistingToken,198 &nesting_budget,199 )200 .map_err(dispatch_to_evm::<T>)201 }202203 204 205 206 207 #[solidity(hide)]208 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]209 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {210 let caller = T::CrossAccountId::from_eth(caller);211 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;212 let key = <Vec<u8>>::from(key)213 .try_into()214 .map_err(|_| "key too long")?;215216 let nesting_budget = self217 .recorder218 .weight_calls_budget(<StructureWeight<T>>::find_parent());219220 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)221 .map_err(dispatch_to_evm::<T>)222 }223224 225 226 227 228 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]229 fn delete_properties(230 &mut self,231 token_id: U256,232 caller: Caller,233 keys: Vec<String>,234 ) -> Result<()> {235 let caller = T::CrossAccountId::from_eth(caller);236 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;237 let keys = keys238 .into_iter()239 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))240 .collect::<Result<Vec<_>>>()?;241242 let nesting_budget = self243 .recorder244 .weight_calls_budget(<StructureWeight<T>>::find_parent());245246 <Pallet<T>>::delete_token_properties(247 self,248 &caller,249 TokenId(token_id),250 keys.into_iter(),251 &nesting_budget,252 )253 .map_err(dispatch_to_evm::<T>)254 }255256 257 258 259 260 261 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {262 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;263 let key = <Vec<u8>>::from(key)264 .try_into()265 .map_err(|_| "key too long")?;266267 let props = <TokenProperties<T>>::get((self.id, token_id));268 let prop = props.get(&key).ok_or("key not found")?;269270 Ok(prop.to_vec().into())271 }272}273274#[derive(ToLog)]275pub enum ERC721Events {276 277 278 279 280 281 Transfer {282 #[indexed]283 from: Address,284 #[indexed]285 to: Address,286 #[indexed]287 token_id: U256,288 },289 290 291 292 293 Approval {294 #[indexed]295 owner: Address,296 #[indexed]297 approved: Address,298 #[indexed]299 token_id: U256,300 },301 302 303 #[allow(dead_code)]304 ApprovalForAll {305 #[indexed]306 owner: Address,307 #[indexed]308 operator: Address,309 approved: bool,310 },311}312313314315#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]316impl<T: Config> NonfungibleHandle<T>317where318 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,319{320 321 322 #[solidity(hide, rename_selector = "name")]323 fn name_proxy(&self) -> String {324 self.name()325 }326327 328 329 #[solidity(hide, rename_selector = "symbol")]330 fn symbol_proxy(&self) -> String {331 self.symbol()332 }333334 335 336 337 338 339 340 341 342 343 #[solidity(rename_selector = "tokenURI")]344 fn token_uri(&self, token_id: U256) -> Result<String> {345 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;346347 match get_token_property(self, token_id_u32, &key::url()).as_deref() {348 Err(_) | Ok("") => (),349 Ok(url) => {350 return Ok(url.into());351 }352 };353354 let base_uri =355 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())356 .map(BoundedVec::into_inner)357 .map(String::from_utf8)358 .transpose()359 .map_err(|e| {360 Error::Revert(alloc::format!(361 "Can not convert value \"baseURI\" to string with error \"{e}\""362 ))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(<CommonWeights<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(|e| dispatch_to_evm::<T>(e.error))?;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 522 523 524 fn get_approved(&self, token_id: U256) -> Result<Address> {525 let token_id = token_id.try_into()?;526 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;527 Ok(if let Some(operator) = operator {528 *operator.as_eth()529 } else {530 Address::zero()531 })532 }533534 535 #[weight(<SelfWeightOf<T>>::allowance_for_all())]536 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {537 let owner = T::CrossAccountId::from_eth(owner);538 let operator = T::CrossAccountId::from_eth(operator);539540 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))541 }542}543544545#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]546impl<T: Config> NonfungibleHandle<T> {547 548 549 550 551 #[weight(<SelfWeightOf<T>>::burn_item())]552 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {553 let caller = T::CrossAccountId::from_eth(caller);554 let token = token_id.try_into()?;555556 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;557 Ok(())558 }559}560561562#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]563impl<T: Config> NonfungibleHandle<T> {564 565 566 567 #[weight(<SelfWeightOf<T>>::create_item())]568 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {569 let token_id: U256 = <TokensMinted<T>>::get(self.id)570 .checked_add(1)571 .ok_or("item id overflow")?572 .into();573 self.mint_check_id(caller, to, token_id)?;574 Ok(token_id)575 }576577 578 579 580 581 582 #[solidity(hide, rename_selector = "mint")]583 #[weight(<SelfWeightOf<T>>::create_item())]584 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {585 let caller = T::CrossAccountId::from_eth(caller);586 let to = T::CrossAccountId::from_eth(to);587 let token_id: u32 = token_id.try_into()?;588 let budget = self589 .recorder590 .weight_calls_budget(<StructureWeight<T>>::find_parent());591592 if <TokensMinted<T>>::get(self.id)593 .checked_add(1)594 .ok_or("item id overflow")?595 != token_id596 {597 return Err("item id should be next".into());598 }599600 <Pallet<T>>::create_item(601 self,602 &caller,603 CreateItemData::<T> {604 properties: BoundedVec::default(),605 owner: to,606 },607 &budget,608 )609 .map_err(dispatch_to_evm::<T>)?;610611 Ok(true)612 }613614 615 616 617 618 #[solidity(rename_selector = "mintWithTokenURI")]619 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]620 fn mint_with_token_uri(621 &mut self,622 caller: Caller,623 to: Address,624 token_uri: String,625 ) -> Result<U256> {626 let token_id: U256 = <TokensMinted<T>>::get(self.id)627 .checked_add(1)628 .ok_or("item id overflow")?629 .into();630 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;631 Ok(token_id)632 }633634 635 636 637 638 639 640 #[solidity(hide, rename_selector = "mintWithTokenURI")]641 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]642 fn mint_with_token_uri_check_id(643 &mut self,644 caller: Caller,645 to: Address,646 token_id: U256,647 token_uri: String,648 ) -> Result<bool> {649 let key = key::url();650 let permission = get_token_permission::<T>(self.id, &key)?;651 if !permission.collection_admin {652 return Err("Operation is not allowed".into());653 }654655 let caller = T::CrossAccountId::from_eth(caller);656 let to = T::CrossAccountId::from_eth(to);657 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;658 let budget = self659 .recorder660 .weight_calls_budget(<StructureWeight<T>>::find_parent());661662 if <TokensMinted<T>>::get(self.id)663 .checked_add(1)664 .ok_or("item id overflow")?665 != token_id666 {667 return Err("item id should be next".into());668 }669670 let mut properties = CollectionPropertiesVec::default();671 properties672 .try_push(Property {673 key,674 value: token_uri675 .into_bytes()676 .try_into()677 .map_err(|_| "token uri is too long")?,678 })679 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;680681 <Pallet<T>>::create_item(682 self,683 &caller,684 CreateItemData::<T> {685 properties,686 owner: to,687 },688 &budget,689 )690 .map_err(dispatch_to_evm::<T>)?;691 Ok(true)692 }693}694695fn get_token_property<T: Config>(696 collection: &CollectionHandle<T>,697 token_id: u32,698 key: &up_data_structs::PropertyKey,699) -> Result<String> {700 collection.consume_store_reads(1)?;701 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))702 .map_err(|_| Error::Revert("Token properties not found".into()))?;703 if let Some(property) = properties.get(key) {704 return Ok(String::from_utf8_lossy(property).into());705 }706707 Err("Property tokenURI not found".into())708}709710fn get_token_permission<T: Config>(711 collection_id: CollectionId,712 key: &PropertyKey,713) -> Result<PropertyPermission> {714 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)715 .map_err(|_| Error::Revert("No permissions for collection".into()))?;716 let a = token_property_permissions717 .get(key)718 .map(Clone::clone)719 .ok_or_else(|| {720 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();721 Error::Revert(alloc::format!("No permission for key {key}"))722 })?;723 Ok(a)724}725726727#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]728impl<T: Config> NonfungibleHandle<T>729where730 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,731{732 733 fn name(&self) -> String {734 decode_utf16(self.name.iter().copied())735 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))736 .collect::<String>()737 }738739 740 fn symbol(&self) -> String {741 String::from_utf8_lossy(&self.token_prefix).into()742 }743744 745 fn description(&self) -> String {746 decode_utf16(self.description.iter().copied())747 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))748 .collect::<String>()749 }750751 752 753 754 #[solidity(hide)]755 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {756 Self::owner_of_cross(self, token_id)757 }758759 760 761 762 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {763 Self::token_owner(self, token_id.try_into()?)764 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))765 .map_err(|_| Error::Revert("token not found".into()))766 }767768 769 770 771 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {772 self.consume_store_reads(1)?;773 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));774 Ok(balance.into())775 }776777 778 779 780 781 782 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {783 let keys = keys784 .into_iter()785 .map(|key| {786 <Vec<u8>>::from(key)787 .try_into()788 .map_err(|_| Error::Revert("key too large".into()))789 })790 .collect::<Result<Vec<_>>>()?;791792 <Self as CommonCollectionOperations<T>>::token_properties(793 self,794 token_id.try_into()?,795 if keys.is_empty() { None } else { Some(keys) },796 )797 .into_iter()798 .map(eth::Property::try_from)799 .collect::<Result<Vec<_>>>()800 }801802 803 804 805 806 807 808 #[weight(<SelfWeightOf<T>>::approve())]809 fn approve_cross(810 &mut self,811 caller: Caller,812 approved: eth::CrossAddress,813 token_id: U256,814 ) -> Result<()> {815 let caller = T::CrossAccountId::from_eth(caller);816 let approved = approved.into_sub_cross_account::<T>()?;817 let token = token_id.try_into()?;818819 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))820 .map_err(dispatch_to_evm::<T>)?;821 Ok(())822 }823824 825 826 827 828 829 #[weight(<CommonWeights<T>>::transfer())]830 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {831 let caller = T::CrossAccountId::from_eth(caller);832 let to = T::CrossAccountId::from_eth(to);833 let token = token_id.try_into()?;834 let budget = self835 .recorder836 .weight_calls_budget(<StructureWeight<T>>::find_parent());837838 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)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()?;858 let budget = self859 .recorder860 .weight_calls_budget(<StructureWeight<T>>::find_parent());861862 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)863 .map_err(|e| dispatch_to_evm::<T>(e.error))?;864 Ok(())865 }866867 868 869 870 871 872 873 #[weight(<CommonWeights<T>>::transfer_from())]874 fn transfer_from_cross(875 &mut self,876 caller: Caller,877 from: eth::CrossAddress,878 to: eth::CrossAddress,879 token_id: U256,880 ) -> Result<()> {881 let caller = T::CrossAccountId::from_eth(caller);882 let from = from.into_sub_cross_account::<T>()?;883 let to = to.into_sub_cross_account::<T>()?;884 let token_id = token_id.try_into()?;885 let budget = self886 .recorder887 .weight_calls_budget(<StructureWeight<T>>::find_parent());888 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)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()?;905 let budget = self906 .recorder907 .weight_calls_budget(<StructureWeight<T>>::find_parent());908909 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)910 .map_err(dispatch_to_evm::<T>)?;911 Ok(())912 }913914 915 916 917 918 919 920 #[weight(<SelfWeightOf<T>>::burn_from())]921 fn burn_from_cross(922 &mut self,923 caller: Caller,924 from: eth::CrossAddress,925 token_id: U256,926 ) -> Result<()> {927 let caller = T::CrossAccountId::from_eth(caller);928 let from = from.into_sub_cross_account::<T>()?;929 let token = token_id.try_into()?;930 let budget = self931 .recorder932 .weight_calls_budget(<StructureWeight<T>>::find_parent());933934 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)935 .map_err(dispatch_to_evm::<T>)?;936 Ok(())937 }938939 940 fn next_token_id(&self) -> Result<U256> {941 self.consume_store_reads(1)?;942 Ok(<Pallet<T>>::next_token_id(self)943 .map_err(dispatch_to_evm::<T>)?944 .into())945 }946947 948 949 950 951 952 #[solidity(hide)]953 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]954 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {955 let caller = T::CrossAccountId::from_eth(caller);956 let to = T::CrossAccountId::from_eth(to);957 let mut expected_index = <TokensMinted<T>>::get(self.id)958 .checked_add(1)959 .ok_or("item id overflow")?;960 let budget = self961 .recorder962 .weight_calls_budget(<StructureWeight<T>>::find_parent());963964 let total_tokens = token_ids.len();965 for id in token_ids.into_iter() {966 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;967 if id != expected_index {968 return Err("item id should be next".into());969 }970 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;971 }972 let data = (0..total_tokens)973 .map(|_| CreateItemData::<T> {974 properties: BoundedVec::default(),975 owner: to.clone(),976 })977 .collect();978979 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)980 .map_err(dispatch_to_evm::<T>)?;981 Ok(true)982 }983984 985 986 987 988 989 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]990 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]991 fn mint_bulk_with_token_uri(992 &mut self,993 caller: Caller,994 to: Address,995 tokens: Vec<TokenUri>,996 ) -> Result<bool> {997 let key = key::url();998 let caller = T::CrossAccountId::from_eth(caller);999 let to = T::CrossAccountId::from_eth(to);1000 let mut expected_index = <TokensMinted<T>>::get(self.id)1001 .checked_add(1)1002 .ok_or("item id overflow")?;1003 let budget = self1004 .recorder1005 .weight_calls_budget(<StructureWeight<T>>::find_parent());10061007 let mut data = Vec::with_capacity(tokens.len());1008 for TokenUri { id, uri } in tokens {1009 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1010 if id != expected_index {1011 return Err("item id should be next".into());1012 }1013 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10141015 let mut properties = CollectionPropertiesVec::default();1016 properties1017 .try_push(Property {1018 key: key.clone(),1019 value: uri1020 .into_bytes()1021 .try_into()1022 .map_err(|_| "token uri is too long")?,1023 })1024 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;10251026 data.push(CreateItemData::<T> {1027 properties,1028 owner: to.clone(),1029 });1030 }10311032 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1033 .map_err(dispatch_to_evm::<T>)?;1034 Ok(true)1035 }10361037 1038 1039 1040 1041 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1042 fn mint_cross(1043 &mut self,1044 caller: Caller,1045 to: eth::CrossAddress,1046 properties: Vec<eth::Property>,1047 ) -> Result<U256> {1048 let token_id = <TokensMinted<T>>::get(self.id)1049 .checked_add(1)1050 .ok_or("item id overflow")?;10511052 let to = to.into_sub_cross_account::<T>()?;10531054 let properties = properties1055 .into_iter()1056 .map(eth::Property::try_into)1057 .collect::<Result<Vec<_>>>()?1058 .try_into()1059 .map_err(|_| Error::Revert("too many properties".to_string()))?;10601061 let caller = T::CrossAccountId::from_eth(caller);10621063 let budget = self1064 .recorder1065 .weight_calls_budget(<StructureWeight<T>>::find_parent());10661067 <Pallet<T>>::create_item(1068 self,1069 &caller,1070 CreateItemData::<T> {1071 properties,1072 owner: to,1073 },1074 &budget,1075 )1076 .map_err(dispatch_to_evm::<T>)?;10771078 Ok(token_id.into())1079 }10801081 1082 fn collection_helper_address(&self) -> Address {1083 T::ContractAddress::get()1084 }1085}10861087#[solidity_interface(1088 name = UniqueNFT,1089 is(1090 ERC721,1091 ERC721Enumerable,1092 ERC721UniqueExtensions,1093 ERC721UniqueMintable,1094 ERC721Burnable,1095 ERC721Metadata(if(this.flags.erc721metadata)),1096 Collection(via(common_mut returns CollectionHandle<T>)),1097 TokenProperties,1098 ),1099 enum(derive(PreDispatch)),1100)]1101impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}110211031104generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1105generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11061107impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1108where1109 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1110{1111 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11121113 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1114 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1115 }1116}