12345678910111213141516171819202122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37 CollectionHandle, CollectionPropertyPermissions,38 eth::convert_tuple_to_cross_account,39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::call;42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4344use crate::{45 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,46 SelfWeightOf, weights::WeightInfo, TokenProperties,47};484950#[solidity_interface(name = TokenProperties)]51impl<T: Config> NonfungibleHandle<T> {52 53 54 55 56 57 58 fn set_token_property_permission(59 &mut self,60 caller: caller,61 key: string,62 is_mutable: bool,63 collection_admin: bool,64 token_owner: bool,65 ) -> Result<()> {66 let caller = T::CrossAccountId::from_eth(caller);67 <Pallet<T>>::set_property_permission(68 self,69 &caller,70 PropertyKeyPermission {71 key: <Vec<u8>>::from(key)72 .try_into()73 .map_err(|_| "too long key")?,74 permission: PropertyPermission {75 mutable: is_mutable,76 collection_admin,77 token_owner,78 },79 },80 )81 .map_err(dispatch_to_evm::<T>)82 }8384 85 86 87 88 89 fn set_property(90 &mut self,91 caller: caller,92 token_id: uint256,93 key: string,94 value: bytes,95 ) -> Result<()> {96 let caller = T::CrossAccountId::from_eth(caller);97 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;98 let key = <Vec<u8>>::from(key)99 .try_into()100 .map_err(|_| "key too long")?;101 let value = value.0.try_into().map_err(|_| "value too long")?;102103 let nesting_budget = self104 .recorder105 .weight_calls_budget(<StructureWeight<T>>::find_parent());106107 <Pallet<T>>::set_token_property(108 self,109 &caller,110 TokenId(token_id),111 Property { key, value },112 &nesting_budget,113 )114 .map_err(dispatch_to_evm::<T>)115 }116117 118 119 120 121 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {122 let caller = T::CrossAccountId::from_eth(caller);123 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;124 let key = <Vec<u8>>::from(key)125 .try_into()126 .map_err(|_| "key too long")?;127128 let nesting_budget = self129 .recorder130 .weight_calls_budget(<StructureWeight<T>>::find_parent());131132 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)133 .map_err(dispatch_to_evm::<T>)134 }135136 137 138 139 140 141 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {142 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;143 let key = <Vec<u8>>::from(key)144 .try_into()145 .map_err(|_| "key too long")?;146147 let props = <TokenProperties<T>>::get((self.id, token_id));148 let prop = props.get(&key).ok_or("key not found")?;149150 Ok(prop.to_vec().into())151 }152}153154#[derive(ToLog)]155pub enum ERC721Events {156 157 158 159 160 161 Transfer {162 #[indexed]163 from: address,164 #[indexed]165 to: address,166 #[indexed]167 token_id: uint256,168 },169 170 171 172 173 Approval {174 #[indexed]175 owner: address,176 #[indexed]177 approved: address,178 #[indexed]179 token_id: uint256,180 },181 182 183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721UniqueMintableEvents {195 #[allow(dead_code)]196 MintingFinished {},197}198199200201#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]202impl<T: Config> NonfungibleHandle<T>203where204 T::AccountId: From<[u8; 32]>,205{206 207 208 #[solidity(hide, rename_selector = "name")]209 fn name_proxy(&self) -> Result<string> {210 self.name()211 }212213 214 215 #[solidity(hide, rename_selector = "symbol")]216 fn symbol_proxy(&self) -> Result<string> {217 self.symbol()218 }219220 221 222 223 224 225 226 227 228 229 #[solidity(rename_selector = "tokenURI")]230 fn token_uri(&self, token_id: uint256) -> Result<string> {231 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;232233 match get_token_property(self, token_id_u32, &key::url()).as_deref() {234 Err(_) | Ok("") => (),235 Ok(url) => {236 return Ok(url.into());237 }238 };239240 let base_uri =241 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())242 .map(BoundedVec::into_inner)243 .map(string::from_utf8)244 .transpose()245 .map_err(|e| {246 Error::Revert(alloc::format!(247 "Can not convert value \"baseURI\" to string with error \"{}\"",248 e249 ))250 })?;251252 let base_uri = match base_uri.as_deref() {253 None | Some("") => {254 return Ok("".into());255 }256 Some(base_uri) => base_uri.into(),257 };258259 Ok(260 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {261 Err(_) | Ok("") => base_uri,262 Ok(suffix) => base_uri + suffix,263 },264 )265 }266}267268269270#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]271impl<T: Config> NonfungibleHandle<T> {272 273 274 275 276 fn token_by_index(&self, index: uint256) -> Result<uint256> {277 Ok(index)278 }279280 281 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {282 283 Err("not implemented".into())284 }285286 287 288 289 fn total_supply(&self) -> Result<uint256> {290 self.consume_store_reads(1)?;291 Ok(<Pallet<T>>::total_supply(self).into())292 }293}294295296297#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]298impl<T: Config> NonfungibleHandle<T> {299 300 301 302 303 304 fn balance_of(&self, owner: address) -> Result<uint256> {305 self.consume_store_reads(1)?;306 let owner = T::CrossAccountId::from_eth(owner);307 let balance = <AccountBalance<T>>::get((self.id, owner));308 Ok(balance.into())309 }310 311 312 313 314 315 fn owner_of(&self, token_id: uint256) -> Result<address> {316 self.consume_store_reads(1)?;317 let token: TokenId = token_id.try_into()?;318 Ok(*<TokenData<T>>::get((self.id, token))319 .ok_or("token not found")?320 .owner321 .as_eth())322 }323 324 #[solidity(rename_selector = "safeTransferFrom")]325 fn safe_transfer_from_with_data(326 &mut self,327 _from: address,328 _to: address,329 _token_id: uint256,330 _data: bytes,331 ) -> Result<void> {332 333 Err("not implemented".into())334 }335 336 fn safe_transfer_from(337 &mut self,338 _from: address,339 _to: address,340 _token_id: uint256,341 ) -> Result<void> {342 343 Err("not implemented".into())344 }345346 347 348 349 350 351 352 353 354 355 #[weight(<SelfWeightOf<T>>::transfer_from())]356 fn transfer_from(357 &mut self,358 caller: caller,359 from: address,360 to: address,361 token_id: uint256,362 ) -> Result<void> {363 let caller = T::CrossAccountId::from_eth(caller);364 let from = T::CrossAccountId::from_eth(from);365 let to = T::CrossAccountId::from_eth(to);366 let token = token_id.try_into()?;367 let budget = self368 .recorder369 .weight_calls_budget(<StructureWeight<T>>::find_parent());370371 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)372 .map_err(dispatch_to_evm::<T>)?;373 Ok(())374 }375376 377 378 379 380 381 382 #[weight(<SelfWeightOf<T>>::approve())]383 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {384 let caller = T::CrossAccountId::from_eth(caller);385 let approved = T::CrossAccountId::from_eth(approved);386 let token = token_id.try_into()?;387388 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))389 .map_err(dispatch_to_evm::<T>)?;390 Ok(())391 }392393 394 fn set_approval_for_all(395 &mut self,396 _caller: caller,397 _operator: address,398 _approved: bool,399 ) -> Result<void> {400 401 Err("not implemented".into())402 }403404 405 fn get_approved(&self, _token_id: uint256) -> Result<address> {406 407 Err("not implemented".into())408 }409410 411 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {412 413 Err("not implemented".into())414 }415}416417418#[solidity_interface(name = ERC721Burnable)]419impl<T: Config> NonfungibleHandle<T> {420 421 422 423 424 #[weight(<SelfWeightOf<T>>::burn_item())]425 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {426 let caller = T::CrossAccountId::from_eth(caller);427 let token = token_id.try_into()?;428429 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;430 Ok(())431 }432}433434435#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]436impl<T: Config> NonfungibleHandle<T> {437 fn minting_finished(&self) -> Result<bool> {438 Ok(false)439 }440441 442 443 444 #[weight(<SelfWeightOf<T>>::create_item())]445 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {446 let token_id: uint256 = <TokensMinted<T>>::get(self.id)447 .checked_add(1)448 .ok_or("item id overflow")?449 .into();450 self.mint_check_id(caller, to, token_id)?;451 Ok(token_id)452 }453454 455 456 457 458 459 #[solidity(hide, rename_selector = "mint")]460 #[weight(<SelfWeightOf<T>>::create_item())]461 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {462 let caller = T::CrossAccountId::from_eth(caller);463 let to = T::CrossAccountId::from_eth(to);464 let token_id: u32 = token_id.try_into()?;465 let budget = self466 .recorder467 .weight_calls_budget(<StructureWeight<T>>::find_parent());468469 if <TokensMinted<T>>::get(self.id)470 .checked_add(1)471 .ok_or("item id overflow")?472 != token_id473 {474 return Err("item id should be next".into());475 }476477 <Pallet<T>>::create_item(478 self,479 &caller,480 CreateItemData::<T> {481 properties: BoundedVec::default(),482 owner: to,483 },484 &budget,485 )486 .map_err(dispatch_to_evm::<T>)?;487488 Ok(true)489 }490491 492 493 494 495 #[solidity(rename_selector = "mintWithTokenURI")]496 #[weight(<SelfWeightOf<T>>::create_item())]497 fn mint_with_token_uri(498 &mut self,499 caller: caller,500 to: address,501 token_uri: string,502 ) -> Result<uint256> {503 let token_id: uint256 = <TokensMinted<T>>::get(self.id)504 .checked_add(1)505 .ok_or("item id overflow")?506 .into();507 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;508 Ok(token_id)509 }510511 512 513 514 515 516 517 #[solidity(hide, rename_selector = "mintWithTokenURI")]518 #[weight(<SelfWeightOf<T>>::create_item())]519 fn mint_with_token_uri_check_id(520 &mut self,521 caller: caller,522 to: address,523 token_id: uint256,524 token_uri: string,525 ) -> Result<bool> {526 let key = key::url();527 let permission = get_token_permission::<T>(self.id, &key)?;528 if !permission.collection_admin {529 return Err("Operation is not allowed".into());530 }531532 let caller = T::CrossAccountId::from_eth(caller);533 let to = T::CrossAccountId::from_eth(to);534 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;535 let budget = self536 .recorder537 .weight_calls_budget(<StructureWeight<T>>::find_parent());538539 if <TokensMinted<T>>::get(self.id)540 .checked_add(1)541 .ok_or("item id overflow")?542 != token_id543 {544 return Err("item id should be next".into());545 }546547 let mut properties = CollectionPropertiesVec::default();548 properties549 .try_push(Property {550 key,551 value: token_uri552 .into_bytes()553 .try_into()554 .map_err(|_| "token uri is too long")?,555 })556 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;557558 <Pallet<T>>::create_item(559 self,560 &caller,561 CreateItemData::<T> {562 properties,563 owner: to,564 },565 &budget,566 )567 .map_err(dispatch_to_evm::<T>)?;568 Ok(true)569 }570571 572 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {573 Err("not implementable".into())574 }575}576577fn get_token_property<T: Config>(578 collection: &CollectionHandle<T>,579 token_id: u32,580 key: &up_data_structs::PropertyKey,581) -> Result<string> {582 collection.consume_store_reads(1)?;583 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))584 .map_err(|_| Error::Revert("Token properties not found".into()))?;585 if let Some(property) = properties.get(key) {586 return Ok(string::from_utf8_lossy(property).into());587 }588589 Err("Property tokenURI not found".into())590}591592fn get_token_permission<T: Config>(593 collection_id: CollectionId,594 key: &PropertyKey,595) -> Result<PropertyPermission> {596 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)597 .map_err(|_| Error::Revert("No permissions for collection".into()))?;598 let a = token_property_permissions599 .get(key)600 .map(Clone::clone)601 .ok_or_else(|| {602 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();603 Error::Revert(alloc::format!("No permission for key {}", key))604 })?;605 Ok(a)606}607608609#[solidity_interface(name = ERC721UniqueExtensions)]610impl<T: Config> NonfungibleHandle<T>611where612 T::AccountId: From<[u8; 32]>,613{614 615 fn name(&self) -> Result<string> {616 Ok(decode_utf16(self.name.iter().copied())617 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))618 .collect::<string>())619 }620621 622 fn symbol(&self) -> Result<string> {623 Ok(string::from_utf8_lossy(&self.token_prefix).into())624 }625626 627 628 629 630 631 632 #[weight(<SelfWeightOf<T>>::approve())]633 fn approve_cross(634 &mut self,635 caller: caller,636 approved: (address, uint256),637 token_id: uint256,638 ) -> Result<void> {639 let caller = T::CrossAccountId::from_eth(caller);640 let approved = convert_tuple_to_cross_account::<T>(approved)?;641 let token = token_id.try_into()?;642643 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))644 .map_err(dispatch_to_evm::<T>)?;645 Ok(())646 }647648 649 650 651 652 653 #[weight(<SelfWeightOf<T>>::transfer())]654 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {655 let caller = T::CrossAccountId::from_eth(caller);656 let to = T::CrossAccountId::from_eth(to);657 let token = token_id.try_into()?;658 let budget = self659 .recorder660 .weight_calls_budget(<StructureWeight<T>>::find_parent());661662 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;663 Ok(())664 }665666 667 668 669 670 671 672 #[weight(<SelfWeightOf<T>>::transfer())]673 fn transfer_from_cross(674 &mut self,675 caller: caller,676 from: (address, uint256),677 to: (address, uint256),678 token_id: uint256,679 ) -> Result<void> {680 let caller = T::CrossAccountId::from_eth(caller);681 let from = convert_tuple_to_cross_account::<T>(from)?;682 let to = convert_tuple_to_cross_account::<T>(to)?;683 let token_id = token_id.try_into()?;684 let budget = self685 .recorder686 .weight_calls_budget(<StructureWeight<T>>::find_parent());687 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)688 .map_err(dispatch_to_evm::<T>)?;689 Ok(())690 }691692 693 694 695 696 697 698 #[weight(<SelfWeightOf<T>>::burn_from())]699 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {700 let caller = T::CrossAccountId::from_eth(caller);701 let from = T::CrossAccountId::from_eth(from);702 let token = token_id.try_into()?;703 let budget = self704 .recorder705 .weight_calls_budget(<StructureWeight<T>>::find_parent());706707 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)708 .map_err(dispatch_to_evm::<T>)?;709 Ok(())710 }711712 713 714 715 716 717 718 #[weight(<SelfWeightOf<T>>::burn_from())]719 fn burn_from_cross(720 &mut self,721 caller: caller,722 from: (address, uint256),723 token_id: uint256,724 ) -> Result<void> {725 let caller = T::CrossAccountId::from_eth(caller);726 let from = convert_tuple_to_cross_account::<T>(from)?;727 let token = token_id.try_into()?;728 let budget = self729 .recorder730 .weight_calls_budget(<StructureWeight<T>>::find_parent());731732 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)733 .map_err(dispatch_to_evm::<T>)?;734 Ok(())735 }736737 738 fn next_token_id(&self) -> Result<uint256> {739 self.consume_store_reads(1)?;740 Ok(<TokensMinted<T>>::get(self.id)741 .checked_add(1)742 .ok_or("item id overflow")?743 .into())744 }745746 747 748 749 750 751 #[solidity(hide)]752 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]753 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {754 let caller = T::CrossAccountId::from_eth(caller);755 let to = T::CrossAccountId::from_eth(to);756 let mut expected_index = <TokensMinted<T>>::get(self.id)757 .checked_add(1)758 .ok_or("item id overflow")?;759 let budget = self760 .recorder761 .weight_calls_budget(<StructureWeight<T>>::find_parent());762763 let total_tokens = token_ids.len();764 for id in token_ids.into_iter() {765 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;766 if id != expected_index {767 return Err("item id should be next".into());768 }769 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;770 }771 let data = (0..total_tokens)772 .map(|_| CreateItemData::<T> {773 properties: BoundedVec::default(),774 owner: to.clone(),775 })776 .collect();777778 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)779 .map_err(dispatch_to_evm::<T>)?;780 Ok(true)781 }782783 784 785 786 787 788 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]789 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]790 fn mint_bulk_with_token_uri(791 &mut self,792 caller: caller,793 to: address,794 tokens: Vec<(uint256, string)>,795 ) -> Result<bool> {796 let key = key::url();797 let caller = T::CrossAccountId::from_eth(caller);798 let to = T::CrossAccountId::from_eth(to);799 let mut expected_index = <TokensMinted<T>>::get(self.id)800 .checked_add(1)801 .ok_or("item id overflow")?;802 let budget = self803 .recorder804 .weight_calls_budget(<StructureWeight<T>>::find_parent());805806 let mut data = Vec::with_capacity(tokens.len());807 for (id, token_uri) in tokens {808 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;809 if id != expected_index {810 return Err("item id should be next".into());811 }812 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;813814 let mut properties = CollectionPropertiesVec::default();815 properties816 .try_push(Property {817 key: key.clone(),818 value: token_uri819 .into_bytes()820 .try_into()821 .map_err(|_| "token uri is too long")?,822 })823 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;824825 data.push(CreateItemData::<T> {826 properties,827 owner: to.clone(),828 });829 }830831 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)832 .map_err(dispatch_to_evm::<T>)?;833 Ok(true)834 }835}836837#[solidity_interface(838 name = UniqueNFT,839 is(840 ERC721,841 ERC721Enumerable,842 ERC721UniqueExtensions,843 ERC721UniqueMintable,844 ERC721Burnable,845 ERC721Metadata(if(this.flags.erc721metadata)),846 Collection(via(common_mut returns CollectionHandle<T>)),847 TokenProperties,848 )849)]850impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}851852853generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);854generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);855856impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>857where858 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,859{860 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");861862 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {863 call::<T, UniqueNFTCall<T>, _, _>(handle, self)864 }865}