12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::{U256, Get};4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 TokenProperties, SelfWeightOf, weights::WeightInfo,51};525354#[derive(ToLog)]55pub enum ERC721TokenEvent {56 57 TokenChanged {58 59 #[indexed]60 token_id: U256,61 },62}6364frontier_contract! {65 macro_rules! NonfungibleHandle_result {...}66 impl<T: Config> Contract for NonfungibleHandle<T> {...}67}686970#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]71impl<T: Config> NonfungibleHandle<T> {72 73 74 75 76 77 78 #[solidity(hide)]79 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]80 fn set_token_property_permission(81 &mut self,82 caller: Caller,83 key: String,84 is_mutable: bool,85 collection_admin: bool,86 token_owner: bool,87 ) -> Result<()> {88 let caller = T::CrossAccountId::from_eth(caller);89 <Pallet<T>>::set_token_property_permissions(90 self,91 &caller,92 vec![PropertyKeyPermission {93 key: <Vec<u8>>::from(key)94 .try_into()95 .map_err(|_| "too long key")?,96 permission: PropertyPermission {97 mutable: is_mutable,98 collection_admin,99 token_owner,100 },101 }],102 )103 .map_err(dispatch_to_evm::<T>)104 }105106 107 108 109 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]110 fn set_token_property_permissions(111 &mut self,112 caller: Caller,113 permissions: Vec<eth::TokenPropertyPermission>,114 ) -> Result<()> {115 let caller = T::CrossAccountId::from_eth(caller);116 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;117118 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)119 .map_err(dispatch_to_evm::<T>)120 }121122 123 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {124 let perms = <Pallet<T>>::token_property_permission(self.id);125 Ok(perms126 .into_iter()127 .map(eth::TokenPropertyPermission::from)128 .collect())129 }130131 132 133 134 135 136 #[solidity(hide)]137 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]138 fn set_property(139 &mut self,140 caller: Caller,141 token_id: U256,142 key: String,143 value: Bytes,144 ) -> Result<()> {145 let caller = T::CrossAccountId::from_eth(caller);146 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;147 let key = <Vec<u8>>::from(key)148 .try_into()149 .map_err(|_| "key too long")?;150 let value = value.0.try_into().map_err(|_| "value too long")?;151152 let nesting_budget = self153 .recorder154 .weight_calls_budget(<StructureWeight<T>>::find_parent());155156 <Pallet<T>>::set_token_property(157 self,158 &caller,159 TokenId(token_id),160 Property { key, value },161 &nesting_budget,162 )163 .map_err(dispatch_to_evm::<T>)164 }165166 167 168 169 170 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]171 fn set_properties(172 &mut self,173 caller: Caller,174 token_id: U256,175 properties: Vec<eth::Property>,176 ) -> Result<()> {177 let caller = T::CrossAccountId::from_eth(caller);178 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;179180 let nesting_budget = self181 .recorder182 .weight_calls_budget(<StructureWeight<T>>::find_parent());183184 let properties = properties185 .into_iter()186 .map(eth::Property::try_into)187 .collect::<Result<Vec<_>>>()?;188189 <Pallet<T>>::set_token_properties(190 self,191 &caller,192 TokenId(token_id),193 properties.into_iter(),194 false,195 &nesting_budget,196 )197 .map_err(dispatch_to_evm::<T>)198 }199200 201 202 203 204 #[solidity(hide)]205 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]206 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {207 let caller = T::CrossAccountId::from_eth(caller);208 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;209 let key = <Vec<u8>>::from(key)210 .try_into()211 .map_err(|_| "key too long")?;212213 let nesting_budget = self214 .recorder215 .weight_calls_budget(<StructureWeight<T>>::find_parent());216217 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)218 .map_err(dispatch_to_evm::<T>)219 }220221 222 223 224 225 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]226 fn delete_properties(227 &mut self,228 token_id: U256,229 caller: Caller,230 keys: Vec<String>,231 ) -> Result<()> {232 let caller = T::CrossAccountId::from_eth(caller);233 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;234 let keys = keys235 .into_iter()236 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))237 .collect::<Result<Vec<_>>>()?;238239 let nesting_budget = self240 .recorder241 .weight_calls_budget(<StructureWeight<T>>::find_parent());242243 <Pallet<T>>::delete_token_properties(244 self,245 &caller,246 TokenId(token_id),247 keys.into_iter(),248 &nesting_budget,249 )250 .map_err(dispatch_to_evm::<T>)251 }252253 254 255 256 257 258 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {259 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;260 let key = <Vec<u8>>::from(key)261 .try_into()262 .map_err(|_| "key too long")?;263264 let props = <TokenProperties<T>>::get((self.id, token_id));265 let prop = props.get(&key).ok_or("key not found")?;266267 Ok(prop.to_vec().into())268 }269}270271#[derive(ToLog)]272pub enum ERC721Events {273 274 275 276 277 278 Transfer {279 #[indexed]280 from: Address,281 #[indexed]282 to: Address,283 #[indexed]284 token_id: U256,285 },286 287 288 289 290 Approval {291 #[indexed]292 owner: Address,293 #[indexed]294 approved: Address,295 #[indexed]296 token_id: U256,297 },298 299 300 #[allow(dead_code)]301 ApprovalForAll {302 #[indexed]303 owner: Address,304 #[indexed]305 operator: Address,306 approved: bool,307 },308}309310311312#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]313impl<T: Config> NonfungibleHandle<T>314where315 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,316{317 318 319 #[solidity(hide, rename_selector = "name")]320 fn name_proxy(&self) -> String {321 self.name()322 }323324 325 326 #[solidity(hide, rename_selector = "symbol")]327 fn symbol_proxy(&self) -> String {328 self.symbol()329 }330331 332 333 334 335 336 337 338 339 340 #[solidity(rename_selector = "tokenURI")]341 fn token_uri(&self, token_id: U256) -> Result<String> {342 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;343344 match get_token_property(self, token_id_u32, &key::url()).as_deref() {345 Err(_) | Ok("") => (),346 Ok(url) => {347 return Ok(url.into());348 }349 };350351 let base_uri =352 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())353 .map(BoundedVec::into_inner)354 .map(String::from_utf8)355 .transpose()356 .map_err(|e| {357 Error::Revert(alloc::format!(358 "Can not convert value \"baseURI\" to string with error \"{}\"",359 e360 ))361 })?;362363 let base_uri = match base_uri.as_deref() {364 None | Some("") => {365 return Ok("".into());366 }367 Some(base_uri) => base_uri.into(),368 };369370 Ok(371 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {372 Err(_) | Ok("") => base_uri,373 Ok(suffix) => base_uri + suffix,374 },375 )376 }377}378379380381#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]382impl<T: Config> NonfungibleHandle<T> {383 384 385 386 387 fn token_by_index(&self, index: U256) -> U256 {388 index389 }390391 392 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {393 394 Err("not implemented".into())395 }396397 398 399 400 fn total_supply(&self) -> Result<U256> {401 self.consume_store_reads(1)?;402 Ok(<Pallet<T>>::total_supply(self).into())403 }404}405406407408#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]409impl<T: Config> NonfungibleHandle<T> {410 411 412 413 414 415 fn balance_of(&self, owner: Address) -> Result<U256> {416 self.consume_store_reads(1)?;417 let owner = T::CrossAccountId::from_eth(owner);418 let balance = <AccountBalance<T>>::get((self.id, owner));419 Ok(balance.into())420 }421 422 423 424 425 426 fn owner_of(&self, token_id: U256) -> Result<Address> {427 self.consume_store_reads(1)?;428 let token: TokenId = token_id.try_into()?;429 Ok(*<TokenData<T>>::get((self.id, token))430 .ok_or("token not found")?431 .owner432 .as_eth())433 }434 435 #[solidity(rename_selector = "safeTransferFrom")]436 fn safe_transfer_from_with_data(437 &mut self,438 _from: Address,439 _to: Address,440 _token_id: U256,441 _data: Bytes,442 ) -> Result<()> {443 444 Err("not implemented".into())445 }446 447 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {448 449 Err("not implemented".into())450 }451452 453 454 455 456 457 458 459 460 461 #[weight(<SelfWeightOf<T>>::transfer_from())]462 fn transfer_from(463 &mut self,464 caller: Caller,465 from: Address,466 to: Address,467 token_id: U256,468 ) -> Result<()> {469 let caller = T::CrossAccountId::from_eth(caller);470 let from = T::CrossAccountId::from_eth(from);471 let to = T::CrossAccountId::from_eth(to);472 let token = token_id.try_into()?;473 let budget = self474 .recorder475 .weight_calls_budget(<StructureWeight<T>>::find_parent());476477 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)478 .map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481482 483 484 485 486 487 488 #[weight(<SelfWeightOf<T>>::approve())]489 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {490 let caller = T::CrossAccountId::from_eth(caller);491 let approved = T::CrossAccountId::from_eth(approved);492 let token = token_id.try_into()?;493494 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))495 .map_err(dispatch_to_evm::<T>)?;496 Ok(())497 }498499 500 501 502 503 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]504 fn set_approval_for_all(505 &mut self,506 caller: Caller,507 operator: Address,508 approved: bool,509 ) -> Result<()> {510 let caller = T::CrossAccountId::from_eth(caller);511 let operator = T::CrossAccountId::from_eth(operator);512513 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)514 .map_err(dispatch_to_evm::<T>)?;515 Ok(())516 }517518 519 520 521 522 fn get_approved(&self, token_id: U256) -> Result<Address> {523 let token_id = token_id.try_into()?;524 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;525 Ok(if let Some(operator) = operator {526 *operator.as_eth()527 } else {528 Address::zero()529 })530 }531532 533 #[weight(<SelfWeightOf<T>>::allowance_for_all())]534 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {535 let owner = T::CrossAccountId::from_eth(owner);536 let operator = T::CrossAccountId::from_eth(operator);537538 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))539 }540}541542543#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]544impl<T: Config> NonfungibleHandle<T> {545 546 547 548 549 #[weight(<SelfWeightOf<T>>::burn_item())]550 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {551 let caller = T::CrossAccountId::from_eth(caller);552 let token = token_id.try_into()?;553554 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;555 Ok(())556 }557}558559560#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]561impl<T: Config> NonfungibleHandle<T> {562 563 564 565 #[weight(<SelfWeightOf<T>>::create_item())]566 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {567 let token_id: U256 = <TokensMinted<T>>::get(self.id)568 .checked_add(1)569 .ok_or("item id overflow")?570 .into();571 self.mint_check_id(caller, to, token_id)?;572 Ok(token_id)573 }574575 576 577 578 579 580 #[solidity(hide, rename_selector = "mint")]581 #[weight(<SelfWeightOf<T>>::create_item())]582 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {583 let caller = T::CrossAccountId::from_eth(caller);584 let to = T::CrossAccountId::from_eth(to);585 let token_id: u32 = token_id.try_into()?;586 let budget = self587 .recorder588 .weight_calls_budget(<StructureWeight<T>>::find_parent());589590 if <TokensMinted<T>>::get(self.id)591 .checked_add(1)592 .ok_or("item id overflow")?593 != token_id594 {595 return Err("item id should be next".into());596 }597598 <Pallet<T>>::create_item(599 self,600 &caller,601 CreateItemData::<T> {602 properties: BoundedVec::default(),603 owner: to,604 },605 &budget,606 )607 .map_err(dispatch_to_evm::<T>)?;608609 Ok(true)610 }611612 613 614 615 616 #[solidity(rename_selector = "mintWithTokenURI")]617 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]618 fn mint_with_token_uri(619 &mut self,620 caller: Caller,621 to: Address,622 token_uri: String,623 ) -> Result<U256> {624 let token_id: U256 = <TokensMinted<T>>::get(self.id)625 .checked_add(1)626 .ok_or("item id overflow")?627 .into();628 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;629 Ok(token_id)630 }631632 633 634 635 636 637 638 #[solidity(hide, rename_selector = "mintWithTokenURI")]639 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]640 fn mint_with_token_uri_check_id(641 &mut self,642 caller: Caller,643 to: Address,644 token_id: U256,645 token_uri: String,646 ) -> Result<bool> {647 let key = key::url();648 let permission = get_token_permission::<T>(self.id, &key)?;649 if !permission.collection_admin {650 return Err("Operation is not allowed".into());651 }652653 let caller = T::CrossAccountId::from_eth(caller);654 let to = T::CrossAccountId::from_eth(to);655 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;656 let budget = self657 .recorder658 .weight_calls_budget(<StructureWeight<T>>::find_parent());659660 if <TokensMinted<T>>::get(self.id)661 .checked_add(1)662 .ok_or("item id overflow")?663 != token_id664 {665 return Err("item id should be next".into());666 }667668 let mut properties = CollectionPropertiesVec::default();669 properties670 .try_push(Property {671 key,672 value: token_uri673 .into_bytes()674 .try_into()675 .map_err(|_| "token uri is too long")?,676 })677 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;678679 <Pallet<T>>::create_item(680 self,681 &caller,682 CreateItemData::<T> {683 properties,684 owner: to,685 },686 &budget,687 )688 .map_err(dispatch_to_evm::<T>)?;689 Ok(true)690 }691}692693fn get_token_property<T: Config>(694 collection: &CollectionHandle<T>,695 token_id: u32,696 key: &up_data_structs::PropertyKey,697) -> Result<String> {698 collection.consume_store_reads(1)?;699 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))700 .map_err(|_| Error::Revert("Token properties not found".into()))?;701 if let Some(property) = properties.get(key) {702 return Ok(String::from_utf8_lossy(property).into());703 }704705 Err("Property tokenURI not found".into())706}707708fn get_token_permission<T: Config>(709 collection_id: CollectionId,710 key: &PropertyKey,711) -> Result<PropertyPermission> {712 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)713 .map_err(|_| Error::Revert("No permissions for collection".into()))?;714 let a = token_property_permissions715 .get(key)716 .map(Clone::clone)717 .ok_or_else(|| {718 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();719 Error::Revert(alloc::format!("No permission for key {}", key))720 })?;721 Ok(a)722}723724725#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]726impl<T: Config> NonfungibleHandle<T>727where728 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,729{730 731 fn name(&self) -> String {732 decode_utf16(self.name.iter().copied())733 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))734 .collect::<String>()735 }736737 738 fn symbol(&self) -> String {739 String::from_utf8_lossy(&self.token_prefix).into()740 }741742 743 fn description(&self) -> String {744 decode_utf16(self.description.iter().copied())745 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))746 .collect::<String>()747 }748749 750 751 752 #[solidity(hide)]753 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {754 Self::owner_of_cross(&self, token_id)755 }756757 758 759 760 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {761 Self::token_owner(&self, token_id.try_into()?)762 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))763 .map_err(|_| Error::Revert("token not found".into()))764 }765766 767 768 769 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {770 self.consume_store_reads(1)?;771 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));772 Ok(balance.into())773 }774775 776 777 778 779 780 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {781 let keys = keys782 .into_iter()783 .map(|key| {784 <Vec<u8>>::from(key)785 .try_into()786 .map_err(|_| Error::Revert("key too large".into()))787 })788 .collect::<Result<Vec<_>>>()?;789790 <Self as CommonCollectionOperations<T>>::token_properties(791 &self,792 token_id.try_into()?,793 if keys.is_empty() { None } else { Some(keys) },794 )795 .into_iter()796 .map(eth::Property::try_from)797 .collect::<Result<Vec<_>>>()798 }799800 801 802 803 804 805 806 #[weight(<SelfWeightOf<T>>::approve())]807 fn approve_cross(808 &mut self,809 caller: Caller,810 approved: eth::CrossAddress,811 token_id: U256,812 ) -> Result<()> {813 let caller = T::CrossAccountId::from_eth(caller);814 let approved = approved.into_sub_cross_account::<T>()?;815 let token = token_id.try_into()?;816817 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))818 .map_err(dispatch_to_evm::<T>)?;819 Ok(())820 }821822 823 824 825 826 827 #[weight(<SelfWeightOf<T>>::transfer())]828 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {829 let caller = T::CrossAccountId::from_eth(caller);830 let to = T::CrossAccountId::from_eth(to);831 let token = token_id.try_into()?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;837 Ok(())838 }839840 841 842 843 844 845 #[weight(<SelfWeightOf<T>>::transfer())]846 fn transfer_cross(847 &mut self,848 caller: Caller,849 to: eth::CrossAddress,850 token_id: U256,851 ) -> Result<()> {852 let caller = T::CrossAccountId::from_eth(caller);853 let to = to.into_sub_cross_account::<T>()?;854 let token = token_id.try_into()?;855 let budget = self856 .recorder857 .weight_calls_budget(<StructureWeight<T>>::find_parent());858859 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;860 Ok(())861 }862863 864 865 866 867 868 869 #[weight(<SelfWeightOf<T>>::transfer())]870 fn transfer_from_cross(871 &mut self,872 caller: Caller,873 from: eth::CrossAddress,874 to: eth::CrossAddress,875 token_id: U256,876 ) -> Result<()> {877 let caller = T::CrossAccountId::from_eth(caller);878 let from = from.into_sub_cross_account::<T>()?;879 let to = to.into_sub_cross_account::<T>()?;880 let token_id = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)885 .map_err(dispatch_to_evm::<T>)?;886 Ok(())887 }888889 890 891 892 893 894 895 #[solidity(hide)]896 #[weight(<SelfWeightOf<T>>::burn_from())]897 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {898 let caller = T::CrossAccountId::from_eth(caller);899 let from = T::CrossAccountId::from_eth(from);900 let token = token_id.try_into()?;901 let budget = self902 .recorder903 .weight_calls_budget(<StructureWeight<T>>::find_parent());904905 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)906 .map_err(dispatch_to_evm::<T>)?;907 Ok(())908 }909910 911 912 913 914 915 916 #[weight(<SelfWeightOf<T>>::burn_from())]917 fn burn_from_cross(918 &mut self,919 caller: Caller,920 from: eth::CrossAddress,921 token_id: U256,922 ) -> Result<()> {923 let caller = T::CrossAccountId::from_eth(caller);924 let from = from.into_sub_cross_account::<T>()?;925 let token = token_id.try_into()?;926 let budget = self927 .recorder928 .weight_calls_budget(<StructureWeight<T>>::find_parent());929930 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)931 .map_err(dispatch_to_evm::<T>)?;932 Ok(())933 }934935 936 fn next_token_id(&self) -> Result<U256> {937 self.consume_store_reads(1)?;938 Ok(<TokensMinted<T>>::get(self.id)939 .checked_add(1)940 .ok_or("item id overflow")?941 .into())942 }943944 945 946 947 948 949 #[solidity(hide)]950 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]951 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {952 let caller = T::CrossAccountId::from_eth(caller);953 let to = T::CrossAccountId::from_eth(to);954 let mut expected_index = <TokensMinted<T>>::get(self.id)955 .checked_add(1)956 .ok_or("item id overflow")?;957 let budget = self958 .recorder959 .weight_calls_budget(<StructureWeight<T>>::find_parent());960961 let total_tokens = token_ids.len();962 for id in token_ids.into_iter() {963 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;964 if id != expected_index {965 return Err("item id should be next".into());966 }967 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;968 }969 let data = (0..total_tokens)970 .map(|_| CreateItemData::<T> {971 properties: BoundedVec::default(),972 owner: to.clone(),973 })974 .collect();975976 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)977 .map_err(dispatch_to_evm::<T>)?;978 Ok(true)979 }980981 982 983 984 985 986 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]987 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]988 fn mint_bulk_with_token_uri(989 &mut self,990 caller: Caller,991 to: Address,992 tokens: Vec<TokenUri>,993 ) -> Result<bool> {994 let key = key::url();995 let caller = T::CrossAccountId::from_eth(caller);996 let to = T::CrossAccountId::from_eth(to);997 let mut expected_index = <TokensMinted<T>>::get(self.id)998 .checked_add(1)999 .ok_or("item id overflow")?;1000 let budget = self1001 .recorder1002 .weight_calls_budget(<StructureWeight<T>>::find_parent());10031004 let mut data = Vec::with_capacity(tokens.len());1005 for TokenUri { id, uri } in tokens {1006 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1007 if id != expected_index {1008 return Err("item id should be next".into());1009 }1010 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10111012 let mut properties = CollectionPropertiesVec::default();1013 properties1014 .try_push(Property {1015 key: key.clone(),1016 value: uri1017 .into_bytes()1018 .try_into()1019 .map_err(|_| "token uri is too long")?,1020 })1021 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10221023 data.push(CreateItemData::<T> {1024 properties,1025 owner: to.clone(),1026 });1027 }10281029 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1030 .map_err(dispatch_to_evm::<T>)?;1031 Ok(true)1032 }10331034 1035 1036 1037 1038 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1039 fn mint_cross(1040 &mut self,1041 caller: Caller,1042 to: eth::CrossAddress,1043 properties: Vec<eth::Property>,1044 ) -> Result<U256> {1045 let token_id = <TokensMinted<T>>::get(self.id)1046 .checked_add(1)1047 .ok_or("item id overflow")?;10481049 let to = to.into_sub_cross_account::<T>()?;10501051 let properties = properties1052 .into_iter()1053 .map(eth::Property::try_into)1054 .collect::<Result<Vec<_>>>()?1055 .try_into()1056 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10571058 let caller = T::CrossAccountId::from_eth(caller);10591060 let budget = self1061 .recorder1062 .weight_calls_budget(<StructureWeight<T>>::find_parent());10631064 <Pallet<T>>::create_item(1065 self,1066 &caller,1067 CreateItemData::<T> {1068 properties,1069 owner: to,1070 },1071 &budget,1072 )1073 .map_err(dispatch_to_evm::<T>)?;10741075 Ok(token_id.into())1076 }10771078 1079 fn collection_helper_address(&self) -> Address {1080 T::ContractAddress::get()1081 }1082}10831084#[solidity_interface(1085 name = UniqueNFT,1086 is(1087 ERC721,1088 ERC721Enumerable,1089 ERC721UniqueExtensions,1090 ERC721UniqueMintable,1091 ERC721Burnable,1092 ERC721Metadata(if(this.flags.erc721metadata)),1093 Collection(via(common_mut returns CollectionHandle<T>)),1094 TokenProperties,1095 ),1096 enum(derive(PreDispatch)),1097)]1098impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}109911001101generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1102generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11031104impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1105where1106 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1107{1108 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11091110 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1111 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1112 }1113}