12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 ToLog,29 execution::*,30 generate_stubgen, solidity, solidity_interface,31 types::*,32 weight,33 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},34 make_signature,35};36use frame_support::BoundedVec;37use up_data_structs::{38 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,39 CollectionPropertiesVec,40};41use pallet_evm_coder_substrate::dispatch_to_evm;42use sp_std::vec::Vec;43use pallet_common::{44 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},45 CollectionHandle, CollectionPropertyPermissions,46};47use pallet_evm::{account::CrossAccountId, PrecompileHandle};48use pallet_evm_coder_substrate::call;49use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};5051use crate::{52 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,53 SelfWeightOf, weights::WeightInfo, TokenProperties,54};555657#[solidity_interface(name = TokenProperties)]58impl<T: Config> NonfungibleHandle<T> {59 60 61 62 63 64 65 fn set_token_property_permission(66 &mut self,67 caller: caller,68 key: string,69 is_mutable: bool,70 collection_admin: bool,71 token_owner: bool,72 ) -> Result<()> {73 let caller = T::CrossAccountId::from_eth(caller);74 <Pallet<T>>::set_property_permission(75 self,76 &caller,77 PropertyKeyPermission {78 key: <Vec<u8>>::from(key)79 .try_into()80 .map_err(|_| "too long key")?,81 permission: PropertyPermission {82 mutable: is_mutable,83 collection_admin,84 token_owner,85 },86 },87 )88 .map_err(dispatch_to_evm::<T>)89 }9091 92 93 94 95 96 fn set_property(97 &mut self,98 caller: caller,99 token_id: uint256,100 key: string,101 value: bytes,102 ) -> Result<()> {103 let caller = T::CrossAccountId::from_eth(caller);104 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105 let key = <Vec<u8>>::from(key)106 .try_into()107 .map_err(|_| "key too long")?;108 let value = value.0.try_into().map_err(|_| "value too long")?;109110 let nesting_budget = self111 .recorder112 .weight_calls_budget(<StructureWeight<T>>::find_parent());113114 <Pallet<T>>::set_token_property(115 self,116 &caller,117 TokenId(token_id),118 Property { key, value },119 &nesting_budget,120 )121 .map_err(dispatch_to_evm::<T>)122 }123124 125 126 127 128 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]129 fn set_properties(130 &mut self,131 caller: caller,132 token_id: uint256,133 properties: Vec<(string, bytes)>,134 ) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;137138 let nesting_budget = self139 .recorder140 .weight_calls_budget(<StructureWeight<T>>::find_parent());141142 let properties = properties143 .into_iter()144 .map(|(key, value)| {145 let key = <Vec<u8>>::from(key)146 .try_into()147 .map_err(|_| "key too large")?;148149 let value = value.0.try_into().map_err(|_| "value too large")?;150151 Ok(Property { key, value })152 })153 .collect::<Result<Vec<_>>>()?;154155 <Pallet<T>>::set_token_properties(156 self,157 &caller,158 TokenId(token_id),159 properties.into_iter(),160 <Pallet<T>>::token_exists(&self, TokenId(token_id)),161 &nesting_budget,162 )163 .map_err(dispatch_to_evm::<T>)164 }165166 167 168 169 170 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {171 let caller = T::CrossAccountId::from_eth(caller);172 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;173 let key = <Vec<u8>>::from(key)174 .try_into()175 .map_err(|_| "key too long")?;176177 let nesting_budget = self178 .recorder179 .weight_calls_budget(<StructureWeight<T>>::find_parent());180181 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)182 .map_err(dispatch_to_evm::<T>)183 }184185 186 187 188 189 190 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {191 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;192 let key = <Vec<u8>>::from(key)193 .try_into()194 .map_err(|_| "key too long")?;195196 let props = <TokenProperties<T>>::get((self.id, token_id));197 let prop = props.get(&key).ok_or("key not found")?;198199 Ok(prop.to_vec().into())200 }201}202203#[derive(ToLog)]204pub enum ERC721Events {205 206 207 208 209 210 Transfer {211 #[indexed]212 from: address,213 #[indexed]214 to: address,215 #[indexed]216 token_id: uint256,217 },218 219 220 221 222 Approval {223 #[indexed]224 owner: address,225 #[indexed]226 approved: address,227 #[indexed]228 token_id: uint256,229 },230 231 232 #[allow(dead_code)]233 ApprovalForAll {234 #[indexed]235 owner: address,236 #[indexed]237 operator: address,238 approved: bool,239 },240}241242#[derive(ToLog)]243pub enum ERC721UniqueMintableEvents {244 #[allow(dead_code)]245 MintingFinished {},246}247248249250#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]251impl<T: Config> NonfungibleHandle<T>252where253 T::AccountId: From<[u8; 32]>,254{255 256 257 #[solidity(hide, rename_selector = "name")]258 fn name_proxy(&self) -> Result<string> {259 self.name()260 }261262 263 264 #[solidity(hide, rename_selector = "symbol")]265 fn symbol_proxy(&self) -> Result<string> {266 self.symbol()267 }268269 270 271 272 273 274 275 276 277 278 #[solidity(rename_selector = "tokenURI")]279 fn token_uri(&self, token_id: uint256) -> Result<string> {280 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;281282 match get_token_property(self, token_id_u32, &key::url()).as_deref() {283 Err(_) | Ok("") => (),284 Ok(url) => {285 return Ok(url.into());286 }287 };288289 let base_uri =290 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())291 .map(BoundedVec::into_inner)292 .map(string::from_utf8)293 .transpose()294 .map_err(|e| {295 Error::Revert(alloc::format!(296 "Can not convert value \"baseURI\" to string with error \"{}\"",297 e298 ))299 })?;300301 let base_uri = match base_uri.as_deref() {302 None | Some("") => {303 return Ok("".into());304 }305 Some(base_uri) => base_uri.into(),306 };307308 Ok(309 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {310 Err(_) | Ok("") => base_uri,311 Ok(suffix) => base_uri + suffix,312 },313 )314 }315}316317318319#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]320impl<T: Config> NonfungibleHandle<T> {321 322 323 324 325 fn token_by_index(&self, index: uint256) -> Result<uint256> {326 Ok(index)327 }328329 330 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {331 332 Err("not implemented".into())333 }334335 336 337 338 fn total_supply(&self) -> Result<uint256> {339 self.consume_store_reads(1)?;340 Ok(<Pallet<T>>::total_supply(self).into())341 }342}343344345346#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]347impl<T: Config> NonfungibleHandle<T> {348 349 350 351 352 353 fn balance_of(&self, owner: address) -> Result<uint256> {354 self.consume_store_reads(1)?;355 let owner = T::CrossAccountId::from_eth(owner);356 let balance = <AccountBalance<T>>::get((self.id, owner));357 Ok(balance.into())358 }359 360 361 362 363 364 fn owner_of(&self, token_id: uint256) -> Result<address> {365 self.consume_store_reads(1)?;366 let token: TokenId = token_id.try_into()?;367 Ok(*<TokenData<T>>::get((self.id, token))368 .ok_or("token not found")?369 .owner370 .as_eth())371 }372 373 #[solidity(rename_selector = "safeTransferFrom")]374 fn safe_transfer_from_with_data(375 &mut self,376 _from: address,377 _to: address,378 _token_id: uint256,379 _data: bytes,380 ) -> Result<void> {381 382 Err("not implemented".into())383 }384 385 fn safe_transfer_from(386 &mut self,387 _from: address,388 _to: address,389 _token_id: uint256,390 ) -> Result<void> {391 392 Err("not implemented".into())393 }394395 396 397 398 399 400 401 402 403 404 #[weight(<SelfWeightOf<T>>::transfer_from())]405 fn transfer_from(406 &mut self,407 caller: caller,408 from: address,409 to: address,410 token_id: uint256,411 ) -> Result<void> {412 let caller = T::CrossAccountId::from_eth(caller);413 let from = T::CrossAccountId::from_eth(from);414 let to = T::CrossAccountId::from_eth(to);415 let token = token_id.try_into()?;416 let budget = self417 .recorder418 .weight_calls_budget(<StructureWeight<T>>::find_parent());419420 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)421 .map_err(dispatch_to_evm::<T>)?;422 Ok(())423 }424425 426 427 428 429 430 431 #[weight(<SelfWeightOf<T>>::approve())]432 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {433 let caller = T::CrossAccountId::from_eth(caller);434 let approved = T::CrossAccountId::from_eth(approved);435 let token = token_id.try_into()?;436437 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))438 .map_err(dispatch_to_evm::<T>)?;439 Ok(())440 }441442 443 fn set_approval_for_all(444 &mut self,445 _caller: caller,446 _operator: address,447 _approved: bool,448 ) -> Result<void> {449 450 Err("not implemented".into())451 }452453 454 fn get_approved(&self, _token_id: uint256) -> Result<address> {455 456 Err("not implemented".into())457 }458459 460 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {461 462 Err("not implemented".into())463 }464}465466467#[solidity_interface(name = ERC721Burnable)]468impl<T: Config> NonfungibleHandle<T> {469 470 471 472 473 #[weight(<SelfWeightOf<T>>::burn_item())]474 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {475 let caller = T::CrossAccountId::from_eth(caller);476 let token = token_id.try_into()?;477478 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481}482483484#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]485impl<T: Config> NonfungibleHandle<T> {486 fn minting_finished(&self) -> Result<bool> {487 Ok(false)488 }489490 491 492 493 #[weight(<SelfWeightOf<T>>::create_item())]494 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {495 let token_id: uint256 = <TokensMinted<T>>::get(self.id)496 .checked_add(1)497 .ok_or("item id overflow")?498 .into();499 self.mint_check_id(caller, to, token_id)?;500 Ok(token_id)501 }502503 504 505 506 507 508 #[solidity(hide, rename_selector = "mint")]509 #[weight(<SelfWeightOf<T>>::create_item())]510 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {511 let caller = T::CrossAccountId::from_eth(caller);512 let to = T::CrossAccountId::from_eth(to);513 let token_id: u32 = token_id.try_into()?;514 let budget = self515 .recorder516 .weight_calls_budget(<StructureWeight<T>>::find_parent());517518 if <TokensMinted<T>>::get(self.id)519 .checked_add(1)520 .ok_or("item id overflow")?521 != token_id522 {523 return Err("item id should be next".into());524 }525526 <Pallet<T>>::create_item(527 self,528 &caller,529 CreateItemData::<T> {530 properties: BoundedVec::default(),531 owner: to,532 },533 &budget,534 )535 .map_err(dispatch_to_evm::<T>)?;536537 Ok(true)538 }539540 541 542 543 544 #[solidity(rename_selector = "mintWithTokenURI")]545 #[weight(<SelfWeightOf<T>>::create_item())]546 fn mint_with_token_uri(547 &mut self,548 caller: caller,549 to: address,550 token_uri: string,551 ) -> Result<uint256> {552 let token_id: uint256 = <TokensMinted<T>>::get(self.id)553 .checked_add(1)554 .ok_or("item id overflow")?555 .into();556 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;557 Ok(token_id)558 }559560 561 562 563 564 565 566 #[solidity(hide, rename_selector = "mintWithTokenURI")]567 #[weight(<SelfWeightOf<T>>::create_item())]568 fn mint_with_token_uri_check_id(569 &mut self,570 caller: caller,571 to: address,572 token_id: uint256,573 token_uri: string,574 ) -> Result<bool> {575 let key = key::url();576 let permission = get_token_permission::<T>(self.id, &key)?;577 if !permission.collection_admin {578 return Err("Operation is not allowed".into());579 }580581 let caller = T::CrossAccountId::from_eth(caller);582 let to = T::CrossAccountId::from_eth(to);583 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;584 let budget = self585 .recorder586 .weight_calls_budget(<StructureWeight<T>>::find_parent());587588 if <TokensMinted<T>>::get(self.id)589 .checked_add(1)590 .ok_or("item id overflow")?591 != token_id592 {593 return Err("item id should be next".into());594 }595596 let mut properties = CollectionPropertiesVec::default();597 properties598 .try_push(Property {599 key,600 value: token_uri601 .into_bytes()602 .try_into()603 .map_err(|_| "token uri is too long")?,604 })605 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;606607 <Pallet<T>>::create_item(608 self,609 &caller,610 CreateItemData::<T> {611 properties,612 owner: to,613 },614 &budget,615 )616 .map_err(dispatch_to_evm::<T>)?;617 Ok(true)618 }619620 621 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {622 Err("not implementable".into())623 }624}625626fn get_token_property<T: Config>(627 collection: &CollectionHandle<T>,628 token_id: u32,629 key: &up_data_structs::PropertyKey,630) -> Result<string> {631 collection.consume_store_reads(1)?;632 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))633 .map_err(|_| Error::Revert("Token properties not found".into()))?;634 if let Some(property) = properties.get(key) {635 return Ok(string::from_utf8_lossy(property).into());636 }637638 Err("Property tokenURI not found".into())639}640641fn get_token_permission<T: Config>(642 collection_id: CollectionId,643 key: &PropertyKey,644) -> Result<PropertyPermission> {645 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)646 .map_err(|_| Error::Revert("No permissions for collection".into()))?;647 let a = token_property_permissions648 .get(key)649 .map(Clone::clone)650 .ok_or_else(|| {651 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();652 Error::Revert(alloc::format!("No permission for key {}", key))653 })?;654 Ok(a)655}656657658#[solidity_interface(name = ERC721UniqueExtensions)]659impl<T: Config> NonfungibleHandle<T>660where661 T::AccountId: From<[u8; 32]>,662{663 664 fn name(&self) -> Result<string> {665 Ok(decode_utf16(self.name.iter().copied())666 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))667 .collect::<string>())668 }669670 671 fn symbol(&self) -> Result<string> {672 Ok(string::from_utf8_lossy(&self.token_prefix).into())673 }674675 676 677 678 679 680 681 #[weight(<SelfWeightOf<T>>::approve())]682 fn approve_cross(683 &mut self,684 caller: caller,685 approved: EthCrossAccount,686 token_id: uint256,687 ) -> Result<void> {688 let caller = T::CrossAccountId::from_eth(caller);689 let approved = approved.into_sub_cross_account::<T>()?;690 let token = token_id.try_into()?;691692 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))693 .map_err(dispatch_to_evm::<T>)?;694 Ok(())695 }696697 698 699 700 701 702 #[weight(<SelfWeightOf<T>>::transfer())]703 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {704 let caller = T::CrossAccountId::from_eth(caller);705 let to = T::CrossAccountId::from_eth(to);706 let token = token_id.try_into()?;707 let budget = self708 .recorder709 .weight_calls_budget(<StructureWeight<T>>::find_parent());710711 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;712 Ok(())713 }714715 716 717 718 719 720 721 #[weight(<SelfWeightOf<T>>::transfer())]722 fn transfer_from_cross(723 &mut self,724 caller: caller,725 from: EthCrossAccount,726 to: EthCrossAccount,727 token_id: uint256,728 ) -> Result<void> {729 let caller = T::CrossAccountId::from_eth(caller);730 let from = from.into_sub_cross_account::<T>()?;731 let to = to.into_sub_cross_account::<T>()?;732 let token_id = token_id.try_into()?;733 let budget = self734 .recorder735 .weight_calls_budget(<StructureWeight<T>>::find_parent());736 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)737 .map_err(dispatch_to_evm::<T>)?;738 Ok(())739 }740741 742 743 744 745 746 747 #[weight(<SelfWeightOf<T>>::burn_from())]748 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {749 let caller = T::CrossAccountId::from_eth(caller);750 let from = T::CrossAccountId::from_eth(from);751 let token = token_id.try_into()?;752 let budget = self753 .recorder754 .weight_calls_budget(<StructureWeight<T>>::find_parent());755756 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)757 .map_err(dispatch_to_evm::<T>)?;758 Ok(())759 }760761 762 763 764 765 766 767 #[weight(<SelfWeightOf<T>>::burn_from())]768 fn burn_from_cross(769 &mut self,770 caller: caller,771 from: EthCrossAccount,772 token_id: uint256,773 ) -> Result<void> {774 let caller = T::CrossAccountId::from_eth(caller);775 let from = from.into_sub_cross_account::<T>()?;776 let token = token_id.try_into()?;777 let budget = self778 .recorder779 .weight_calls_budget(<StructureWeight<T>>::find_parent());780781 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)782 .map_err(dispatch_to_evm::<T>)?;783 Ok(())784 }785786 787 fn next_token_id(&self) -> Result<uint256> {788 self.consume_store_reads(1)?;789 Ok(<TokensMinted<T>>::get(self.id)790 .checked_add(1)791 .ok_or("item id overflow")?792 .into())793 }794795 796 797 798 799 800 #[solidity(hide)]801 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]802 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {803 let caller = T::CrossAccountId::from_eth(caller);804 let to = T::CrossAccountId::from_eth(to);805 let mut expected_index = <TokensMinted<T>>::get(self.id)806 .checked_add(1)807 .ok_or("item id overflow")?;808 let budget = self809 .recorder810 .weight_calls_budget(<StructureWeight<T>>::find_parent());811812 let total_tokens = token_ids.len();813 for id in token_ids.into_iter() {814 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;815 if id != expected_index {816 return Err("item id should be next".into());817 }818 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;819 }820 let data = (0..total_tokens)821 .map(|_| CreateItemData::<T> {822 properties: BoundedVec::default(),823 owner: to.clone(),824 })825 .collect();826827 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)828 .map_err(dispatch_to_evm::<T>)?;829 Ok(true)830 }831832 833 834 835 836 837 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]838 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]839 fn mint_bulk_with_token_uri(840 &mut self,841 caller: caller,842 to: address,843 tokens: Vec<(uint256, string)>,844 ) -> Result<bool> {845 let key = key::url();846 let caller = T::CrossAccountId::from_eth(caller);847 let to = T::CrossAccountId::from_eth(to);848 let mut expected_index = <TokensMinted<T>>::get(self.id)849 .checked_add(1)850 .ok_or("item id overflow")?;851 let budget = self852 .recorder853 .weight_calls_budget(<StructureWeight<T>>::find_parent());854855 let mut data = Vec::with_capacity(tokens.len());856 for (id, token_uri) in tokens {857 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;858 if id != expected_index {859 return Err("item id should be next".into());860 }861 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;862863 let mut properties = CollectionPropertiesVec::default();864 properties865 .try_push(Property {866 key: key.clone(),867 value: token_uri868 .into_bytes()869 .try_into()870 .map_err(|_| "token uri is too long")?,871 })872 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;873874 data.push(CreateItemData::<T> {875 properties,876 owner: to.clone(),877 });878 }879880 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)881 .map_err(dispatch_to_evm::<T>)?;882 Ok(true)883 }884}885886#[solidity_interface(887 name = UniqueNFT,888 is(889 ERC721,890 ERC721Enumerable,891 ERC721UniqueExtensions,892 ERC721UniqueMintable,893 ERC721Burnable,894 ERC721Metadata(if(this.flags.erc721metadata)),895 Collection(via(common_mut returns CollectionHandle<T>)),896 TokenProperties,897 )898)]899impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}900901902generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);903generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);904905impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>906where907 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,908{909 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");910911 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {912 call::<T, UniqueNFTCall<T>, _, _>(handle, self)913 }914}