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};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::call;41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4243use crate::{44 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,45 SelfWeightOf, weights::WeightInfo, TokenProperties,46};474849#[solidity_interface(name = TokenProperties)]50impl<T: Config> NonfungibleHandle<T> {51 52 53 54 55 56 57 fn set_token_property_permission(58 &mut self,59 caller: caller,60 key: string,61 is_mutable: bool,62 collection_admin: bool,63 token_owner: bool,64 ) -> Result<()> {65 let caller = T::CrossAccountId::from_eth(caller);66 <Pallet<T>>::set_property_permission(67 self,68 &caller,69 PropertyKeyPermission {70 key: <Vec<u8>>::from(key)71 .try_into()72 .map_err(|_| "too long key")?,73 permission: PropertyPermission {74 mutable: is_mutable,75 collection_admin,76 token_owner,77 },78 },79 )80 .map_err(dispatch_to_evm::<T>)81 }8283 84 85 86 87 88 fn set_property(89 &mut self,90 caller: caller,91 token_id: uint256,92 key: string,93 value: bytes,94 ) -> Result<()> {95 let caller = T::CrossAccountId::from_eth(caller);96 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;97 let key = <Vec<u8>>::from(key)98 .try_into()99 .map_err(|_| "key too long")?;100 let value = value.0.try_into().map_err(|_| "value too long")?;101102 let nesting_budget = self103 .recorder104 .weight_calls_budget(<StructureWeight<T>>::find_parent());105106 <Pallet<T>>::set_token_property(107 self,108 &caller,109 TokenId(token_id),110 Property { key, value },111 &nesting_budget,112 )113 .map_err(dispatch_to_evm::<T>)114 }115116 117 118 119 120 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]121 fn set_properties(122 &mut self,123 caller: caller,124 token_id: uint256,125 properties: Vec<(string, bytes)>,126 ) -> Result<()> {127 let caller = T::CrossAccountId::from_eth(caller);128 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;129130 let nesting_budget = self131 .recorder132 .weight_calls_budget(<StructureWeight<T>>::find_parent());133134 let properties = properties135 .into_iter()136 .map(|(key, value)| {137 let key = <Vec<u8>>::from(key)138 .try_into()139 .map_err(|_| "key too large")?;140141 let value = value.0.try_into().map_err(|_| "value too large")?;142143 Ok(Property { key, value })144 })145 .collect::<Result<Vec<_>>>()?;146147 <Pallet<T>>::set_token_properties(148 self,149 &caller,150 TokenId(token_id),151 properties.into_iter(),152 <Pallet<T>>::token_exists(&self, TokenId(token_id)),153 &nesting_budget,154 )155 .map_err(dispatch_to_evm::<T>)156 }157158 159 160 161 162 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {163 let caller = T::CrossAccountId::from_eth(caller);164 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too long")?;168169 let nesting_budget = self170 .recorder171 .weight_calls_budget(<StructureWeight<T>>::find_parent());172173 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)174 .map_err(dispatch_to_evm::<T>)175 }176177 178 179 180 181 182 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {183 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;184 let key = <Vec<u8>>::from(key)185 .try_into()186 .map_err(|_| "key too long")?;187188 let props = <TokenProperties<T>>::get((self.id, token_id));189 let prop = props.get(&key).ok_or("key not found")?;190191 Ok(prop.to_vec().into())192 }193}194195#[derive(ToLog)]196pub enum ERC721Events {197 198 199 200 201 202 Transfer {203 #[indexed]204 from: address,205 #[indexed]206 to: address,207 #[indexed]208 token_id: uint256,209 },210 211 212 213 214 Approval {215 #[indexed]216 owner: address,217 #[indexed]218 approved: address,219 #[indexed]220 token_id: uint256,221 },222 223 224 #[allow(dead_code)]225 ApprovalForAll {226 #[indexed]227 owner: address,228 #[indexed]229 operator: address,230 approved: bool,231 },232}233234#[derive(ToLog)]235pub enum ERC721UniqueMintableEvents {236 #[allow(dead_code)]237 MintingFinished {},238}239240241242#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]243impl<T: Config> NonfungibleHandle<T>244where245 T::AccountId: From<[u8; 32]>,246{247 248 249 #[solidity(hide, rename_selector = "name")]250 fn name_proxy(&self) -> Result<string> {251 self.name()252 }253254 255 256 #[solidity(hide, rename_selector = "symbol")]257 fn symbol_proxy(&self) -> Result<string> {258 self.symbol()259 }260261 262 263 264 265 266 267 268 269 270 #[solidity(rename_selector = "tokenURI")]271 fn token_uri(&self, token_id: uint256) -> Result<string> {272 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;273274 match get_token_property(self, token_id_u32, &key::url()).as_deref() {275 Err(_) | Ok("") => (),276 Ok(url) => {277 return Ok(url.into());278 }279 };280281 let base_uri =282 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())283 .map(BoundedVec::into_inner)284 .map(string::from_utf8)285 .transpose()286 .map_err(|e| {287 Error::Revert(alloc::format!(288 "Can not convert value \"baseURI\" to string with error \"{}\"",289 e290 ))291 })?;292293 let base_uri = match base_uri.as_deref() {294 None | Some("") => {295 return Ok("".into());296 }297 Some(base_uri) => base_uri.into(),298 };299300 Ok(301 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {302 Err(_) | Ok("") => base_uri,303 Ok(suffix) => base_uri + suffix,304 },305 )306 }307}308309310311#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]312impl<T: Config> NonfungibleHandle<T> {313 314 315 316 317 fn token_by_index(&self, index: uint256) -> Result<uint256> {318 Ok(index)319 }320321 322 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {323 324 Err("not implemented".into())325 }326327 328 329 330 fn total_supply(&self) -> Result<uint256> {331 self.consume_store_reads(1)?;332 Ok(<Pallet<T>>::total_supply(self).into())333 }334}335336337338#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]339impl<T: Config> NonfungibleHandle<T> {340 341 342 343 344 345 fn balance_of(&self, owner: address) -> Result<uint256> {346 self.consume_store_reads(1)?;347 let owner = T::CrossAccountId::from_eth(owner);348 let balance = <AccountBalance<T>>::get((self.id, owner));349 Ok(balance.into())350 }351 352 353 354 355 356 fn owner_of(&self, token_id: uint256) -> Result<address> {357 self.consume_store_reads(1)?;358 let token: TokenId = token_id.try_into()?;359 Ok(*<TokenData<T>>::get((self.id, token))360 .ok_or("token not found")?361 .owner362 .as_eth())363 }364 365 #[solidity(rename_selector = "safeTransferFrom")]366 fn safe_transfer_from_with_data(367 &mut self,368 _from: address,369 _to: address,370 _token_id: uint256,371 _data: bytes,372 ) -> Result<void> {373 374 Err("not implemented".into())375 }376 377 fn safe_transfer_from(378 &mut self,379 _from: address,380 _to: address,381 _token_id: uint256,382 ) -> Result<void> {383 384 Err("not implemented".into())385 }386387 388 389 390 391 392 393 394 395 396 #[weight(<SelfWeightOf<T>>::transfer_from())]397 fn transfer_from(398 &mut self,399 caller: caller,400 from: address,401 to: address,402 token_id: uint256,403 ) -> Result<void> {404 let caller = T::CrossAccountId::from_eth(caller);405 let from = T::CrossAccountId::from_eth(from);406 let to = T::CrossAccountId::from_eth(to);407 let token = token_id.try_into()?;408 let budget = self409 .recorder410 .weight_calls_budget(<StructureWeight<T>>::find_parent());411412 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)413 .map_err(dispatch_to_evm::<T>)?;414 Ok(())415 }416417 418 419 420 421 422 423 #[weight(<SelfWeightOf<T>>::approve())]424 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {425 let caller = T::CrossAccountId::from_eth(caller);426 let approved = T::CrossAccountId::from_eth(approved);427 let token = token_id.try_into()?;428429 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))430 .map_err(dispatch_to_evm::<T>)?;431 Ok(())432 }433434 435 fn set_approval_for_all(436 &mut self,437 _caller: caller,438 _operator: address,439 _approved: bool,440 ) -> Result<void> {441 442 Err("not implemented".into())443 }444445 446 fn get_approved(&self, _token_id: uint256) -> Result<address> {447 448 Err("not implemented".into())449 }450451 452 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {453 454 Err("not implemented".into())455 }456}457458459#[solidity_interface(name = ERC721Burnable)]460impl<T: Config> NonfungibleHandle<T> {461 462 463 464 465 #[weight(<SelfWeightOf<T>>::burn_item())]466 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {467 let caller = T::CrossAccountId::from_eth(caller);468 let token = token_id.try_into()?;469470 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;471 Ok(())472 }473}474475476#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]477impl<T: Config> NonfungibleHandle<T> {478 fn minting_finished(&self) -> Result<bool> {479 Ok(false)480 }481482 483 484 485 #[weight(<SelfWeightOf<T>>::create_item())]486 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {487 let token_id: uint256 = <TokensMinted<T>>::get(self.id)488 .checked_add(1)489 .ok_or("item id overflow")?490 .into();491 self.mint_check_id(caller, to, token_id)?;492 Ok(token_id)493 }494495 496 497 498 499 500 #[solidity(hide, rename_selector = "mint")]501 #[weight(<SelfWeightOf<T>>::create_item())]502 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {503 let caller = T::CrossAccountId::from_eth(caller);504 let to = T::CrossAccountId::from_eth(to);505 let token_id: u32 = token_id.try_into()?;506 let budget = self507 .recorder508 .weight_calls_budget(<StructureWeight<T>>::find_parent());509510 if <TokensMinted<T>>::get(self.id)511 .checked_add(1)512 .ok_or("item id overflow")?513 != token_id514 {515 return Err("item id should be next".into());516 }517518 <Pallet<T>>::create_item(519 self,520 &caller,521 CreateItemData::<T> {522 properties: BoundedVec::default(),523 owner: to,524 },525 &budget,526 )527 .map_err(dispatch_to_evm::<T>)?;528529 Ok(true)530 }531532 533 534 535 536 #[solidity(rename_selector = "mintWithTokenURI")]537 #[weight(<SelfWeightOf<T>>::create_item())]538 fn mint_with_token_uri(539 &mut self,540 caller: caller,541 to: address,542 token_uri: string,543 ) -> Result<uint256> {544 let token_id: uint256 = <TokensMinted<T>>::get(self.id)545 .checked_add(1)546 .ok_or("item id overflow")?547 .into();548 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;549 Ok(token_id)550 }551552 553 554 555 556 557 558 #[solidity(hide, rename_selector = "mintWithTokenURI")]559 #[weight(<SelfWeightOf<T>>::create_item())]560 fn mint_with_token_uri_check_id(561 &mut self,562 caller: caller,563 to: address,564 token_id: uint256,565 token_uri: string,566 ) -> Result<bool> {567 let key = key::url();568 let permission = get_token_permission::<T>(self.id, &key)?;569 if !permission.collection_admin {570 return Err("Operation is not allowed".into());571 }572573 let caller = T::CrossAccountId::from_eth(caller);574 let to = T::CrossAccountId::from_eth(to);575 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;576 let budget = self577 .recorder578 .weight_calls_budget(<StructureWeight<T>>::find_parent());579580 if <TokensMinted<T>>::get(self.id)581 .checked_add(1)582 .ok_or("item id overflow")?583 != token_id584 {585 return Err("item id should be next".into());586 }587588 let mut properties = CollectionPropertiesVec::default();589 properties590 .try_push(Property {591 key,592 value: token_uri593 .into_bytes()594 .try_into()595 .map_err(|_| "token uri is too long")?,596 })597 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;598599 <Pallet<T>>::create_item(600 self,601 &caller,602 CreateItemData::<T> {603 properties,604 owner: to,605 },606 &budget,607 )608 .map_err(dispatch_to_evm::<T>)?;609 Ok(true)610 }611612 613 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {614 Err("not implementable".into())615 }616}617618fn get_token_property<T: Config>(619 collection: &CollectionHandle<T>,620 token_id: u32,621 key: &up_data_structs::PropertyKey,622) -> Result<string> {623 collection.consume_store_reads(1)?;624 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))625 .map_err(|_| Error::Revert("Token properties not found".into()))?;626 if let Some(property) = properties.get(key) {627 return Ok(string::from_utf8_lossy(property).into());628 }629630 Err("Property tokenURI not found".into())631}632633fn get_token_permission<T: Config>(634 collection_id: CollectionId,635 key: &PropertyKey,636) -> Result<PropertyPermission> {637 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)638 .map_err(|_| Error::Revert("No permissions for collection".into()))?;639 let a = token_property_permissions640 .get(key)641 .map(Clone::clone)642 .ok_or_else(|| {643 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();644 Error::Revert(alloc::format!("No permission for key {}", key))645 })?;646 Ok(a)647}648649650#[solidity_interface(name = ERC721UniqueExtensions)]651impl<T: Config> NonfungibleHandle<T>652where653 T::AccountId: From<[u8; 32]>,654{655 656 fn name(&self) -> Result<string> {657 Ok(decode_utf16(self.name.iter().copied())658 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))659 .collect::<string>())660 }661662 663 fn symbol(&self) -> Result<string> {664 Ok(string::from_utf8_lossy(&self.token_prefix).into())665 }666667 668 669 670 671 672 673 #[weight(<SelfWeightOf<T>>::approve())]674 fn approve_cross(675 &mut self,676 caller: caller,677 approved: EthCrossAccount,678 token_id: uint256,679 ) -> Result<void> {680 let caller = T::CrossAccountId::from_eth(caller);681 let approved = approved.into_sub_cross_account::<T>()?;682 let token = token_id.try_into()?;683684 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))685 .map_err(dispatch_to_evm::<T>)?;686 Ok(())687 }688689 690 691 692 693 694 #[weight(<SelfWeightOf<T>>::transfer())]695 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {696 let caller = T::CrossAccountId::from_eth(caller);697 let to = T::CrossAccountId::from_eth(to);698 let token = token_id.try_into()?;699 let budget = self700 .recorder701 .weight_calls_budget(<StructureWeight<T>>::find_parent());702703 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;704 Ok(())705 }706707 708 709 710 711 712 713 #[weight(<SelfWeightOf<T>>::transfer())]714 fn transfer_from_cross(715 &mut self,716 caller: caller,717 from: EthCrossAccount,718 to: EthCrossAccount,719 token_id: uint256,720 ) -> Result<void> {721 let caller = T::CrossAccountId::from_eth(caller);722 let from = from.into_sub_cross_account::<T>()?;723 let to = to.into_sub_cross_account::<T>()?;724 let token_id = token_id.try_into()?;725 let budget = self726 .recorder727 .weight_calls_budget(<StructureWeight<T>>::find_parent());728 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)729 .map_err(dispatch_to_evm::<T>)?;730 Ok(())731 }732733 734 735 736 737 738 739 #[weight(<SelfWeightOf<T>>::burn_from())]740 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {741 let caller = T::CrossAccountId::from_eth(caller);742 let from = T::CrossAccountId::from_eth(from);743 let token = token_id.try_into()?;744 let budget = self745 .recorder746 .weight_calls_budget(<StructureWeight<T>>::find_parent());747748 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)749 .map_err(dispatch_to_evm::<T>)?;750 Ok(())751 }752753 754 755 756 757 758 759 #[weight(<SelfWeightOf<T>>::burn_from())]760 fn burn_from_cross(761 &mut self,762 caller: caller,763 from: EthCrossAccount,764 token_id: uint256,765 ) -> Result<void> {766 let caller = T::CrossAccountId::from_eth(caller);767 let from = from.into_sub_cross_account::<T>()?;768 let token = token_id.try_into()?;769 let budget = self770 .recorder771 .weight_calls_budget(<StructureWeight<T>>::find_parent());772773 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)774 .map_err(dispatch_to_evm::<T>)?;775 Ok(())776 }777778 779 fn next_token_id(&self) -> Result<uint256> {780 self.consume_store_reads(1)?;781 Ok(<TokensMinted<T>>::get(self.id)782 .checked_add(1)783 .ok_or("item id overflow")?784 .into())785 }786787 788 789 790 791 792 #[solidity(hide)]793 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]794 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {795 let caller = T::CrossAccountId::from_eth(caller);796 let to = T::CrossAccountId::from_eth(to);797 let mut expected_index = <TokensMinted<T>>::get(self.id)798 .checked_add(1)799 .ok_or("item id overflow")?;800 let budget = self801 .recorder802 .weight_calls_budget(<StructureWeight<T>>::find_parent());803804 let total_tokens = token_ids.len();805 for id in token_ids.into_iter() {806 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;807 if id != expected_index {808 return Err("item id should be next".into());809 }810 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;811 }812 let data = (0..total_tokens)813 .map(|_| CreateItemData::<T> {814 properties: BoundedVec::default(),815 owner: to.clone(),816 })817 .collect();818819 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)820 .map_err(dispatch_to_evm::<T>)?;821 Ok(true)822 }823824 825 826 827 828 829 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]830 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]831 fn mint_bulk_with_token_uri(832 &mut self,833 caller: caller,834 to: address,835 tokens: Vec<(uint256, string)>,836 ) -> Result<bool> {837 let key = key::url();838 let caller = T::CrossAccountId::from_eth(caller);839 let to = T::CrossAccountId::from_eth(to);840 let mut expected_index = <TokensMinted<T>>::get(self.id)841 .checked_add(1)842 .ok_or("item id overflow")?;843 let budget = self844 .recorder845 .weight_calls_budget(<StructureWeight<T>>::find_parent());846847 let mut data = Vec::with_capacity(tokens.len());848 for (id, token_uri) in tokens {849 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;850 if id != expected_index {851 return Err("item id should be next".into());852 }853 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;854855 let mut properties = CollectionPropertiesVec::default();856 properties857 .try_push(Property {858 key: key.clone(),859 value: token_uri860 .into_bytes()861 .try_into()862 .map_err(|_| "token uri is too long")?,863 })864 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;865866 data.push(CreateItemData::<T> {867 properties,868 owner: to.clone(),869 });870 }871872 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)873 .map_err(dispatch_to_evm::<T>)?;874 Ok(true)875 }876}877878#[solidity_interface(879 name = UniqueNFT,880 is(881 ERC721,882 ERC721Enumerable,883 ERC721UniqueExtensions,884 ERC721UniqueMintable,885 ERC721Burnable,886 ERC721Metadata(if(this.flags.erc721metadata)),887 Collection(via(common_mut returns CollectionHandle<T>)),888 TokenProperties,889 )890)]891impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}892893894generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);895generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);896897impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>898where899 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,900{901 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");902903 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {904 call::<T, UniqueNFTCall<T>, _, _>(handle, self)905 }906}