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 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]122 fn set_properties(123 &mut self,124 caller: caller,125 token_id: uint256,126 properties: Vec<(string, bytes)>,127 ) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130131 let nesting_budget = self132 .recorder133 .weight_calls_budget(<StructureWeight<T>>::find_parent());134135 let properties = properties136 .into_iter()137 .map(|(key, value)| {138 let key = <Vec<u8>>::from(key)139 .try_into()140 .map_err(|_| "key too large")?;141142 let value = value.0.try_into().map_err(|_| "value too large")?;143144 Ok(Property { key, value })145 })146 .collect::<Result<Vec<_>>>()?;147148 <Pallet<T>>::set_token_properties(149 self,150 &caller,151 TokenId(token_id),152 properties.into_iter(),153 <Pallet<T>>::token_exists(&self, TokenId(token_id)),154 &nesting_budget,155 )156 .map_err(dispatch_to_evm::<T>)157 }158159 160 161 162 163 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;166 let key = <Vec<u8>>::from(key)167 .try_into()168 .map_err(|_| "key too long")?;169170 let nesting_budget = self171 .recorder172 .weight_calls_budget(<StructureWeight<T>>::find_parent());173174 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)175 .map_err(dispatch_to_evm::<T>)176 }177178 179 180 181 182 183 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {184 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;185 let key = <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| "key too long")?;188189 let props = <TokenProperties<T>>::get((self.id, token_id));190 let prop = props.get(&key).ok_or("key not found")?;191192 Ok(prop.to_vec().into())193 }194}195196#[derive(ToLog)]197pub enum ERC721Events {198 199 200 201 202 203 Transfer {204 #[indexed]205 from: address,206 #[indexed]207 to: address,208 #[indexed]209 token_id: uint256,210 },211 212 213 214 215 Approval {216 #[indexed]217 owner: address,218 #[indexed]219 approved: address,220 #[indexed]221 token_id: uint256,222 },223 224 225 #[allow(dead_code)]226 ApprovalForAll {227 #[indexed]228 owner: address,229 #[indexed]230 operator: address,231 approved: bool,232 },233}234235#[derive(ToLog)]236pub enum ERC721UniqueMintableEvents {237 #[allow(dead_code)]238 MintingFinished {},239}240241242243#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]244impl<T: Config> NonfungibleHandle<T>245where246 T::AccountId: From<[u8; 32]>,247{248 249 250 #[solidity(hide, rename_selector = "name")]251 fn name_proxy(&self) -> Result<string> {252 self.name()253 }254255 256 257 #[solidity(hide, rename_selector = "symbol")]258 fn symbol_proxy(&self) -> Result<string> {259 self.symbol()260 }261262 263 264 265 266 267 268 269 270 271 #[solidity(rename_selector = "tokenURI")]272 fn token_uri(&self, token_id: uint256) -> Result<string> {273 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;274275 match get_token_property(self, token_id_u32, &key::url()).as_deref() {276 Err(_) | Ok("") => (),277 Ok(url) => {278 return Ok(url.into());279 }280 };281282 let base_uri =283 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())284 .map(BoundedVec::into_inner)285 .map(string::from_utf8)286 .transpose()287 .map_err(|e| {288 Error::Revert(alloc::format!(289 "Can not convert value \"baseURI\" to string with error \"{}\"",290 e291 ))292 })?;293294 let base_uri = match base_uri.as_deref() {295 None | Some("") => {296 return Ok("".into());297 }298 Some(base_uri) => base_uri.into(),299 };300301 Ok(302 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {303 Err(_) | Ok("") => base_uri,304 Ok(suffix) => base_uri + suffix,305 },306 )307 }308}309310311312#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]313impl<T: Config> NonfungibleHandle<T> {314 315 316 317 318 fn token_by_index(&self, index: uint256) -> Result<uint256> {319 Ok(index)320 }321322 323 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {324 325 Err("not implemented".into())326 }327328 329 330 331 fn total_supply(&self) -> Result<uint256> {332 self.consume_store_reads(1)?;333 Ok(<Pallet<T>>::total_supply(self).into())334 }335}336337338339#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]340impl<T: Config> NonfungibleHandle<T> {341 342 343 344 345 346 fn balance_of(&self, owner: address) -> Result<uint256> {347 self.consume_store_reads(1)?;348 let owner = T::CrossAccountId::from_eth(owner);349 let balance = <AccountBalance<T>>::get((self.id, owner));350 Ok(balance.into())351 }352 353 354 355 356 357 fn owner_of(&self, token_id: uint256) -> Result<address> {358 self.consume_store_reads(1)?;359 let token: TokenId = token_id.try_into()?;360 Ok(*<TokenData<T>>::get((self.id, token))361 .ok_or("token not found")?362 .owner363 .as_eth())364 }365 366 #[solidity(rename_selector = "safeTransferFrom")]367 fn safe_transfer_from_with_data(368 &mut self,369 _from: address,370 _to: address,371 _token_id: uint256,372 _data: bytes,373 ) -> Result<void> {374 375 Err("not implemented".into())376 }377 378 fn safe_transfer_from(379 &mut self,380 _from: address,381 _to: address,382 _token_id: uint256,383 ) -> Result<void> {384 385 Err("not implemented".into())386 }387388 389 390 391 392 393 394 395 396 397 #[weight(<SelfWeightOf<T>>::transfer_from())]398 fn transfer_from(399 &mut self,400 caller: caller,401 from: address,402 to: address,403 token_id: uint256,404 ) -> Result<void> {405 let caller = T::CrossAccountId::from_eth(caller);406 let from = T::CrossAccountId::from_eth(from);407 let to = T::CrossAccountId::from_eth(to);408 let token = token_id.try_into()?;409 let budget = self410 .recorder411 .weight_calls_budget(<StructureWeight<T>>::find_parent());412413 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)414 .map_err(dispatch_to_evm::<T>)?;415 Ok(())416 }417418 419 420 421 422 423 424 #[weight(<SelfWeightOf<T>>::approve())]425 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {426 let caller = T::CrossAccountId::from_eth(caller);427 let approved = T::CrossAccountId::from_eth(approved);428 let token = token_id.try_into()?;429430 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))431 .map_err(dispatch_to_evm::<T>)?;432 Ok(())433 }434435 436 fn set_approval_for_all(437 &mut self,438 _caller: caller,439 _operator: address,440 _approved: bool,441 ) -> Result<void> {442 443 Err("not implemented".into())444 }445446 447 fn get_approved(&self, _token_id: uint256) -> Result<address> {448 449 Err("not implemented".into())450 }451452 453 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {454 455 Err("not implemented".into())456 }457}458459460#[solidity_interface(name = ERC721Burnable)]461impl<T: Config> NonfungibleHandle<T> {462 463 464 465 466 #[weight(<SelfWeightOf<T>>::burn_item())]467 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {468 let caller = T::CrossAccountId::from_eth(caller);469 let token = token_id.try_into()?;470471 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;472 Ok(())473 }474}475476477#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]478impl<T: Config> NonfungibleHandle<T> {479 fn minting_finished(&self) -> Result<bool> {480 Ok(false)481 }482483 484 485 486 #[weight(<SelfWeightOf<T>>::create_item())]487 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {488 let token_id: uint256 = <TokensMinted<T>>::get(self.id)489 .checked_add(1)490 .ok_or("item id overflow")?491 .into();492 self.mint_check_id(caller, to, token_id)?;493 Ok(token_id)494 }495496 497 498 499 500 501 #[solidity(hide, rename_selector = "mint")]502 #[weight(<SelfWeightOf<T>>::create_item())]503 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {504 let caller = T::CrossAccountId::from_eth(caller);505 let to = T::CrossAccountId::from_eth(to);506 let token_id: u32 = token_id.try_into()?;507 let budget = self508 .recorder509 .weight_calls_budget(<StructureWeight<T>>::find_parent());510511 if <TokensMinted<T>>::get(self.id)512 .checked_add(1)513 .ok_or("item id overflow")?514 != token_id515 {516 return Err("item id should be next".into());517 }518519 <Pallet<T>>::create_item(520 self,521 &caller,522 CreateItemData::<T> {523 properties: BoundedVec::default(),524 owner: to,525 },526 &budget,527 )528 .map_err(dispatch_to_evm::<T>)?;529530 Ok(true)531 }532533 534 535 536 537 #[solidity(rename_selector = "mintWithTokenURI")]538 #[weight(<SelfWeightOf<T>>::create_item())]539 fn mint_with_token_uri(540 &mut self,541 caller: caller,542 to: address,543 token_uri: string,544 ) -> Result<uint256> {545 let token_id: uint256 = <TokensMinted<T>>::get(self.id)546 .checked_add(1)547 .ok_or("item id overflow")?548 .into();549 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;550 Ok(token_id)551 }552553 554 555 556 557 558 559 #[solidity(hide, rename_selector = "mintWithTokenURI")]560 #[weight(<SelfWeightOf<T>>::create_item())]561 fn mint_with_token_uri_check_id(562 &mut self,563 caller: caller,564 to: address,565 token_id: uint256,566 token_uri: string,567 ) -> Result<bool> {568 let key = key::url();569 let permission = get_token_permission::<T>(self.id, &key)?;570 if !permission.collection_admin {571 return Err("Operation is not allowed".into());572 }573574 let caller = T::CrossAccountId::from_eth(caller);575 let to = T::CrossAccountId::from_eth(to);576 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;577 let budget = self578 .recorder579 .weight_calls_budget(<StructureWeight<T>>::find_parent());580581 if <TokensMinted<T>>::get(self.id)582 .checked_add(1)583 .ok_or("item id overflow")?584 != token_id585 {586 return Err("item id should be next".into());587 }588589 let mut properties = CollectionPropertiesVec::default();590 properties591 .try_push(Property {592 key,593 value: token_uri594 .into_bytes()595 .try_into()596 .map_err(|_| "token uri is too long")?,597 })598 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;599600 <Pallet<T>>::create_item(601 self,602 &caller,603 CreateItemData::<T> {604 properties,605 owner: to,606 },607 &budget,608 )609 .map_err(dispatch_to_evm::<T>)?;610 Ok(true)611 }612613 614 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {615 Err("not implementable".into())616 }617}618619fn get_token_property<T: Config>(620 collection: &CollectionHandle<T>,621 token_id: u32,622 key: &up_data_structs::PropertyKey,623) -> Result<string> {624 collection.consume_store_reads(1)?;625 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))626 .map_err(|_| Error::Revert("Token properties not found".into()))?;627 if let Some(property) = properties.get(key) {628 return Ok(string::from_utf8_lossy(property).into());629 }630631 Err("Property tokenURI not found".into())632}633634fn get_token_permission<T: Config>(635 collection_id: CollectionId,636 key: &PropertyKey,637) -> Result<PropertyPermission> {638 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)639 .map_err(|_| Error::Revert("No permissions for collection".into()))?;640 let a = token_property_permissions641 .get(key)642 .map(Clone::clone)643 .ok_or_else(|| {644 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();645 Error::Revert(alloc::format!("No permission for key {}", key))646 })?;647 Ok(a)648}649650651#[solidity_interface(name = ERC721UniqueExtensions)]652impl<T: Config> NonfungibleHandle<T>653where654 T::AccountId: From<[u8; 32]>,655{656 657 fn name(&self) -> Result<string> {658 Ok(decode_utf16(self.name.iter().copied())659 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))660 .collect::<string>())661 }662663 664 fn symbol(&self) -> Result<string> {665 Ok(string::from_utf8_lossy(&self.token_prefix).into())666 }667668 669 670 671 672 673 674 #[weight(<SelfWeightOf<T>>::approve())]675 fn approve_cross(676 &mut self,677 caller: caller,678 approved: (address, uint256),679 token_id: uint256,680 ) -> Result<void> {681 let caller = T::CrossAccountId::from_eth(caller);682 let approved = convert_tuple_to_cross_account::<T>(approved)?;683 let token = token_id.try_into()?;684685 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))686 .map_err(dispatch_to_evm::<T>)?;687 Ok(())688 }689690 691 692 693 694 695 #[weight(<SelfWeightOf<T>>::transfer())]696 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {697 let caller = T::CrossAccountId::from_eth(caller);698 let to = T::CrossAccountId::from_eth(to);699 let token = token_id.try_into()?;700 let budget = self701 .recorder702 .weight_calls_budget(<StructureWeight<T>>::find_parent());703704 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;705 Ok(())706 }707708 709 710 711 712 713 714 #[weight(<SelfWeightOf<T>>::transfer())]715 fn transfer_from_cross(716 &mut self,717 caller: caller,718 from: (address, uint256),719 to: (address, uint256),720 token_id: uint256,721 ) -> Result<void> {722 let caller = T::CrossAccountId::from_eth(caller);723 let from = convert_tuple_to_cross_account::<T>(from)?;724 let to = convert_tuple_to_cross_account::<T>(to)?;725 let token_id = token_id.try_into()?;726 let budget = self727 .recorder728 .weight_calls_budget(<StructureWeight<T>>::find_parent());729 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)730 .map_err(dispatch_to_evm::<T>)?;731 Ok(())732 }733734 735 736 737 738 739 740 #[weight(<SelfWeightOf<T>>::burn_from())]741 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {742 let caller = T::CrossAccountId::from_eth(caller);743 let from = T::CrossAccountId::from_eth(from);744 let token = token_id.try_into()?;745 let budget = self746 .recorder747 .weight_calls_budget(<StructureWeight<T>>::find_parent());748749 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)750 .map_err(dispatch_to_evm::<T>)?;751 Ok(())752 }753754 755 756 757 758 759 760 #[weight(<SelfWeightOf<T>>::burn_from())]761 fn burn_from_cross(762 &mut self,763 caller: caller,764 from: (address, uint256),765 token_id: uint256,766 ) -> Result<void> {767 let caller = T::CrossAccountId::from_eth(caller);768 let from = convert_tuple_to_cross_account::<T>(from)?;769 let token = token_id.try_into()?;770 let budget = self771 .recorder772 .weight_calls_budget(<StructureWeight<T>>::find_parent());773774 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)775 .map_err(dispatch_to_evm::<T>)?;776 Ok(())777 }778779 780 fn next_token_id(&self) -> Result<uint256> {781 self.consume_store_reads(1)?;782 Ok(<TokensMinted<T>>::get(self.id)783 .checked_add(1)784 .ok_or("item id overflow")?785 .into())786 }787788 789 790 791 792 793 #[solidity(hide)]794 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]795 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {796 let caller = T::CrossAccountId::from_eth(caller);797 let to = T::CrossAccountId::from_eth(to);798 let mut expected_index = <TokensMinted<T>>::get(self.id)799 .checked_add(1)800 .ok_or("item id overflow")?;801 let budget = self802 .recorder803 .weight_calls_budget(<StructureWeight<T>>::find_parent());804805 let total_tokens = token_ids.len();806 for id in token_ids.into_iter() {807 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;808 if id != expected_index {809 return Err("item id should be next".into());810 }811 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;812 }813 let data = (0..total_tokens)814 .map(|_| CreateItemData::<T> {815 properties: BoundedVec::default(),816 owner: to.clone(),817 })818 .collect();819820 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)821 .map_err(dispatch_to_evm::<T>)?;822 Ok(true)823 }824825 826 827 828 829 830 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]831 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]832 fn mint_bulk_with_token_uri(833 &mut self,834 caller: caller,835 to: address,836 tokens: Vec<(uint256, string)>,837 ) -> Result<bool> {838 let key = key::url();839 let caller = T::CrossAccountId::from_eth(caller);840 let to = T::CrossAccountId::from_eth(to);841 let mut expected_index = <TokensMinted<T>>::get(self.id)842 .checked_add(1)843 .ok_or("item id overflow")?;844 let budget = self845 .recorder846 .weight_calls_budget(<StructureWeight<T>>::find_parent());847848 let mut data = Vec::with_capacity(tokens.len());849 for (id, token_uri) in tokens {850 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;851 if id != expected_index {852 return Err("item id should be next".into());853 }854 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;855856 let mut properties = CollectionPropertiesVec::default();857 properties858 .try_push(Property {859 key: key.clone(),860 value: token_uri861 .into_bytes()862 .try_into()863 .map_err(|_| "token uri is too long")?,864 })865 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;866867 data.push(CreateItemData::<T> {868 properties,869 owner: to.clone(),870 });871 }872873 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)874 .map_err(dispatch_to_evm::<T>)?;875 Ok(true)876 }877}878879#[solidity_interface(880 name = UniqueNFT,881 is(882 ERC721,883 ERC721Enumerable,884 ERC721UniqueExtensions,885 ERC721UniqueMintable,886 ERC721Burnable,887 ERC721Metadata(if(this.flags.erc721metadata)),888 Collection(via(common_mut returns CollectionHandle<T>)),889 TokenProperties,890 )891)]892impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}893894895generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);896generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);897898impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>899where900 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,901{902 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");903904 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {905 call::<T, UniqueNFTCall<T>, _, _>(handle, self)906 }907}