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 =276 <TokenProperties<T>>::get((self.id, token_id)).ok_or("Token properties not found")?;277 let prop = props.get(&key).ok_or("key not found")?;278279 Ok(prop.to_vec().into())280 }281}282283#[derive(ToLog)]284pub enum ERC721Events {285 286 287 288 289 290 Transfer {291 #[indexed]292 from: Address,293 #[indexed]294 to: Address,295 #[indexed]296 token_id: U256,297 },298 299 300 301 302 Approval {303 #[indexed]304 owner: Address,305 #[indexed]306 approved: Address,307 #[indexed]308 token_id: U256,309 },310 311 312 #[allow(dead_code)]313 ApprovalForAll {314 #[indexed]315 owner: Address,316 #[indexed]317 operator: Address,318 approved: bool,319 },320}321322323324#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]325impl<T: Config> NonfungibleHandle<T>326where327 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,328{329 330 331 #[solidity(hide, rename_selector = "name")]332 fn name_proxy(&self) -> String {333 self.name()334 }335336 337 338 #[solidity(hide, rename_selector = "symbol")]339 fn symbol_proxy(&self) -> String {340 self.symbol()341 }342343 344 345 346 347 348 349 350 351 352 #[solidity(rename_selector = "tokenURI")]353 fn token_uri(&self, token_id: U256) -> Result<String> {354 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;355356 match get_token_property(self, token_id_u32, &key::url()).as_deref() {357 Err(_) | Ok("") => (),358 Ok(url) => {359 return Ok(url.into());360 }361 };362363 let base_uri =364 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())365 .map(BoundedVec::into_inner)366 .map(String::from_utf8)367 .transpose()368 .map_err(|e| {369 Error::Revert(alloc::format!(370 "Can not convert value \"baseURI\" to string with error \"{e}\""371 ))372 })?;373374 let base_uri = match base_uri.as_deref() {375 None | Some("") => {376 return Ok("".into());377 }378 Some(base_uri) => base_uri.into(),379 };380381 Ok(382 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {383 Err(_) | Ok("") => base_uri,384 Ok(suffix) => base_uri + suffix,385 },386 )387 }388}389390391392#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]393impl<T: Config> NonfungibleHandle<T> {394 395 396 397 398 fn token_by_index(&self, index: U256) -> U256 {399 index400 }401402 403 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {404 405 Err("not implemented".into())406 }407408 409 410 411 fn total_supply(&self) -> Result<U256> {412 self.consume_store_reads(1)?;413 Ok(<Pallet<T>>::total_supply(self).into())414 }415}416417418419#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]420impl<T: Config> NonfungibleHandle<T> {421 422 423 424 425 426 fn balance_of(&self, owner: Address) -> Result<U256> {427 self.consume_store_reads(1)?;428 let owner = T::CrossAccountId::from_eth(owner);429 let balance = <AccountBalance<T>>::get((self.id, owner));430 Ok(balance.into())431 }432 433 434 435 436 437 fn owner_of(&self, token_id: U256) -> Result<Address> {438 self.consume_store_reads(1)?;439 let token: TokenId = token_id.try_into()?;440 Ok(*<TokenData<T>>::get((self.id, token))441 .ok_or("token not found")?442 .owner443 .as_eth())444 }445 446 #[solidity(rename_selector = "safeTransferFrom")]447 fn safe_transfer_from_with_data(448 &mut self,449 _from: Address,450 _to: Address,451 _token_id: U256,452 _data: Bytes,453 ) -> Result<()> {454 455 Err("not implemented".into())456 }457 458 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {459 460 Err("not implemented".into())461 }462463 464 465 466 467 468 469 470 471 472 #[weight(<CommonWeights<T>>::transfer_from())]473 fn transfer_from(474 &mut self,475 caller: Caller,476 from: Address,477 to: Address,478 token_id: U256,479 ) -> Result<()> {480 let caller = T::CrossAccountId::from_eth(caller);481 let from = T::CrossAccountId::from_eth(from);482 let to = T::CrossAccountId::from_eth(to);483 let token = token_id.try_into()?;484 let budget = self485 .recorder486 .weight_calls_budget(<StructureWeight<T>>::find_parent());487488 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)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()?;597 let budget = self598 .recorder599 .weight_calls_budget(<StructureWeight<T>>::find_parent());600601 if <TokensMinted<T>>::get(self.id)602 .checked_add(1)603 .ok_or("item id overflow")?604 != token_id605 {606 return Err("item id should be next".into());607 }608609 <Pallet<T>>::create_item(610 self,611 &caller,612 CreateItemData::<T> {613 properties: BoundedVec::default(),614 owner: to,615 },616 &budget,617 )618 .map_err(dispatch_to_evm::<T>)?;619620 Ok(true)621 }622623 624 625 626 627 #[solidity(rename_selector = "mintWithTokenURI")]628 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]629 fn mint_with_token_uri(630 &mut self,631 caller: Caller,632 to: Address,633 token_uri: String,634 ) -> Result<U256> {635 let token_id: U256 = <TokensMinted<T>>::get(self.id)636 .checked_add(1)637 .ok_or("item id overflow")?638 .into();639 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;640 Ok(token_id)641 }642643 644 645 646 647 648 649 #[solidity(hide, rename_selector = "mintWithTokenURI")]650 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]651 fn mint_with_token_uri_check_id(652 &mut self,653 caller: Caller,654 to: Address,655 token_id: U256,656 token_uri: String,657 ) -> Result<bool> {658 let key = key::url();659 let permission = get_token_permission::<T>(self.id, &key)?;660 if !permission.collection_admin {661 return Err("Operation is not allowed".into());662 }663664 let caller = T::CrossAccountId::from_eth(caller);665 let to = T::CrossAccountId::from_eth(to);666 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;667 let budget = self668 .recorder669 .weight_calls_budget(<StructureWeight<T>>::find_parent());670671 if <TokensMinted<T>>::get(self.id)672 .checked_add(1)673 .ok_or("item id overflow")?674 != token_id675 {676 return Err("item id should be next".into());677 }678679 let mut properties = CollectionPropertiesVec::default();680 properties681 .try_push(Property {682 key,683 value: token_uri684 .into_bytes()685 .try_into()686 .map_err(|_| "token uri is too long")?,687 })688 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;689690 <Pallet<T>>::create_item(691 self,692 &caller,693 CreateItemData::<T> {694 properties,695 owner: to,696 },697 &budget,698 )699 .map_err(dispatch_to_evm::<T>)?;700 Ok(true)701 }702}703704fn get_token_property<T: Config>(705 collection: &CollectionHandle<T>,706 token_id: u32,707 key: &up_data_structs::PropertyKey,708) -> Result<String> {709 collection.consume_store_reads(1)?;710 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))711 .map_err(|_| Error::Revert("Token properties not found".into()))?;712 if let Some(property) = properties.get(key) {713 return Ok(String::from_utf8_lossy(property).into());714 }715716 Err("Property tokenURI not found".into())717}718719fn get_token_permission<T: Config>(720 collection_id: CollectionId,721 key: &PropertyKey,722) -> Result<PropertyPermission> {723 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)724 .map_err(|_| Error::Revert("No permissions for collection".into()))?;725 let a = token_property_permissions726 .get(key)727 .map(Clone::clone)728 .ok_or_else(|| {729 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();730 Error::Revert(alloc::format!("No permission for key {key}"))731 })?;732 Ok(a)733}734735736#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]737impl<T: Config> NonfungibleHandle<T>738where739 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,740{741 742 fn name(&self) -> String {743 decode_utf16(self.name.iter().copied())744 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))745 .collect::<String>()746 }747748 749 fn symbol(&self) -> String {750 String::from_utf8_lossy(&self.token_prefix).into()751 }752753 754 fn description(&self) -> String {755 decode_utf16(self.description.iter().copied())756 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))757 .collect::<String>()758 }759760 761 762 763 #[solidity(hide)]764 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {765 Self::owner_of_cross(self, token_id)766 }767768 769 770 771 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {772 Self::token_owner(self, token_id.try_into()?)773 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))774 .map_err(|_| Error::Revert("token not found".into()))775 }776777 778 779 780 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {781 self.consume_store_reads(1)?;782 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));783 Ok(balance.into())784 }785786 787 788 789 790 791 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {792 let keys = keys793 .into_iter()794 .map(|key| {795 <Vec<u8>>::from(key)796 .try_into()797 .map_err(|_| Error::Revert("key too large".into()))798 })799 .collect::<Result<Vec<_>>>()?;800801 <Self as CommonCollectionOperations<T>>::token_properties(802 self,803 token_id.try_into()?,804 if keys.is_empty() { None } else { Some(keys) },805 )806 .into_iter()807 .map(eth::Property::try_from)808 .collect::<Result<Vec<_>>>()809 }810811 812 813 814 815 816 817 #[weight(<SelfWeightOf<T>>::approve())]818 fn approve_cross(819 &mut self,820 caller: Caller,821 approved: eth::CrossAddress,822 token_id: U256,823 ) -> Result<()> {824 let caller = T::CrossAccountId::from_eth(caller);825 let approved = approved.into_sub_cross_account::<T>()?;826 let token = token_id.try_into()?;827828 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))829 .map_err(dispatch_to_evm::<T>)?;830 Ok(())831 }832833 834 835 836 837 838 #[weight(<CommonWeights<T>>::transfer())]839 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {840 let caller = T::CrossAccountId::from_eth(caller);841 let to = T::CrossAccountId::from_eth(to);842 let token = token_id.try_into()?;843 let budget = self844 .recorder845 .weight_calls_budget(<StructureWeight<T>>::find_parent());846847 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)848 .map_err(|e| dispatch_to_evm::<T>(e.error))?;849 Ok(())850 }851852 853 854 855 856 857 #[weight(<CommonWeights<T>>::transfer())]858 fn transfer_cross(859 &mut self,860 caller: Caller,861 to: eth::CrossAddress,862 token_id: U256,863 ) -> Result<()> {864 let caller = T::CrossAccountId::from_eth(caller);865 let to = to.into_sub_cross_account::<T>()?;866 let token = token_id.try_into()?;867 let budget = self868 .recorder869 .weight_calls_budget(<StructureWeight<T>>::find_parent());870871 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)872 .map_err(|e| dispatch_to_evm::<T>(e.error))?;873 Ok(())874 }875876 877 878 879 880 881 882 #[weight(<CommonWeights<T>>::transfer_from())]883 fn transfer_from_cross(884 &mut self,885 caller: Caller,886 from: eth::CrossAddress,887 to: eth::CrossAddress,888 token_id: U256,889 ) -> Result<()> {890 let caller = T::CrossAccountId::from_eth(caller);891 let from = from.into_sub_cross_account::<T>()?;892 let to = to.into_sub_cross_account::<T>()?;893 let token_id = token_id.try_into()?;894 let budget = self895 .recorder896 .weight_calls_budget(<StructureWeight<T>>::find_parent());897 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)898 .map_err(|e| dispatch_to_evm::<T>(e.error))?;899 Ok(())900 }901902 903 904 905 906 907 908 #[solidity(hide)]909 #[weight(<SelfWeightOf<T>>::burn_from())]910 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {911 let caller = T::CrossAccountId::from_eth(caller);912 let from = T::CrossAccountId::from_eth(from);913 let token = token_id.try_into()?;914 let budget = self915 .recorder916 .weight_calls_budget(<StructureWeight<T>>::find_parent());917918 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)919 .map_err(dispatch_to_evm::<T>)?;920 Ok(())921 }922923 924 925 926 927 928 929 #[weight(<SelfWeightOf<T>>::burn_from())]930 fn burn_from_cross(931 &mut self,932 caller: Caller,933 from: eth::CrossAddress,934 token_id: U256,935 ) -> Result<()> {936 let caller = T::CrossAccountId::from_eth(caller);937 let from = from.into_sub_cross_account::<T>()?;938 let token = token_id.try_into()?;939 let budget = self940 .recorder941 .weight_calls_budget(<StructureWeight<T>>::find_parent());942943 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)944 .map_err(dispatch_to_evm::<T>)?;945 Ok(())946 }947948 949 fn next_token_id(&self) -> Result<U256> {950 self.consume_store_reads(1)?;951 Ok(<Pallet<T>>::next_token_id(self)952 .map_err(dispatch_to_evm::<T>)?953 .into())954 }955956 957 958 959 960 961 #[solidity(hide)]962 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]963 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {964 let caller = T::CrossAccountId::from_eth(caller);965 let to = T::CrossAccountId::from_eth(to);966 let mut expected_index = <TokensMinted<T>>::get(self.id)967 .checked_add(1)968 .ok_or("item id overflow")?;969 let budget = self970 .recorder971 .weight_calls_budget(<StructureWeight<T>>::find_parent());972973 let total_tokens = token_ids.len();974 for id in token_ids.into_iter() {975 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;976 if id != expected_index {977 return Err("item id should be next".into());978 }979 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;980 }981 let data = (0..total_tokens)982 .map(|_| CreateItemData::<T> {983 properties: BoundedVec::default(),984 owner: to.clone(),985 })986 .collect();987988 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)989 .map_err(dispatch_to_evm::<T>)?;990 Ok(true)991 }992993 994 995 #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]996 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {997 let caller = T::CrossAccountId::from_eth(caller);998 let budget = self999 .recorder1000 .weight_calls_budget(<StructureWeight<T>>::find_parent());10011002 let mut create_nft_data = Vec::with_capacity(data.len());1003 for MintTokenData { owner, properties } in data {1004 let owner = owner.into_sub_cross_account::<T>()?;1005 create_nft_data.push(CreateItemData::<T> {1006 properties: properties1007 .into_iter()1008 .map(|property| property.try_into())1009 .collect::<Result<Vec<_>>>()?1010 .try_into()1011 .map_err(|_| "too many properties")?,1012 owner,1013 });1014 }10151016 <Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)1017 .map_err(dispatch_to_evm::<T>)?;1018 Ok(true)1019 }10201021 1022 1023 1024 1025 1026 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1027 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1028 fn mint_bulk_with_token_uri(1029 &mut self,1030 caller: Caller,1031 to: Address,1032 tokens: Vec<TokenUri>,1033 ) -> Result<bool> {1034 let key = key::url();1035 let caller = T::CrossAccountId::from_eth(caller);1036 let to = T::CrossAccountId::from_eth(to);1037 let mut expected_index = <TokensMinted<T>>::get(self.id)1038 .checked_add(1)1039 .ok_or("item id overflow")?;1040 let budget = self1041 .recorder1042 .weight_calls_budget(<StructureWeight<T>>::find_parent());10431044 let mut data = Vec::with_capacity(tokens.len());1045 for TokenUri { id, uri } in tokens {1046 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1047 if id != expected_index {1048 return Err("item id should be next".into());1049 }1050 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10511052 let mut properties = CollectionPropertiesVec::default();1053 properties1054 .try_push(Property {1055 key: key.clone(),1056 value: uri1057 .into_bytes()1058 .try_into()1059 .map_err(|_| "token uri is too long")?,1060 })1061 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;10621063 data.push(CreateItemData::<T> {1064 properties,1065 owner: to.clone(),1066 });1067 }10681069 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1070 .map_err(dispatch_to_evm::<T>)?;1071 Ok(true)1072 }10731074 1075 1076 1077 1078 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1079 fn mint_cross(1080 &mut self,1081 caller: Caller,1082 to: eth::CrossAddress,1083 properties: Vec<eth::Property>,1084 ) -> Result<U256> {1085 let token_id = <TokensMinted<T>>::get(self.id)1086 .checked_add(1)1087 .ok_or("item id overflow")?;10881089 let to = to.into_sub_cross_account::<T>()?;10901091 let properties = properties1092 .into_iter()1093 .map(eth::Property::try_into)1094 .collect::<Result<Vec<_>>>()?1095 .try_into()1096 .map_err(|_| Error::Revert("too many properties".to_string()))?;10971098 let caller = T::CrossAccountId::from_eth(caller);10991100 let budget = self1101 .recorder1102 .weight_calls_budget(<StructureWeight<T>>::find_parent());11031104 <Pallet<T>>::create_item(1105 self,1106 &caller,1107 CreateItemData::<T> {1108 properties,1109 owner: to,1110 },1111 &budget,1112 )1113 .map_err(dispatch_to_evm::<T>)?;11141115 Ok(token_id.into())1116 }11171118 1119 fn collection_helper_address(&self) -> Address {1120 T::ContractAddress::get()1121 }1122}11231124#[solidity_interface(1125 name = UniqueNFT,1126 is(1127 ERC721,1128 ERC721Enumerable,1129 ERC721UniqueExtensions,1130 ERC721UniqueMintable,1131 ERC721Burnable,1132 ERC721Metadata(if(this.flags.erc721metadata)),1133 Collection(via(common_mut returns CollectionHandle<T>)),1134 TokenProperties,1135 ),1136 enum(derive(PreDispatch)),1137)]1138impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}113911401141generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1142generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11431144impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1145where1146 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1147{1148 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11491150 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1151 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1152 }1153}