12345678910111213141516171819202122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{abi::AbiType, AbiCoder, 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}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}808182#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]83impl<T: Config> NonfungibleHandle<T> {84 85 86 87 88 89 90 #[solidity(hide)]91 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]92 fn set_token_property_permission(93 &mut self,94 caller: Caller,95 key: String,96 is_mutable: bool,97 collection_admin: bool,98 token_owner: bool,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 <Pallet<T>>::set_token_property_permissions(102 self,103 &caller,104 vec![PropertyKeyPermission {105 key: <Vec<u8>>::from(key)106 .try_into()107 .map_err(|_| "too long key")?,108 permission: PropertyPermission {109 mutable: is_mutable,110 collection_admin,111 token_owner,112 },113 }],114 )115 .map_err(dispatch_to_evm::<T>)116 }117118 119 120 121 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]122 fn set_token_property_permissions(123 &mut self,124 caller: Caller,125 permissions: Vec<eth::TokenPropertyPermission>,126 ) -> Result<()> {127 let caller = T::CrossAccountId::from_eth(caller);128 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;129130 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)131 .map_err(dispatch_to_evm::<T>)132 }133134 135 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {136 let perms = <Pallet<T>>::token_property_permission(self.id);137 Ok(perms138 .into_iter()139 .map(eth::TokenPropertyPermission::from)140 .collect())141 }142143 144 145 146 147 148 #[solidity(hide)]149 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]150 fn set_property(151 &mut self,152 caller: Caller,153 token_id: U256,154 key: String,155 value: Bytes,156 ) -> Result<()> {157 let caller = T::CrossAccountId::from_eth(caller);158 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;159 let key = <Vec<u8>>::from(key)160 .try_into()161 .map_err(|_| "key too long")?;162 let value = value.0.try_into().map_err(|_| "value too long")?;163164 let nesting_budget = self165 .recorder166 .weight_calls_budget(<StructureWeight<T>>::find_parent());167168 <Pallet<T>>::set_token_property(169 self,170 &caller,171 TokenId(token_id),172 Property { key, value },173 &nesting_budget,174 )175 .map_err(dispatch_to_evm::<T>)176 }177178 179 180 181 182 #[weight(<SelfWeightOf<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 nesting_budget = self193 .recorder194 .weight_calls_budget(<StructureWeight<T>>::find_parent());195196 let properties = properties197 .into_iter()198 .map(eth::Property::try_into)199 .collect::<Result<Vec<_>>>()?;200201 <Pallet<T>>::set_token_properties(202 self,203 &caller,204 TokenId(token_id),205 properties.into_iter(),206 &nesting_budget,207 )208 .map_err(dispatch_to_evm::<T>)209 }210211 212 213 214 215 #[solidity(hide)]216 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]217 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {218 let caller = T::CrossAccountId::from_eth(caller);219 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;220 let key = <Vec<u8>>::from(key)221 .try_into()222 .map_err(|_| "key too long")?;223224 let nesting_budget = self225 .recorder226 .weight_calls_budget(<StructureWeight<T>>::find_parent());227228 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)229 .map_err(dispatch_to_evm::<T>)230 }231232 233 234 235 236 #[weight(<SelfWeightOf<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 let nesting_budget = self251 .recorder252 .weight_calls_budget(<StructureWeight<T>>::find_parent());253254 <Pallet<T>>::delete_token_properties(255 self,256 &caller,257 TokenId(token_id),258 keys.into_iter(),259 &nesting_budget,260 )261 .map_err(dispatch_to_evm::<T>)262 }263264 265 266 267 268 269 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {270 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;271 let key = <Vec<u8>>::from(key)272 .try_into()273 .map_err(|_| "key too long")?;274275 let props = <TokenProperties<T>>::get((self.id, token_id));276 let prop = props.get(&key).ok_or("key not found")?;277278 Ok(prop.to_vec().into())279 }280}281282#[derive(ToLog)]283pub enum ERC721Events {284 285 286 287 288 289 Transfer {290 #[indexed]291 from: Address,292 #[indexed]293 to: Address,294 #[indexed]295 token_id: U256,296 },297 298 299 300 301 Approval {302 #[indexed]303 owner: Address,304 #[indexed]305 approved: Address,306 #[indexed]307 token_id: U256,308 },309 310 311 #[allow(dead_code)]312 ApprovalForAll {313 #[indexed]314 owner: Address,315 #[indexed]316 operator: Address,317 approved: bool,318 },319}320321322323#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]324impl<T: Config> NonfungibleHandle<T>325where326 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,327{328 329 330 #[solidity(hide, rename_selector = "name")]331 fn name_proxy(&self) -> String {332 self.name()333 }334335 336 337 #[solidity(hide, rename_selector = "symbol")]338 fn symbol_proxy(&self) -> String {339 self.symbol()340 }341342 343 344 345 346 347 348 349 350 351 #[solidity(rename_selector = "tokenURI")]352 fn token_uri(&self, token_id: U256) -> Result<String> {353 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;354355 match get_token_property(self, token_id_u32, &key::url()).as_deref() {356 Err(_) | Ok("") => (),357 Ok(url) => {358 return Ok(url.into());359 }360 };361362 let base_uri =363 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())364 .map(BoundedVec::into_inner)365 .map(String::from_utf8)366 .transpose()367 .map_err(|e| {368 Error::Revert(alloc::format!(369 "Can not convert value \"baseURI\" to string with error \"{e}\""370 ))371 })?;372373 let base_uri = match base_uri.as_deref() {374 None | Some("") => {375 return Ok("".into());376 }377 Some(base_uri) => base_uri.into(),378 };379380 Ok(381 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {382 Err(_) | Ok("") => base_uri,383 Ok(suffix) => base_uri + suffix,384 },385 )386 }387}388389390391#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]392impl<T: Config> NonfungibleHandle<T> {393 394 395 396 397 fn token_by_index(&self, index: U256) -> U256 {398 index399 }400401 402 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {403 404 Err("not implemented".into())405 }406407 408 409 410 fn total_supply(&self) -> Result<U256> {411 self.consume_store_reads(1)?;412 Ok(<Pallet<T>>::total_supply(self).into())413 }414}415416417418#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]419impl<T: Config> NonfungibleHandle<T> {420 421 422 423 424 425 fn balance_of(&self, owner: Address) -> Result<U256> {426 self.consume_store_reads(1)?;427 let owner = T::CrossAccountId::from_eth(owner);428 let balance = <AccountBalance<T>>::get((self.id, owner));429 Ok(balance.into())430 }431 432 433 434 435 436 fn owner_of(&self, token_id: U256) -> Result<Address> {437 self.consume_store_reads(1)?;438 let token: TokenId = token_id.try_into()?;439 Ok(*<TokenData<T>>::get((self.id, token))440 .ok_or("token not found")?441 .owner442 .as_eth())443 }444 445 #[solidity(rename_selector = "safeTransferFrom")]446 fn safe_transfer_from_with_data(447 &mut self,448 _from: Address,449 _to: Address,450 _token_id: U256,451 _data: Bytes,452 ) -> Result<()> {453 454 Err("not implemented".into())455 }456 457 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {458 459 Err("not implemented".into())460 }461462 463 464 465 466 467 468 469 470 471 #[weight(<CommonWeights<T>>::transfer_from())]472 fn transfer_from(473 &mut self,474 caller: Caller,475 from: Address,476 to: Address,477 token_id: U256,478 ) -> Result<()> {479 let caller = T::CrossAccountId::from_eth(caller);480 let from = T::CrossAccountId::from_eth(from);481 let to = T::CrossAccountId::from_eth(to);482 let token = token_id.try_into()?;483 let budget = self484 .recorder485 .weight_calls_budget(<StructureWeight<T>>::find_parent());486487 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)488 .map_err(|e| dispatch_to_evm::<T>(e.error))?;489 Ok(())490 }491492 493 494 495 496 497 498 #[weight(<SelfWeightOf<T>>::approve())]499 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {500 let caller = T::CrossAccountId::from_eth(caller);501 let approved = T::CrossAccountId::from_eth(approved);502 let token = token_id.try_into()?;503504 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))505 .map_err(dispatch_to_evm::<T>)?;506 Ok(())507 }508509 510 511 512 513 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]514 fn set_approval_for_all(515 &mut self,516 caller: Caller,517 operator: Address,518 approved: bool,519 ) -> Result<()> {520 let caller = T::CrossAccountId::from_eth(caller);521 let operator = T::CrossAccountId::from_eth(operator);522523 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)524 .map_err(dispatch_to_evm::<T>)?;525 Ok(())526 }527528 529 530 531 532 fn get_approved(&self, token_id: U256) -> Result<Address> {533 let token_id = token_id.try_into()?;534 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;535 Ok(if let Some(operator) = operator {536 *operator.as_eth()537 } else {538 Address::zero()539 })540 }541542 543 #[weight(<SelfWeightOf<T>>::allowance_for_all())]544 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {545 let owner = T::CrossAccountId::from_eth(owner);546 let operator = T::CrossAccountId::from_eth(operator);547548 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))549 }550}551552553#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]554impl<T: Config> NonfungibleHandle<T> {555 556 557 558 559 #[weight(<SelfWeightOf<T>>::burn_item())]560 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {561 let caller = T::CrossAccountId::from_eth(caller);562 let token = token_id.try_into()?;563564 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;565 Ok(())566 }567}568569570#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]571impl<T: Config> NonfungibleHandle<T> {572 573 574 575 #[weight(<SelfWeightOf<T>>::create_item())]576 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {577 let token_id: U256 = <TokensMinted<T>>::get(self.id)578 .checked_add(1)579 .ok_or("item id overflow")?580 .into();581 self.mint_check_id(caller, to, token_id)?;582 Ok(token_id)583 }584585 586 587 588 589 590 #[solidity(hide, rename_selector = "mint")]591 #[weight(<SelfWeightOf<T>>::create_item())]592 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {593 let caller = T::CrossAccountId::from_eth(caller);594 let to = T::CrossAccountId::from_eth(to);595 let token_id: u32 = token_id.try_into()?;596 let budget = self597 .recorder598 .weight_calls_budget(<StructureWeight<T>>::find_parent());599600 if <TokensMinted<T>>::get(self.id)601 .checked_add(1)602 .ok_or("item id overflow")?603 != token_id604 {605 return Err("item id should be next".into());606 }607608 <Pallet<T>>::create_item(609 self,610 &caller,611 CreateItemData::<T> {612 properties: BoundedVec::default(),613 owner: to,614 },615 &budget,616 )617 .map_err(dispatch_to_evm::<T>)?;618619 Ok(true)620 }621622 623 624 625 626 #[solidity(rename_selector = "mintWithTokenURI")]627 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]628 fn mint_with_token_uri(629 &mut self,630 caller: Caller,631 to: Address,632 token_uri: String,633 ) -> Result<U256> {634 let token_id: U256 = <TokensMinted<T>>::get(self.id)635 .checked_add(1)636 .ok_or("item id overflow")?637 .into();638 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;639 Ok(token_id)640 }641642 643 644 645 646 647 648 #[solidity(hide, rename_selector = "mintWithTokenURI")]649 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]650 fn mint_with_token_uri_check_id(651 &mut self,652 caller: Caller,653 to: Address,654 token_id: U256,655 token_uri: String,656 ) -> Result<bool> {657 let key = key::url();658 let permission = get_token_permission::<T>(self.id, &key)?;659 if !permission.collection_admin {660 return Err("Operation is not allowed".into());661 }662663 let caller = T::CrossAccountId::from_eth(caller);664 let to = T::CrossAccountId::from_eth(to);665 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;666 let budget = self667 .recorder668 .weight_calls_budget(<StructureWeight<T>>::find_parent());669670 if <TokensMinted<T>>::get(self.id)671 .checked_add(1)672 .ok_or("item id overflow")?673 != token_id674 {675 return Err("item id should be next".into());676 }677678 let mut properties = CollectionPropertiesVec::default();679 properties680 .try_push(Property {681 key,682 value: token_uri683 .into_bytes()684 .try_into()685 .map_err(|_| "token uri is too long")?,686 })687 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;688689 <Pallet<T>>::create_item(690 self,691 &caller,692 CreateItemData::<T> {693 properties,694 owner: to,695 },696 &budget,697 )698 .map_err(dispatch_to_evm::<T>)?;699 Ok(true)700 }701}702703fn get_token_property<T: Config>(704 collection: &CollectionHandle<T>,705 token_id: u32,706 key: &up_data_structs::PropertyKey,707) -> Result<String> {708 collection.consume_store_reads(1)?;709 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))710 .map_err(|_| Error::Revert("Token properties not found".into()))?;711 if let Some(property) = properties.get(key) {712 return Ok(String::from_utf8_lossy(property).into());713 }714715 Err("Property tokenURI not found".into())716}717718fn get_token_permission<T: Config>(719 collection_id: CollectionId,720 key: &PropertyKey,721) -> Result<PropertyPermission> {722 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)723 .map_err(|_| Error::Revert("No permissions for collection".into()))?;724 let a = token_property_permissions725 .get(key)726 .map(Clone::clone)727 .ok_or_else(|| {728 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();729 Error::Revert(alloc::format!("No permission for key {key}"))730 })?;731 Ok(a)732}733734735#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]736impl<T: Config> NonfungibleHandle<T>737where738 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,739{740 741 fn name(&self) -> String {742 decode_utf16(self.name.iter().copied())743 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))744 .collect::<String>()745 }746747 748 fn symbol(&self) -> String {749 String::from_utf8_lossy(&self.token_prefix).into()750 }751752 753 fn description(&self) -> String {754 decode_utf16(self.description.iter().copied())755 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))756 .collect::<String>()757 }758759 760 761 762 #[solidity(hide)]763 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {764 Self::owner_of_cross(self, token_id)765 }766767 768 769 770 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {771 Self::token_owner(self, token_id.try_into()?)772 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))773 .map_err(|_| Error::Revert("token not found".into()))774 }775776 777 778 779 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {780 self.consume_store_reads(1)?;781 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));782 Ok(balance.into())783 }784785 786 787 788 789 790 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {791 let keys = keys792 .into_iter()793 .map(|key| {794 <Vec<u8>>::from(key)795 .try_into()796 .map_err(|_| Error::Revert("key too large".into()))797 })798 .collect::<Result<Vec<_>>>()?;799800 <Self as CommonCollectionOperations<T>>::token_properties(801 self,802 token_id.try_into()?,803 if keys.is_empty() { None } else { Some(keys) },804 )805 .into_iter()806 .map(eth::Property::try_from)807 .collect::<Result<Vec<_>>>()808 }809810 811 812 813 814 815 816 #[weight(<SelfWeightOf<T>>::approve())]817 fn approve_cross(818 &mut self,819 caller: Caller,820 approved: eth::CrossAddress,821 token_id: U256,822 ) -> Result<()> {823 let caller = T::CrossAccountId::from_eth(caller);824 let approved = approved.into_sub_cross_account::<T>()?;825 let token = token_id.try_into()?;826827 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))828 .map_err(dispatch_to_evm::<T>)?;829 Ok(())830 }831832 833 834 835 836 837 #[weight(<CommonWeights<T>>::transfer())]838 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {839 let caller = T::CrossAccountId::from_eth(caller);840 let to = T::CrossAccountId::from_eth(to);841 let token = token_id.try_into()?;842 let budget = self843 .recorder844 .weight_calls_budget(<StructureWeight<T>>::find_parent());845846 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)847 .map_err(|e| dispatch_to_evm::<T>(e.error))?;848 Ok(())849 }850851 852 853 854 855 856 #[weight(<CommonWeights<T>>::transfer())]857 fn transfer_cross(858 &mut self,859 caller: Caller,860 to: eth::CrossAddress,861 token_id: U256,862 ) -> Result<()> {863 let caller = T::CrossAccountId::from_eth(caller);864 let to = to.into_sub_cross_account::<T>()?;865 let token = token_id.try_into()?;866 let budget = self867 .recorder868 .weight_calls_budget(<StructureWeight<T>>::find_parent());869870 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)871 .map_err(|e| dispatch_to_evm::<T>(e.error))?;872 Ok(())873 }874875 876 877 878 879 880 881 #[weight(<CommonWeights<T>>::transfer_from())]882 fn transfer_from_cross(883 &mut self,884 caller: Caller,885 from: eth::CrossAddress,886 to: eth::CrossAddress,887 token_id: U256,888 ) -> Result<()> {889 let caller = T::CrossAccountId::from_eth(caller);890 let from = from.into_sub_cross_account::<T>()?;891 let to = to.into_sub_cross_account::<T>()?;892 let token_id = token_id.try_into()?;893 let budget = self894 .recorder895 .weight_calls_budget(<StructureWeight<T>>::find_parent());896 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)897 .map_err(|e| dispatch_to_evm::<T>(e.error))?;898 Ok(())899 }900901 902 903 904 905 906 907 #[solidity(hide)]908 #[weight(<SelfWeightOf<T>>::burn_from())]909 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {910 let caller = T::CrossAccountId::from_eth(caller);911 let from = T::CrossAccountId::from_eth(from);912 let token = token_id.try_into()?;913 let budget = self914 .recorder915 .weight_calls_budget(<StructureWeight<T>>::find_parent());916917 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)918 .map_err(dispatch_to_evm::<T>)?;919 Ok(())920 }921922 923 924 925 926 927 928 #[weight(<SelfWeightOf<T>>::burn_from())]929 fn burn_from_cross(930 &mut self,931 caller: Caller,932 from: eth::CrossAddress,933 token_id: U256,934 ) -> Result<()> {935 let caller = T::CrossAccountId::from_eth(caller);936 let from = from.into_sub_cross_account::<T>()?;937 let token = token_id.try_into()?;938 let budget = self939 .recorder940 .weight_calls_budget(<StructureWeight<T>>::find_parent());941942 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)943 .map_err(dispatch_to_evm::<T>)?;944 Ok(())945 }946947 948 fn next_token_id(&self) -> Result<U256> {949 self.consume_store_reads(1)?;950 Ok(<Pallet<T>>::next_token_id(self)951 .map_err(dispatch_to_evm::<T>)?952 .into())953 }954955 956 957 958 959 960 #[solidity(hide)]961 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]962 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {963 let caller = T::CrossAccountId::from_eth(caller);964 let to = T::CrossAccountId::from_eth(to);965 let mut expected_index = <TokensMinted<T>>::get(self.id)966 .checked_add(1)967 .ok_or("item id overflow")?;968 let budget = self969 .recorder970 .weight_calls_budget(<StructureWeight<T>>::find_parent());971972 let total_tokens = token_ids.len();973 for id in token_ids.into_iter() {974 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;975 if id != expected_index {976 return Err("item id should be next".into());977 }978 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;979 }980 let data = (0..total_tokens)981 .map(|_| CreateItemData::<T> {982 properties: BoundedVec::default(),983 owner: to.clone(),984 })985 .collect();986987 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)988 .map_err(dispatch_to_evm::<T>)?;989 Ok(true)990 }991992 993 994 #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]995 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {996 let caller = T::CrossAccountId::from_eth(caller);997 let budget = self998 .recorder999 .weight_calls_budget(<StructureWeight<T>>::find_parent());10001001 let mut create_nft_data = Vec::with_capacity(data.len());1002 for MintTokenData { owner, properties } in data {1003 let owner = owner.into_sub_cross_account::<T>()?;1004 create_nft_data.push(CreateItemData::<T> {1005 properties: properties1006 .into_iter()1007 .map(|property| property.try_into())1008 .collect::<Result<Vec<_>>>()?1009 .try_into()1010 .map_err(|_| "too many properties")?,1011 owner,1012 });1013 }10141015 <Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)1016 .map_err(dispatch_to_evm::<T>)?;1017 Ok(true)1018 }10191020 1021 1022 1023 1024 1025 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1026 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1027 fn mint_bulk_with_token_uri(1028 &mut self,1029 caller: Caller,1030 to: Address,1031 tokens: Vec<TokenUri>,1032 ) -> Result<bool> {1033 let key = key::url();1034 let caller = T::CrossAccountId::from_eth(caller);1035 let to = T::CrossAccountId::from_eth(to);1036 let mut expected_index = <TokensMinted<T>>::get(self.id)1037 .checked_add(1)1038 .ok_or("item id overflow")?;1039 let budget = self1040 .recorder1041 .weight_calls_budget(<StructureWeight<T>>::find_parent());10421043 let mut data = Vec::with_capacity(tokens.len());1044 for TokenUri { id, uri } in tokens {1045 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1046 if id != expected_index {1047 return Err("item id should be next".into());1048 }1049 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10501051 let mut properties = CollectionPropertiesVec::default();1052 properties1053 .try_push(Property {1054 key: key.clone(),1055 value: uri1056 .into_bytes()1057 .try_into()1058 .map_err(|_| "token uri is too long")?,1059 })1060 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;10611062 data.push(CreateItemData::<T> {1063 properties,1064 owner: to.clone(),1065 });1066 }10671068 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1069 .map_err(dispatch_to_evm::<T>)?;1070 Ok(true)1071 }10721073 1074 1075 1076 1077 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1078 fn mint_cross(1079 &mut self,1080 caller: Caller,1081 to: eth::CrossAddress,1082 properties: Vec<eth::Property>,1083 ) -> Result<U256> {1084 let token_id = <TokensMinted<T>>::get(self.id)1085 .checked_add(1)1086 .ok_or("item id overflow")?;10871088 let to = to.into_sub_cross_account::<T>()?;10891090 let properties = properties1091 .into_iter()1092 .map(eth::Property::try_into)1093 .collect::<Result<Vec<_>>>()?1094 .try_into()1095 .map_err(|_| Error::Revert("too many properties".to_string()))?;10961097 let caller = T::CrossAccountId::from_eth(caller);10981099 let budget = self1100 .recorder1101 .weight_calls_budget(<StructureWeight<T>>::find_parent());11021103 <Pallet<T>>::create_item(1104 self,1105 &caller,1106 CreateItemData::<T> {1107 properties,1108 owner: to,1109 },1110 &budget,1111 )1112 .map_err(dispatch_to_evm::<T>)?;11131114 Ok(token_id.into())1115 }11161117 1118 fn collection_helper_address(&self) -> Address {1119 T::ContractAddress::get()1120 }1121}11221123#[solidity_interface(1124 name = UniqueNFT,1125 is(1126 ERC721,1127 ERC721Enumerable,1128 ERC721UniqueExtensions,1129 ERC721UniqueMintable,1130 ERC721Burnable,1131 ERC721Metadata(if(this.flags.erc721metadata)),1132 Collection(via(common_mut returns CollectionHandle<T>)),1133 TokenProperties,1134 ),1135 enum(derive(PreDispatch)),1136)]1137impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}113811391140generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1141generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11421143impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1144where1145 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1146{1147 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11481149 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1150 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1151 }1152}