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 CommonWeightInfo,43};44use pallet_evm::{account::CrossAccountId, PrecompileHandle};45use pallet_evm_coder_substrate::call;46use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};47use sp_core::{U256, Get};4849use crate::{50 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,51 TokenProperties, SelfWeightOf, weights::WeightInfo, common::CommonWeights,52};535455#[derive(ToLog)]56pub enum ERC721TokenEvent {57 58 TokenChanged {59 60 #[indexed]61 token_id: U256,62 },63}6465frontier_contract! {66 macro_rules! NonfungibleHandle_result {...}67 impl<T: Config> Contract for NonfungibleHandle<T> {...}68}697071#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]72impl<T: Config> NonfungibleHandle<T> {73 74 75 76 77 78 79 #[solidity(hide)]80 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]81 fn set_token_property_permission(82 &mut self,83 caller: Caller,84 key: String,85 is_mutable: bool,86 collection_admin: bool,87 token_owner: bool,88 ) -> Result<()> {89 let caller = T::CrossAccountId::from_eth(caller);90 <Pallet<T>>::set_token_property_permissions(91 self,92 &caller,93 vec![PropertyKeyPermission {94 key: <Vec<u8>>::from(key)95 .try_into()96 .map_err(|_| "too long key")?,97 permission: PropertyPermission {98 mutable: is_mutable,99 collection_admin,100 token_owner,101 },102 }],103 )104 .map_err(dispatch_to_evm::<T>)105 }106107 108 109 110 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]111 fn set_token_property_permissions(112 &mut self,113 caller: Caller,114 permissions: Vec<eth::TokenPropertyPermission>,115 ) -> Result<()> {116 let caller = T::CrossAccountId::from_eth(caller);117 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;118119 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)120 .map_err(dispatch_to_evm::<T>)121 }122123 124 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {125 let perms = <Pallet<T>>::token_property_permission(self.id);126 Ok(perms127 .into_iter()128 .map(eth::TokenPropertyPermission::from)129 .collect())130 }131132 133 134 135 136 137 #[solidity(hide)]138 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]139 fn set_property(140 &mut self,141 caller: Caller,142 token_id: U256,143 key: String,144 value: Bytes,145 ) -> Result<()> {146 let caller = T::CrossAccountId::from_eth(caller);147 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;148 let key = <Vec<u8>>::from(key)149 .try_into()150 .map_err(|_| "key too long")?;151 let value = value.0.try_into().map_err(|_| "value too long")?;152153 let nesting_budget = self154 .recorder155 .weight_calls_budget(<StructureWeight<T>>::find_parent());156157 <Pallet<T>>::set_token_property(158 self,159 &caller,160 TokenId(token_id),161 Property { key, value },162 &nesting_budget,163 )164 .map_err(dispatch_to_evm::<T>)165 }166167 168 169 170 171 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]172 fn set_properties(173 &mut self,174 caller: Caller,175 token_id: U256,176 properties: Vec<eth::Property>,177 ) -> Result<()> {178 let caller = T::CrossAccountId::from_eth(caller);179 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;180181 let nesting_budget = self182 .recorder183 .weight_calls_budget(<StructureWeight<T>>::find_parent());184185 let properties = properties186 .into_iter()187 .map(eth::Property::try_into)188 .collect::<Result<Vec<_>>>()?;189190 <Pallet<T>>::set_token_properties(191 self,192 &caller,193 TokenId(token_id),194 properties.into_iter(),195 false,196 &nesting_budget,197 )198 .map_err(dispatch_to_evm::<T>)199 }200201 202 203 204 205 #[solidity(hide)]206 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]207 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {208 let caller = T::CrossAccountId::from_eth(caller);209 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;210 let key = <Vec<u8>>::from(key)211 .try_into()212 .map_err(|_| "key too long")?;213214 let nesting_budget = self215 .recorder216 .weight_calls_budget(<StructureWeight<T>>::find_parent());217218 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)219 .map_err(dispatch_to_evm::<T>)220 }221222 223 224 225 226 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]227 fn delete_properties(228 &mut self,229 token_id: U256,230 caller: Caller,231 keys: Vec<String>,232 ) -> Result<()> {233 let caller = T::CrossAccountId::from_eth(caller);234 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;235 let keys = keys236 .into_iter()237 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))238 .collect::<Result<Vec<_>>>()?;239240 let nesting_budget = self241 .recorder242 .weight_calls_budget(<StructureWeight<T>>::find_parent());243244 <Pallet<T>>::delete_token_properties(245 self,246 &caller,247 TokenId(token_id),248 keys.into_iter(),249 &nesting_budget,250 )251 .map_err(dispatch_to_evm::<T>)252 }253254 255 256 257 258 259 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {260 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;261 let key = <Vec<u8>>::from(key)262 .try_into()263 .map_err(|_| "key too long")?;264265 let props = <TokenProperties<T>>::get((self.id, token_id));266 let prop = props.get(&key).ok_or("key not found")?;267268 Ok(prop.to_vec().into())269 }270}271272#[derive(ToLog)]273pub enum ERC721Events {274 275 276 277 278 279 Transfer {280 #[indexed]281 from: Address,282 #[indexed]283 to: Address,284 #[indexed]285 token_id: U256,286 },287 288 289 290 291 Approval {292 #[indexed]293 owner: Address,294 #[indexed]295 approved: Address,296 #[indexed]297 token_id: U256,298 },299 300 301 #[allow(dead_code)]302 ApprovalForAll {303 #[indexed]304 owner: Address,305 #[indexed]306 operator: Address,307 approved: bool,308 },309}310311312313#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]314impl<T: Config> NonfungibleHandle<T>315where316 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,317{318 319 320 #[solidity(hide, rename_selector = "name")]321 fn name_proxy(&self) -> String {322 self.name()323 }324325 326 327 #[solidity(hide, rename_selector = "symbol")]328 fn symbol_proxy(&self) -> String {329 self.symbol()330 }331332 333 334 335 336 337 338 339 340 341 #[solidity(rename_selector = "tokenURI")]342 fn token_uri(&self, token_id: U256) -> Result<String> {343 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;344345 match get_token_property(self, token_id_u32, &key::url()).as_deref() {346 Err(_) | Ok("") => (),347 Ok(url) => {348 return Ok(url.into());349 }350 };351352 let base_uri =353 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())354 .map(BoundedVec::into_inner)355 .map(String::from_utf8)356 .transpose()357 .map_err(|e| {358 Error::Revert(alloc::format!(359 "Can not convert value \"baseURI\" to string with error \"{}\"",360 e361 ))362 })?;363364 let base_uri = match base_uri.as_deref() {365 None | Some("") => {366 return Ok("".into());367 }368 Some(base_uri) => base_uri.into(),369 };370371 Ok(372 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {373 Err(_) | Ok("") => base_uri,374 Ok(suffix) => base_uri + suffix,375 },376 )377 }378}379380381382#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]383impl<T: Config> NonfungibleHandle<T> {384 385 386 387 388 fn token_by_index(&self, index: U256) -> U256 {389 index390 }391392 393 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {394 395 Err("not implemented".into())396 }397398 399 400 401 fn total_supply(&self) -> Result<U256> {402 self.consume_store_reads(1)?;403 Ok(<Pallet<T>>::total_supply(self).into())404 }405}406407408409#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]410impl<T: Config> NonfungibleHandle<T> {411 412 413 414 415 416 fn balance_of(&self, owner: Address) -> Result<U256> {417 self.consume_store_reads(1)?;418 let owner = T::CrossAccountId::from_eth(owner);419 let balance = <AccountBalance<T>>::get((self.id, owner));420 Ok(balance.into())421 }422 423 424 425 426 427 fn owner_of(&self, token_id: U256) -> Result<Address> {428 self.consume_store_reads(1)?;429 let token: TokenId = token_id.try_into()?;430 Ok(*<TokenData<T>>::get((self.id, token))431 .ok_or("token not found")?432 .owner433 .as_eth())434 }435 436 #[solidity(rename_selector = "safeTransferFrom")]437 fn safe_transfer_from_with_data(438 &mut self,439 _from: Address,440 _to: Address,441 _token_id: U256,442 _data: Bytes,443 ) -> Result<()> {444 445 Err("not implemented".into())446 }447 448 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {449 450 Err("not implemented".into())451 }452453 454 455 456 457 458 459 460 461 462 #[weight(<CommonWeights<T>>::transfer_from())]463 fn transfer_from(464 &mut self,465 caller: Caller,466 from: Address,467 to: Address,468 token_id: U256,469 ) -> Result<()> {470 let caller = T::CrossAccountId::from_eth(caller);471 let from = T::CrossAccountId::from_eth(from);472 let to = T::CrossAccountId::from_eth(to);473 let token = token_id.try_into()?;474 let budget = self475 .recorder476 .weight_calls_budget(<StructureWeight<T>>::find_parent());477478 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)479 .map_err(|e| dispatch_to_evm::<T>(e.error))?;480 Ok(())481 }482483 484 485 486 487 488 489 #[weight(<SelfWeightOf<T>>::approve())]490 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {491 let caller = T::CrossAccountId::from_eth(caller);492 let approved = T::CrossAccountId::from_eth(approved);493 let token = token_id.try_into()?;494495 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))496 .map_err(dispatch_to_evm::<T>)?;497 Ok(())498 }499500 501 502 503 504 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]505 fn set_approval_for_all(506 &mut self,507 caller: Caller,508 operator: Address,509 approved: bool,510 ) -> Result<()> {511 let caller = T::CrossAccountId::from_eth(caller);512 let operator = T::CrossAccountId::from_eth(operator);513514 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)515 .map_err(dispatch_to_evm::<T>)?;516 Ok(())517 }518519 520 521 522 523 fn get_approved(&self, token_id: U256) -> Result<Address> {524 let token_id = token_id.try_into()?;525 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;526 Ok(if let Some(operator) = operator {527 *operator.as_eth()528 } else {529 Address::zero()530 })531 }532533 534 #[weight(<SelfWeightOf<T>>::allowance_for_all())]535 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {536 let owner = T::CrossAccountId::from_eth(owner);537 let operator = T::CrossAccountId::from_eth(operator);538539 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))540 }541}542543544#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]545impl<T: Config> NonfungibleHandle<T> {546 547 548 549 550 #[weight(<SelfWeightOf<T>>::burn_item())]551 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {552 let caller = T::CrossAccountId::from_eth(caller);553 let token = token_id.try_into()?;554555 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558}559560561#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]562impl<T: Config> NonfungibleHandle<T> {563 564 565 566 #[weight(<SelfWeightOf<T>>::create_item())]567 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {568 let token_id: U256 = <TokensMinted<T>>::get(self.id)569 .checked_add(1)570 .ok_or("item id overflow")?571 .into();572 self.mint_check_id(caller, to, token_id)?;573 Ok(token_id)574 }575576 577 578 579 580 581 #[solidity(hide, rename_selector = "mint")]582 #[weight(<SelfWeightOf<T>>::create_item())]583 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {584 let caller = T::CrossAccountId::from_eth(caller);585 let to = T::CrossAccountId::from_eth(to);586 let token_id: u32 = token_id.try_into()?;587 let budget = self588 .recorder589 .weight_calls_budget(<StructureWeight<T>>::find_parent());590591 if <TokensMinted<T>>::get(self.id)592 .checked_add(1)593 .ok_or("item id overflow")?594 != token_id595 {596 return Err("item id should be next".into());597 }598599 <Pallet<T>>::create_item(600 self,601 &caller,602 CreateItemData::<T> {603 properties: BoundedVec::default(),604 owner: to,605 },606 &budget,607 )608 .map_err(dispatch_to_evm::<T>)?;609610 Ok(true)611 }612613 614 615 616 617 #[solidity(rename_selector = "mintWithTokenURI")]618 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]619 fn mint_with_token_uri(620 &mut self,621 caller: Caller,622 to: Address,623 token_uri: String,624 ) -> Result<U256> {625 let token_id: U256 = <TokensMinted<T>>::get(self.id)626 .checked_add(1)627 .ok_or("item id overflow")?628 .into();629 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;630 Ok(token_id)631 }632633 634 635 636 637 638 639 #[solidity(hide, rename_selector = "mintWithTokenURI")]640 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]641 fn mint_with_token_uri_check_id(642 &mut self,643 caller: Caller,644 to: Address,645 token_id: U256,646 token_uri: String,647 ) -> Result<bool> {648 let key = key::url();649 let permission = get_token_permission::<T>(self.id, &key)?;650 if !permission.collection_admin {651 return Err("Operation is not allowed".into());652 }653654 let caller = T::CrossAccountId::from_eth(caller);655 let to = T::CrossAccountId::from_eth(to);656 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;657 let budget = self658 .recorder659 .weight_calls_budget(<StructureWeight<T>>::find_parent());660661 if <TokensMinted<T>>::get(self.id)662 .checked_add(1)663 .ok_or("item id overflow")?664 != token_id665 {666 return Err("item id should be next".into());667 }668669 let mut properties = CollectionPropertiesVec::default();670 properties671 .try_push(Property {672 key,673 value: token_uri674 .into_bytes()675 .try_into()676 .map_err(|_| "token uri is too long")?,677 })678 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;679680 <Pallet<T>>::create_item(681 self,682 &caller,683 CreateItemData::<T> {684 properties,685 owner: to,686 },687 &budget,688 )689 .map_err(dispatch_to_evm::<T>)?;690 Ok(true)691 }692}693694fn get_token_property<T: Config>(695 collection: &CollectionHandle<T>,696 token_id: u32,697 key: &up_data_structs::PropertyKey,698) -> Result<String> {699 collection.consume_store_reads(1)?;700 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))701 .map_err(|_| Error::Revert("Token properties not found".into()))?;702 if let Some(property) = properties.get(key) {703 return Ok(String::from_utf8_lossy(property).into());704 }705706 Err("Property tokenURI not found".into())707}708709fn get_token_permission<T: Config>(710 collection_id: CollectionId,711 key: &PropertyKey,712) -> Result<PropertyPermission> {713 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)714 .map_err(|_| Error::Revert("No permissions for collection".into()))?;715 let a = token_property_permissions716 .get(key)717 .map(Clone::clone)718 .ok_or_else(|| {719 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();720 Error::Revert(alloc::format!("No permission for key {}", key))721 })?;722 Ok(a)723}724725726#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]727impl<T: Config> NonfungibleHandle<T>728where729 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,730{731 732 fn name(&self) -> String {733 decode_utf16(self.name.iter().copied())734 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))735 .collect::<String>()736 }737738 739 fn symbol(&self) -> String {740 String::from_utf8_lossy(&self.token_prefix).into()741 }742743 744 fn description(&self) -> String {745 decode_utf16(self.description.iter().copied())746 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))747 .collect::<String>()748 }749750 751 752 753 #[solidity(hide)]754 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {755 Self::owner_of_cross(&self, token_id)756 }757758 759 760 761 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {762 Self::token_owner(&self, token_id.try_into()?)763 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))764 .map_err(|_| Error::Revert("token not found".into()))765 }766767 768 769 770 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {771 self.consume_store_reads(1)?;772 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));773 Ok(balance.into())774 }775776 777 778 779 780 781 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {782 let keys = keys783 .into_iter()784 .map(|key| {785 <Vec<u8>>::from(key)786 .try_into()787 .map_err(|_| Error::Revert("key too large".into()))788 })789 .collect::<Result<Vec<_>>>()?;790791 <Self as CommonCollectionOperations<T>>::token_properties(792 &self,793 token_id.try_into()?,794 if keys.is_empty() { None } else { Some(keys) },795 )796 .into_iter()797 .map(eth::Property::try_from)798 .collect::<Result<Vec<_>>>()799 }800801 802 803 804 805 806 807 #[weight(<SelfWeightOf<T>>::approve())]808 fn approve_cross(809 &mut self,810 caller: Caller,811 approved: eth::CrossAddress,812 token_id: U256,813 ) -> Result<()> {814 let caller = T::CrossAccountId::from_eth(caller);815 let approved = approved.into_sub_cross_account::<T>()?;816 let token = token_id.try_into()?;817818 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))819 .map_err(dispatch_to_evm::<T>)?;820 Ok(())821 }822823 824 825 826 827 828 #[weight(<CommonWeights<T>>::transfer())]829 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {830 let caller = T::CrossAccountId::from_eth(caller);831 let to = T::CrossAccountId::from_eth(to);832 let token = token_id.try_into()?;833 let budget = self834 .recorder835 .weight_calls_budget(<StructureWeight<T>>::find_parent());836837 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)838 .map_err(|e| dispatch_to_evm::<T>(e.error))?;839 Ok(())840 }841842 843 844 845 846 847 #[weight(<CommonWeights<T>>::transfer())]848 fn transfer_cross(849 &mut self,850 caller: Caller,851 to: eth::CrossAddress,852 token_id: U256,853 ) -> Result<()> {854 let caller = T::CrossAccountId::from_eth(caller);855 let to = to.into_sub_cross_account::<T>()?;856 let token = token_id.try_into()?;857 let budget = self858 .recorder859 .weight_calls_budget(<StructureWeight<T>>::find_parent());860861 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)862 .map_err(|e| dispatch_to_evm::<T>(e.error))?;863 Ok(())864 }865866 867 868 869 870 871 872 #[weight(<CommonWeights<T>>::transfer_from())]873 fn transfer_from_cross(874 &mut self,875 caller: Caller,876 from: eth::CrossAddress,877 to: eth::CrossAddress,878 token_id: U256,879 ) -> Result<()> {880 let caller = T::CrossAccountId::from_eth(caller);881 let from = from.into_sub_cross_account::<T>()?;882 let to = to.into_sub_cross_account::<T>()?;883 let token_id = token_id.try_into()?;884 let budget = self885 .recorder886 .weight_calls_budget(<StructureWeight<T>>::find_parent());887 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)888 .map_err(|e| dispatch_to_evm::<T>(e.error))?;889 Ok(())890 }891892 893 894 895 896 897 898 #[solidity(hide)]899 #[weight(<SelfWeightOf<T>>::burn_from())]900 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {901 let caller = T::CrossAccountId::from_eth(caller);902 let from = T::CrossAccountId::from_eth(from);903 let token = token_id.try_into()?;904 let budget = self905 .recorder906 .weight_calls_budget(<StructureWeight<T>>::find_parent());907908 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)909 .map_err(dispatch_to_evm::<T>)?;910 Ok(())911 }912913 914 915 916 917 918 919 #[weight(<SelfWeightOf<T>>::burn_from())]920 fn burn_from_cross(921 &mut self,922 caller: Caller,923 from: eth::CrossAddress,924 token_id: U256,925 ) -> Result<()> {926 let caller = T::CrossAccountId::from_eth(caller);927 let from = from.into_sub_cross_account::<T>()?;928 let token = token_id.try_into()?;929 let budget = self930 .recorder931 .weight_calls_budget(<StructureWeight<T>>::find_parent());932933 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)934 .map_err(dispatch_to_evm::<T>)?;935 Ok(())936 }937938 939 fn next_token_id(&self) -> Result<U256> {940 self.consume_store_reads(1)?;941 Ok(<TokensMinted<T>>::get(self.id)942 .checked_add(1)943 .ok_or("item id overflow")?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(alloc::format!("too many properties")))?;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}