12345678910111213141516171819202122extern crate alloc;23use alloc::{format, string::ToString};24use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};29use frame_support::BoundedVec;30use up_data_structs::{31 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,32 CollectionPropertiesVec,33};34use pallet_evm_coder_substrate::dispatch_to_evm;35use sp_std::vec::Vec;36use pallet_common::{37 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},38 CollectionHandle, CollectionPropertyPermissions,39 eth::convert_tuple_to_cross_account,40};41use pallet_evm::{account::CrossAccountId, PrecompileHandle};42use pallet_evm_coder_substrate::call;43use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4445use crate::{46 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,47 SelfWeightOf, weights::WeightInfo, TokenProperties,48};495051#[solidity_interface(name = TokenProperties)]52impl<T: Config> NonfungibleHandle<T> {53 54 55 56 57 58 59 fn set_token_property_permission(60 &mut self,61 caller: caller,62 key: string,63 is_mutable: bool,64 collection_admin: bool,65 token_owner: bool,66 ) -> Result<()> {67 let caller = T::CrossAccountId::from_eth(caller);68 <Pallet<T>>::set_property_permission(69 self,70 &caller,71 PropertyKeyPermission {72 key: <Vec<u8>>::from(key)73 .try_into()74 .map_err(|_| "too long key")?,75 permission: PropertyPermission {76 mutable: is_mutable,77 collection_admin,78 token_owner,79 },80 },81 )82 .map_err(dispatch_to_evm::<T>)83 }8485 86 87 88 89 90 fn set_property(91 &mut self,92 caller: caller,93 token_id: uint256,94 key: string,95 value: bytes,96 ) -> Result<()> {97 let caller = T::CrossAccountId::from_eth(caller);98 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;99 let key = <Vec<u8>>::from(key)100 .try_into()101 .map_err(|_| "key too long")?;102 let value = value.0.try_into().map_err(|_| "value too long")?;103104 let nesting_budget = self105 .recorder106 .weight_calls_budget(<StructureWeight<T>>::find_parent());107108 <Pallet<T>>::set_token_property(109 self,110 &caller,111 TokenId(token_id),112 Property { key, value },113 &nesting_budget,114 )115 .map_err(dispatch_to_evm::<T>)116 }117118 119 120 121 122 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]123 fn set_properties(124 &mut self,125 caller: caller,126 token_id: uint256,127 properties: Vec<(string, bytes)>,128 ) -> Result<()> {129 let caller = T::CrossAccountId::from_eth(caller);130 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;131132 let nesting_budget = self133 .recorder134 .weight_calls_budget(<StructureWeight<T>>::find_parent());135136 let properties = properties137 .into_iter()138 .map(|(key, value)| {139 let key = <Vec<u8>>::from(key)140 .try_into()141 .map_err(|_| "key too large")?;142143 let value = value.0.try_into().map_err(|_| "value too large")?;144145 Ok(Property { key, value })146 })147 .collect::<Result<Vec<_>>>()?;148149 <Pallet<T>>::set_token_properties(150 self,151 &caller,152 TokenId(token_id),153 properties.into_iter(),154 <Pallet<T>>::token_exists(&self, TokenId(token_id)),155 &nesting_budget,156 )157 .map_err(dispatch_to_evm::<T>)158 }159160 161 162 163 164 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {165 let caller = T::CrossAccountId::from_eth(caller);166 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too long")?;170171 let nesting_budget = self172 .recorder173 .weight_calls_budget(<StructureWeight<T>>::find_parent());174175 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)176 .map_err(dispatch_to_evm::<T>)177 }178179 180 181 182 183 184 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {185 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;186 let key = <Vec<u8>>::from(key)187 .try_into()188 .map_err(|_| "key too long")?;189190 let props = <TokenProperties<T>>::get((self.id, token_id));191 let prop = props.get(&key).ok_or("key not found")?;192193 Ok(prop.to_vec().into())194 }195}196197#[derive(ToLog)]198pub enum ERC721Events {199 200 201 202 203 204 Transfer {205 #[indexed]206 from: address,207 #[indexed]208 to: address,209 #[indexed]210 token_id: uint256,211 },212 213 214 215 216 Approval {217 #[indexed]218 owner: address,219 #[indexed]220 approved: address,221 #[indexed]222 token_id: uint256,223 },224 225 226 #[allow(dead_code)]227 ApprovalForAll {228 #[indexed]229 owner: address,230 #[indexed]231 operator: address,232 approved: bool,233 },234}235236#[derive(ToLog)]237pub enum ERC721UniqueMintableEvents {238 #[allow(dead_code)]239 MintingFinished {},240}241242243244#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]245impl<T: Config> NonfungibleHandle<T>246where247 T::AccountId: From<[u8; 32]>,248{249 250 251 #[solidity(hide, rename_selector = "name")]252 fn name_proxy(&self) -> Result<string> {253 self.name()254 }255256 257 258 #[solidity(hide, rename_selector = "symbol")]259 fn symbol_proxy(&self) -> Result<string> {260 self.symbol()261 }262263 264 265 266 267 268 269 270 271 272 #[solidity(rename_selector = "tokenURI")]273 fn token_uri(&self, token_id: uint256) -> Result<string> {274 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;275276 match get_token_property(self, token_id_u32, &key::url()).as_deref() {277 Err(_) | Ok("") => (),278 Ok(url) => {279 return Ok(url.into());280 }281 };282283 let base_uri =284 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())285 .map(BoundedVec::into_inner)286 .map(string::from_utf8)287 .transpose()288 .map_err(|e| {289 Error::Revert(alloc::format!(290 "Can not convert value \"baseURI\" to string with error \"{}\"",291 e292 ))293 })?;294295 let base_uri = match base_uri.as_deref() {296 None | Some("") => {297 return Ok("".into());298 }299 Some(base_uri) => base_uri.into(),300 };301302 Ok(303 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {304 Err(_) | Ok("") => base_uri,305 Ok(suffix) => base_uri + suffix,306 },307 )308 }309}310311312313#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]314impl<T: Config> NonfungibleHandle<T> {315 316 317 318 319 fn token_by_index(&self, index: uint256) -> Result<uint256> {320 Ok(index)321 }322323 324 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {325 326 Err("not implemented".into())327 }328329 330 331 332 fn total_supply(&self) -> Result<uint256> {333 self.consume_store_reads(1)?;334 Ok(<Pallet<T>>::total_supply(self).into())335 }336}337338339340#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]341impl<T: Config> NonfungibleHandle<T> {342 343 344 345 346 347 fn balance_of(&self, owner: address) -> Result<uint256> {348 self.consume_store_reads(1)?;349 let owner = T::CrossAccountId::from_eth(owner);350 let balance = <AccountBalance<T>>::get((self.id, owner));351 Ok(balance.into())352 }353 354 355 356 357 358 fn owner_of(&self, token_id: uint256) -> Result<address> {359 self.consume_store_reads(1)?;360 let token: TokenId = token_id.try_into()?;361 Ok(*<TokenData<T>>::get((self.id, token))362 .ok_or("token not found")?363 .owner364 .as_eth())365 }366 367 #[solidity(rename_selector = "safeTransferFrom")]368 fn safe_transfer_from_with_data(369 &mut self,370 _from: address,371 _to: address,372 _token_id: uint256,373 _data: bytes,374 ) -> Result<void> {375 376 Err("not implemented".into())377 }378 379 fn safe_transfer_from(380 &mut self,381 _from: address,382 _to: address,383 _token_id: uint256,384 ) -> Result<void> {385 386 Err("not implemented".into())387 }388389 390 391 392 393 394 395 396 397 398 #[weight(<SelfWeightOf<T>>::transfer_from())]399 fn transfer_from(400 &mut self,401 caller: caller,402 from: address,403 to: address,404 token_id: uint256,405 ) -> Result<void> {406 let caller = T::CrossAccountId::from_eth(caller);407 let from = T::CrossAccountId::from_eth(from);408 let to = T::CrossAccountId::from_eth(to);409 let token = token_id.try_into()?;410 let budget = self411 .recorder412 .weight_calls_budget(<StructureWeight<T>>::find_parent());413414 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)415 .map_err(dispatch_to_evm::<T>)?;416 Ok(())417 }418419 420 421 422 423 424 425 #[weight(<SelfWeightOf<T>>::approve())]426 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {427 let caller = T::CrossAccountId::from_eth(caller);428 let approved = T::CrossAccountId::from_eth(approved);429 let token = token_id.try_into()?;430431 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))432 .map_err(dispatch_to_evm::<T>)?;433 Ok(())434 }435436 437 fn set_approval_for_all(438 &mut self,439 _caller: caller,440 _operator: address,441 _approved: bool,442 ) -> Result<void> {443 444 Err("not implemented".into())445 }446447 448 fn get_approved(&self, _token_id: uint256) -> Result<address> {449 450 Err("not implemented".into())451 }452453 454 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455 456 Err("not implemented".into())457 }458}459460461#[solidity_interface(name = ERC721Burnable)]462impl<T: Config> NonfungibleHandle<T> {463 464 465 466 467 #[weight(<SelfWeightOf<T>>::burn_item())]468 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {469 let caller = T::CrossAccountId::from_eth(caller);470 let token = token_id.try_into()?;471472 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;473 Ok(())474 }475}476477478#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]479impl<T: Config> NonfungibleHandle<T> {480 fn minting_finished(&self) -> Result<bool> {481 Ok(false)482 }483484 485 486 487 #[weight(<SelfWeightOf<T>>::create_item())]488 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {489 let token_id: uint256 = <TokensMinted<T>>::get(self.id)490 .checked_add(1)491 .ok_or("item id overflow")?492 .into();493 self.mint_check_id(caller, to, token_id)?;494 Ok(token_id)495 }496497 498 499 500 501 502 #[solidity(hide, rename_selector = "mint")]503 #[weight(<SelfWeightOf<T>>::create_item())]504 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {505 let caller = T::CrossAccountId::from_eth(caller);506 let to = T::CrossAccountId::from_eth(to);507 let token_id: u32 = token_id.try_into()?;508 let budget = self509 .recorder510 .weight_calls_budget(<StructureWeight<T>>::find_parent());511512 if <TokensMinted<T>>::get(self.id)513 .checked_add(1)514 .ok_or("item id overflow")?515 != token_id516 {517 return Err("item id should be next".into());518 }519520 <Pallet<T>>::create_item(521 self,522 &caller,523 CreateItemData::<T> {524 properties: BoundedVec::default(),525 owner: to,526 },527 &budget,528 )529 .map_err(dispatch_to_evm::<T>)?;530531 Ok(true)532 }533534 535 536 537 538 #[solidity(rename_selector = "mintWithTokenURI")]539 #[weight(<SelfWeightOf<T>>::create_item())]540 fn mint_with_token_uri(541 &mut self,542 caller: caller,543 to: address,544 token_uri: string,545 ) -> Result<uint256> {546 let token_id: uint256 = <TokensMinted<T>>::get(self.id)547 .checked_add(1)548 .ok_or("item id overflow")?549 .into();550 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;551 Ok(token_id)552 }553554 555 556 557 558 559 560 #[solidity(hide, rename_selector = "mintWithTokenURI")]561 #[weight(<SelfWeightOf<T>>::create_item())]562 fn mint_with_token_uri_check_id(563 &mut self,564 caller: caller,565 to: address,566 token_id: uint256,567 token_uri: string,568 ) -> Result<bool> {569 let key = key::url();570 let permission = get_token_permission::<T>(self.id, &key)?;571 if !permission.collection_admin {572 return Err("Operation is not allowed".into());573 }574575 let caller = T::CrossAccountId::from_eth(caller);576 let to = T::CrossAccountId::from_eth(to);577 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;578 let budget = self579 .recorder580 .weight_calls_budget(<StructureWeight<T>>::find_parent());581582 if <TokensMinted<T>>::get(self.id)583 .checked_add(1)584 .ok_or("item id overflow")?585 != token_id586 {587 return Err("item id should be next".into());588 }589590 let mut properties = CollectionPropertiesVec::default();591 properties592 .try_push(Property {593 key,594 value: token_uri595 .into_bytes()596 .try_into()597 .map_err(|_| "token uri is too long")?,598 })599 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;600601 <Pallet<T>>::create_item(602 self,603 &caller,604 CreateItemData::<T> {605 properties,606 owner: to,607 },608 &budget,609 )610 .map_err(dispatch_to_evm::<T>)?;611 Ok(true)612 }613614 615 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {616 Err("not implementable".into())617 }618}619620fn get_token_property<T: Config>(621 collection: &CollectionHandle<T>,622 token_id: u32,623 key: &up_data_structs::PropertyKey,624) -> Result<string> {625 collection.consume_store_reads(1)?;626 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))627 .map_err(|_| Error::Revert("Token properties not found".into()))?;628 if let Some(property) = properties.get(key) {629 return Ok(string::from_utf8_lossy(property).into());630 }631632 Err("Property tokenURI not found".into())633}634635fn get_token_permission<T: Config>(636 collection_id: CollectionId,637 key: &PropertyKey,638) -> Result<PropertyPermission> {639 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)640 .map_err(|_| Error::Revert("No permissions for collection".into()))?;641 let a = token_property_permissions642 .get(key)643 .map(Clone::clone)644 .ok_or_else(|| {645 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();646 Error::Revert(alloc::format!("No permission for key {}", key))647 })?;648 Ok(a)649}650651652#[solidity_interface(name = ERC721UniqueExtensions)]653impl<T: Config> NonfungibleHandle<T>654where655 T::AccountId: From<[u8; 32]>,656{657 658 fn name(&self) -> Result<string> {659 Ok(decode_utf16(self.name.iter().copied())660 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))661 .collect::<string>())662 }663664 665 fn symbol(&self) -> Result<string> {666 Ok(string::from_utf8_lossy(&self.token_prefix).into())667 }668669 670 671 672 673 674 675 #[weight(<SelfWeightOf<T>>::approve())]676 fn approve_cross(677 &mut self,678 caller: caller,679 approved: (address, uint256),680 token_id: uint256,681 ) -> Result<void> {682 let caller = T::CrossAccountId::from_eth(caller);683 let approved = convert_tuple_to_cross_account::<T>(approved)?;684 let token = token_id.try_into()?;685686 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))687 .map_err(dispatch_to_evm::<T>)?;688 Ok(())689 }690691 692 693 694 695 696 #[weight(<SelfWeightOf<T>>::transfer())]697 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {698 let caller = T::CrossAccountId::from_eth(caller);699 let to = T::CrossAccountId::from_eth(to);700 let token = token_id.try_into()?;701 let budget = self702 .recorder703 .weight_calls_budget(<StructureWeight<T>>::find_parent());704705 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;706 Ok(())707 }708709 710 711 712 713 714 715 #[weight(<SelfWeightOf<T>>::transfer())]716 fn transfer_from_cross(717 &mut self,718 caller: caller,719 from: EthCrossAccount,720 to: EthCrossAccount,721 token_id: uint256,722 ) -> Result<void> {723 let caller = T::CrossAccountId::from_eth(caller);724 let from = from.into_sub_cross_account::<T>()?;725 let to = to.into_sub_cross_account::<T>()?;726 let token_id = token_id.try_into()?;727 let budget = self728 .recorder729 .weight_calls_budget(<StructureWeight<T>>::find_parent());730 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)731 .map_err(dispatch_to_evm::<T>)?;732 Ok(())733 }734735 736 737 738 739 740 741 #[weight(<SelfWeightOf<T>>::burn_from())]742 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {743 let caller = T::CrossAccountId::from_eth(caller);744 let from = T::CrossAccountId::from_eth(from);745 let token = token_id.try_into()?;746 let budget = self747 .recorder748 .weight_calls_budget(<StructureWeight<T>>::find_parent());749750 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)751 .map_err(dispatch_to_evm::<T>)?;752 Ok(())753 }754755 756 757 758 759 760 761 #[weight(<SelfWeightOf<T>>::burn_from())]762 fn burn_from_cross(763 &mut self,764 caller: caller,765 from: (address, uint256),766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let from = convert_tuple_to_cross_account::<T>(from)?;770 let token = token_id.try_into()?;771 let budget = self772 .recorder773 .weight_calls_budget(<StructureWeight<T>>::find_parent());774775 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)776 .map_err(dispatch_to_evm::<T>)?;777 Ok(())778 }779780 781 fn next_token_id(&self) -> Result<uint256> {782 self.consume_store_reads(1)?;783 Ok(<TokensMinted<T>>::get(self.id)784 .checked_add(1)785 .ok_or("item id overflow")?786 .into())787 }788789 790 791 792 793 794 #[solidity(hide)]795 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]796 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {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 total_tokens = token_ids.len();807 for id in token_ids.into_iter() {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")?;813 }814 let data = (0..total_tokens)815 .map(|_| CreateItemData::<T> {816 properties: BoundedVec::default(),817 owner: to.clone(),818 })819 .collect();820821 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)822 .map_err(dispatch_to_evm::<T>)?;823 Ok(true)824 }825826 827 828 829 830 831 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]832 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]833 fn mint_bulk_with_token_uri(834 &mut self,835 caller: caller,836 to: address,837 tokens: Vec<(uint256, string)>,838 ) -> Result<bool> {839 let key = key::url();840 let caller = T::CrossAccountId::from_eth(caller);841 let to = T::CrossAccountId::from_eth(to);842 let mut expected_index = <TokensMinted<T>>::get(self.id)843 .checked_add(1)844 .ok_or("item id overflow")?;845 let budget = self846 .recorder847 .weight_calls_budget(<StructureWeight<T>>::find_parent());848849 let mut data = Vec::with_capacity(tokens.len());850 for (id, token_uri) in tokens {851 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;852 if id != expected_index {853 return Err("item id should be next".into());854 }855 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;856857 let mut properties = CollectionPropertiesVec::default();858 properties859 .try_push(Property {860 key: key.clone(),861 value: token_uri862 .into_bytes()863 .try_into()864 .map_err(|_| "token uri is too long")?,865 })866 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;867868 data.push(CreateItemData::<T> {869 properties,870 owner: to.clone(),871 });872 }873874 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)875 .map_err(dispatch_to_evm::<T>)?;876 Ok(true)877 }878}879880#[solidity_interface(881 name = UniqueNFT,882 is(883 ERC721,884 ERC721Enumerable,885 ERC721UniqueExtensions,886 ERC721UniqueMintable,887 ERC721Burnable,888 ERC721Metadata(if(this.flags.erc721metadata)),889 Collection(via(common_mut returns CollectionHandle<T>)),890 TokenProperties,891 )892)]893impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}894895896generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);897generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);898899impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>900where901 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,902{903 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");904905 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {906 call::<T, UniqueNFTCall<T>, _, _>(handle, self)907 }908}