12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4546use crate::{47 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,48 SelfWeightOf, weights::WeightInfo, TokenProperties,49};505152#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54 55 56 57 58 59 60 fn set_token_property_permission(61 &mut self,62 caller: caller,63 key: string,64 is_mutable: bool,65 collection_admin: bool,66 token_owner: bool,67 ) -> Result<()> {68 let caller = T::CrossAccountId::from_eth(caller);69 <Pallet<T>>::set_property_permission(70 self,71 &caller,72 PropertyKeyPermission {73 key: <Vec<u8>>::from(key)74 .try_into()75 .map_err(|_| "too long key")?,76 permission: PropertyPermission {77 mutable: is_mutable,78 collection_admin,79 token_owner,80 },81 },82 )83 .map_err(dispatch_to_evm::<T>)84 }8586 87 88 89 90 91 fn set_property(92 &mut self,93 caller: caller,94 token_id: uint256,95 key: string,96 value: bytes,97 ) -> Result<()> {98 let caller = T::CrossAccountId::from_eth(caller);99 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too long")?;103 let value = value.0.try_into().map_err(|_| "value too long")?;104105 let nesting_budget = self106 .recorder107 .weight_calls_budget(<StructureWeight<T>>::find_parent());108109 <Pallet<T>>::set_token_property(110 self,111 &caller,112 TokenId(token_id),113 Property { key, value },114 &nesting_budget,115 )116 .map_err(dispatch_to_evm::<T>)117 }118119 120 121 122 123 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124 fn set_properties(125 &mut self,126 caller: caller,127 token_id: uint256,128 properties: Vec<(string, bytes)>,129 ) -> Result<()> {130 let caller = T::CrossAccountId::from_eth(caller);131 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133 let nesting_budget = self134 .recorder135 .weight_calls_budget(<StructureWeight<T>>::find_parent());136137 let properties = properties138 .into_iter()139 .map(|(key, value)| {140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too large")?;143144 let value = value.0.try_into().map_err(|_| "value too large")?;145146 Ok(Property { key, value })147 })148 .collect::<Result<Vec<_>>>()?;149150 <Pallet<T>>::set_token_properties(151 self,152 &caller,153 TokenId(token_id),154 properties.into_iter(),155 <Pallet<T>>::token_exists(&self, TokenId(token_id)),156 &nesting_budget,157 )158 .map_err(dispatch_to_evm::<T>)159 }160161 162 163 164 165 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {166 let caller = T::CrossAccountId::from_eth(caller);167 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;168 let key = <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| "key too long")?;171172 let nesting_budget = self173 .recorder174 .weight_calls_budget(<StructureWeight<T>>::find_parent());175176 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)177 .map_err(dispatch_to_evm::<T>)178 }179180 181 182 183 184 185 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {186 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;187 let key = <Vec<u8>>::from(key)188 .try_into()189 .map_err(|_| "key too long")?;190191 let props = <TokenProperties<T>>::get((self.id, token_id));192 let prop = props.get(&key).ok_or("key not found")?;193194 Ok(prop.to_vec().into())195 }196}197198#[derive(ToLog)]199pub enum ERC721Events {200 201 202 203 204 205 Transfer {206 #[indexed]207 from: address,208 #[indexed]209 to: address,210 #[indexed]211 token_id: uint256,212 },213 214 215 216 217 Approval {218 #[indexed]219 owner: address,220 #[indexed]221 approved: address,222 #[indexed]223 token_id: uint256,224 },225 226 227 #[allow(dead_code)]228 ApprovalForAll {229 #[indexed]230 owner: address,231 #[indexed]232 operator: address,233 approved: bool,234 },235}236237#[derive(ToLog)]238pub enum ERC721UniqueMintableEvents {239 #[allow(dead_code)]240 MintingFinished {},241}242243244245#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]246impl<T: Config> NonfungibleHandle<T>247where248 T::AccountId: From<[u8; 32]>,249{250 251 252 #[solidity(hide, rename_selector = "name")]253 fn name_proxy(&self) -> Result<string> {254 self.name()255 }256257 258 259 #[solidity(hide, rename_selector = "symbol")]260 fn symbol_proxy(&self) -> Result<string> {261 self.symbol()262 }263264 265 266 267 268 269 270 271 272 273 #[solidity(rename_selector = "tokenURI")]274 fn token_uri(&self, token_id: uint256) -> Result<string> {275 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;276277 match get_token_property(self, token_id_u32, &key::url()).as_deref() {278 Err(_) | Ok("") => (),279 Ok(url) => {280 return Ok(url.into());281 }282 };283284 let base_uri =285 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())286 .map(BoundedVec::into_inner)287 .map(string::from_utf8)288 .transpose()289 .map_err(|e| {290 Error::Revert(alloc::format!(291 "Can not convert value \"baseURI\" to string with error \"{}\"",292 e293 ))294 })?;295296 let base_uri = match base_uri.as_deref() {297 None | Some("") => {298 return Ok("".into());299 }300 Some(base_uri) => base_uri.into(),301 };302303 Ok(304 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {305 Err(_) | Ok("") => base_uri,306 Ok(suffix) => base_uri + suffix,307 },308 )309 }310}311312313314#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]315impl<T: Config> NonfungibleHandle<T> {316 317 318 319 320 fn token_by_index(&self, index: uint256) -> Result<uint256> {321 Ok(index)322 }323324 325 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {326 327 Err("not implemented".into())328 }329330 331 332 333 fn total_supply(&self) -> Result<uint256> {334 self.consume_store_reads(1)?;335 Ok(<Pallet<T>>::total_supply(self).into())336 }337}338339340341#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]342impl<T: Config> NonfungibleHandle<T> {343 344 345 346 347 348 fn balance_of(&self, owner: address) -> Result<uint256> {349 self.consume_store_reads(1)?;350 let owner = T::CrossAccountId::from_eth(owner);351 let balance = <AccountBalance<T>>::get((self.id, owner));352 Ok(balance.into())353 }354 355 356 357 358 359 fn owner_of(&self, token_id: uint256) -> Result<address> {360 self.consume_store_reads(1)?;361 let token: TokenId = token_id.try_into()?;362 Ok(*<TokenData<T>>::get((self.id, token))363 .ok_or("token not found")?364 .owner365 .as_eth())366 }367 368 #[solidity(rename_selector = "safeTransferFrom")]369 fn safe_transfer_from_with_data(370 &mut self,371 _from: address,372 _to: address,373 _token_id: uint256,374 _data: bytes,375 ) -> Result<void> {376 377 Err("not implemented".into())378 }379 380 fn safe_transfer_from(381 &mut self,382 _from: address,383 _to: address,384 _token_id: uint256,385 ) -> Result<void> {386 387 Err("not implemented".into())388 }389390 391 392 393 394 395 396 397 398 399 #[weight(<SelfWeightOf<T>>::transfer_from())]400 fn transfer_from(401 &mut self,402 caller: caller,403 from: address,404 to: address,405 token_id: uint256,406 ) -> Result<void> {407 let caller = T::CrossAccountId::from_eth(caller);408 let from = T::CrossAccountId::from_eth(from);409 let to = T::CrossAccountId::from_eth(to);410 let token = token_id.try_into()?;411 let budget = self412 .recorder413 .weight_calls_budget(<StructureWeight<T>>::find_parent());414415 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)416 .map_err(dispatch_to_evm::<T>)?;417 Ok(())418 }419420 421 422 423 424 425 426 #[weight(<SelfWeightOf<T>>::approve())]427 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {428 let caller = T::CrossAccountId::from_eth(caller);429 let approved = T::CrossAccountId::from_eth(approved);430 let token = token_id.try_into()?;431432 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))433 .map_err(dispatch_to_evm::<T>)?;434 Ok(())435 }436437 438 fn set_approval_for_all(439 &mut self,440 _caller: caller,441 _operator: address,442 _approved: bool,443 ) -> Result<void> {444 445 Err("not implemented".into())446 }447448 449 fn get_approved(&self, _token_id: uint256) -> Result<address> {450 451 Err("not implemented".into())452 }453454 455 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {456 457 Err("not implemented".into())458 }459}460461462#[solidity_interface(name = ERC721Burnable)]463impl<T: Config> NonfungibleHandle<T> {464 465 466 467 468 #[weight(<SelfWeightOf<T>>::burn_item())]469 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {470 let caller = T::CrossAccountId::from_eth(caller);471 let token = token_id.try_into()?;472473 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;474 Ok(())475 }476}477478479#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]480impl<T: Config> NonfungibleHandle<T> {481 fn minting_finished(&self) -> Result<bool> {482 Ok(false)483 }484485 486 487 488 #[weight(<SelfWeightOf<T>>::create_item())]489 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {490 let token_id: uint256 = <TokensMinted<T>>::get(self.id)491 .checked_add(1)492 .ok_or("item id overflow")?493 .into();494 self.mint_check_id(caller, to, token_id)?;495 Ok(token_id)496 }497498 499 500 501 502 503 #[solidity(hide, rename_selector = "mint")]504 #[weight(<SelfWeightOf<T>>::create_item())]505 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {506 let caller = T::CrossAccountId::from_eth(caller);507 let to = T::CrossAccountId::from_eth(to);508 let token_id: u32 = token_id.try_into()?;509 let budget = self510 .recorder511 .weight_calls_budget(<StructureWeight<T>>::find_parent());512513 if <TokensMinted<T>>::get(self.id)514 .checked_add(1)515 .ok_or("item id overflow")?516 != token_id517 {518 return Err("item id should be next".into());519 }520521 <Pallet<T>>::create_item(522 self,523 &caller,524 CreateItemData::<T> {525 properties: BoundedVec::default(),526 owner: to,527 },528 &budget,529 )530 .map_err(dispatch_to_evm::<T>)?;531532 Ok(true)533 }534535 536 537 538 539 #[solidity(rename_selector = "mintWithTokenURI")]540 #[weight(<SelfWeightOf<T>>::create_item())]541 fn mint_with_token_uri(542 &mut self,543 caller: caller,544 to: address,545 token_uri: string,546 ) -> Result<uint256> {547 let token_id: uint256 = <TokensMinted<T>>::get(self.id)548 .checked_add(1)549 .ok_or("item id overflow")?550 .into();551 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;552 Ok(token_id)553 }554555 556 557 558 559 560 561 #[solidity(hide, rename_selector = "mintWithTokenURI")]562 #[weight(<SelfWeightOf<T>>::create_item())]563 fn mint_with_token_uri_check_id(564 &mut self,565 caller: caller,566 to: address,567 token_id: uint256,568 token_uri: string,569 ) -> Result<bool> {570 let key = key::url();571 let permission = get_token_permission::<T>(self.id, &key)?;572 if !permission.collection_admin {573 return Err("Operation is not allowed".into());574 }575576 let caller = T::CrossAccountId::from_eth(caller);577 let to = T::CrossAccountId::from_eth(to);578 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;579 let budget = self580 .recorder581 .weight_calls_budget(<StructureWeight<T>>::find_parent());582583 if <TokensMinted<T>>::get(self.id)584 .checked_add(1)585 .ok_or("item id overflow")?586 != token_id587 {588 return Err("item id should be next".into());589 }590591 let mut properties = CollectionPropertiesVec::default();592 properties593 .try_push(Property {594 key,595 value: token_uri596 .into_bytes()597 .try_into()598 .map_err(|_| "token uri is too long")?,599 })600 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;601602 <Pallet<T>>::create_item(603 self,604 &caller,605 CreateItemData::<T> {606 properties,607 owner: to,608 },609 &budget,610 )611 .map_err(dispatch_to_evm::<T>)?;612 Ok(true)613 }614615 616 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {617 Err("not implementable".into())618 }619}620621fn get_token_property<T: Config>(622 collection: &CollectionHandle<T>,623 token_id: u32,624 key: &up_data_structs::PropertyKey,625) -> Result<string> {626 collection.consume_store_reads(1)?;627 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))628 .map_err(|_| Error::Revert("Token properties not found".into()))?;629 if let Some(property) = properties.get(key) {630 return Ok(string::from_utf8_lossy(property).into());631 }632633 Err("Property tokenURI not found".into())634}635636fn get_token_permission<T: Config>(637 collection_id: CollectionId,638 key: &PropertyKey,639) -> Result<PropertyPermission> {640 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)641 .map_err(|_| Error::Revert("No permissions for collection".into()))?;642 let a = token_property_permissions643 .get(key)644 .map(Clone::clone)645 .ok_or_else(|| {646 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();647 Error::Revert(alloc::format!("No permission for key {}", key))648 })?;649 Ok(a)650}651652653#[solidity_interface(name = ERC721UniqueExtensions)]654impl<T: Config> NonfungibleHandle<T>655where656 T::AccountId: From<[u8; 32]>,657{658 659 fn name(&self) -> Result<string> {660 Ok(decode_utf16(self.name.iter().copied())661 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))662 .collect::<string>())663 }664665 666 fn symbol(&self) -> Result<string> {667 Ok(string::from_utf8_lossy(&self.token_prefix).into())668 }669670 671 672 673 674 675 676 #[weight(<SelfWeightOf<T>>::approve())]677 fn approve_cross(678 &mut self,679 caller: caller,680 approved: EthCrossAccount,681 token_id: uint256,682 ) -> Result<void> {683 let caller = T::CrossAccountId::from_eth(caller);684 let approved = approved.into_sub_cross_account::<T>()?;685 let token = token_id.try_into()?;686687 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))688 .map_err(dispatch_to_evm::<T>)?;689 Ok(())690 }691692 693 694 695 696 697 #[weight(<SelfWeightOf<T>>::transfer())]698 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {699 let caller = T::CrossAccountId::from_eth(caller);700 let to = T::CrossAccountId::from_eth(to);701 let token = token_id.try_into()?;702 let budget = self703 .recorder704 .weight_calls_budget(<StructureWeight<T>>::find_parent());705706 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;707 Ok(())708 }709710 711 712 713 714 715 716 #[weight(<SelfWeightOf<T>>::transfer())]717 fn transfer_from_cross(718 &mut self,719 caller: caller,720 from: EthCrossAccount,721 to: EthCrossAccount,722 token_id: uint256,723 ) -> Result<void> {724 let caller = T::CrossAccountId::from_eth(caller);725 let from = from.into_sub_cross_account::<T>()?;726 let to = to.into_sub_cross_account::<T>()?;727 let token_id = token_id.try_into()?;728 let budget = self729 .recorder730 .weight_calls_budget(<StructureWeight<T>>::find_parent());731 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)732 .map_err(dispatch_to_evm::<T>)?;733 Ok(())734 }735736 737 738 739 740 741 742 #[weight(<SelfWeightOf<T>>::burn_from())]743 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {744 let caller = T::CrossAccountId::from_eth(caller);745 let from = T::CrossAccountId::from_eth(from);746 let token = token_id.try_into()?;747 let budget = self748 .recorder749 .weight_calls_budget(<StructureWeight<T>>::find_parent());750751 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)752 .map_err(dispatch_to_evm::<T>)?;753 Ok(())754 }755756 757 758 759 760 761 762 #[weight(<SelfWeightOf<T>>::burn_from())]763 fn burn_from_cross(764 &mut self,765 caller: caller,766 from: EthCrossAccount,767 token_id: uint256,768 ) -> Result<void> {769 let caller = T::CrossAccountId::from_eth(caller);770 let from = from.into_sub_cross_account::<T>()?;771 let token = token_id.try_into()?;772 let budget = self773 .recorder774 .weight_calls_budget(<StructureWeight<T>>::find_parent());775776 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)777 .map_err(dispatch_to_evm::<T>)?;778 Ok(())779 }780781 782 fn next_token_id(&self) -> Result<uint256> {783 self.consume_store_reads(1)?;784 Ok(<TokensMinted<T>>::get(self.id)785 .checked_add(1)786 .ok_or("item id overflow")?787 .into())788 }789790 791 792 793 794 795 #[solidity(hide)]796 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]797 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {798 let caller = T::CrossAccountId::from_eth(caller);799 let to = T::CrossAccountId::from_eth(to);800 let mut expected_index = <TokensMinted<T>>::get(self.id)801 .checked_add(1)802 .ok_or("item id overflow")?;803 let budget = self804 .recorder805 .weight_calls_budget(<StructureWeight<T>>::find_parent());806807 let total_tokens = token_ids.len();808 for id in token_ids.into_iter() {809 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;810 if id != expected_index {811 return Err("item id should be next".into());812 }813 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;814 }815 let data = (0..total_tokens)816 .map(|_| CreateItemData::<T> {817 properties: BoundedVec::default(),818 owner: to.clone(),819 })820 .collect();821822 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)823 .map_err(dispatch_to_evm::<T>)?;824 Ok(true)825 }826827 828 829 830 831 832 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]833 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]834 fn mint_bulk_with_token_uri(835 &mut self,836 caller: caller,837 to: address,838 tokens: Vec<(uint256, string)>,839 ) -> Result<bool> {840 let key = key::url();841 let caller = T::CrossAccountId::from_eth(caller);842 let to = T::CrossAccountId::from_eth(to);843 let mut expected_index = <TokensMinted<T>>::get(self.id)844 .checked_add(1)845 .ok_or("item id overflow")?;846 let budget = self847 .recorder848 .weight_calls_budget(<StructureWeight<T>>::find_parent());849850 let mut data = Vec::with_capacity(tokens.len());851 for (id, token_uri) in tokens {852 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;853 if id != expected_index {854 return Err("item id should be next".into());855 }856 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;857858 let mut properties = CollectionPropertiesVec::default();859 properties860 .try_push(Property {861 key: key.clone(),862 value: token_uri863 .into_bytes()864 .try_into()865 .map_err(|_| "token uri is too long")?,866 })867 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;868869 data.push(CreateItemData::<T> {870 properties,871 owner: to.clone(),872 });873 }874875 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)876 .map_err(dispatch_to_evm::<T>)?;877 Ok(true)878 }879}880881#[solidity_interface(882 name = UniqueNFT,883 is(884 ERC721,885 ERC721Enumerable,886 ERC721UniqueExtensions,887 ERC721UniqueMintable,888 ERC721Burnable,889 ERC721Metadata(if(this.flags.erc721metadata)),890 Collection(via(common_mut returns CollectionHandle<T>)),891 TokenProperties,892 )893)]894impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}895896897generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);898generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);899900impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>901where902 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,903{904 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");905906 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {907 call::<T, UniqueNFTCall<T>, _, _>(handle, self)908 }909}