12345678910111213141516171819202122extern crate alloc;2324use 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::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31 CollectionHandle, CollectionPropertyPermissions,32 erc::{CommonEvmHandler, CollectionCall, static_property::key},33 eth::convert_tuple_to_cross_account,34};35use pallet_evm::{account::CrossAccountId, PrecompileHandle};36use pallet_evm_coder_substrate::{call, dispatch_to_evm};37use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};38use sp_core::H160;39use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};40use up_data_structs::{41 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,42 PropertyKeyPermission, PropertyPermission, TokenId,43};4445use crate::{46 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,47 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,48};4950pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);515253#[solidity_interface(name = TokenProperties)]54impl<T: Config> RefungibleHandle<T> {55 56 57 58 59 60 61 fn set_token_property_permission(62 &mut self,63 caller: caller,64 key: string,65 is_mutable: bool,66 collection_admin: bool,67 token_owner: bool,68 ) -> Result<()> {69 let caller = T::CrossAccountId::from_eth(caller);70 <Pallet<T>>::set_token_property_permissions(71 self,72 &caller,73 vec![PropertyKeyPermission {74 key: <Vec<u8>>::from(key)75 .try_into()76 .map_err(|_| "too long key")?,77 permission: PropertyPermission {78 mutable: is_mutable,79 collection_admin,80 token_owner,81 },82 }],83 )84 .map_err(dispatch_to_evm::<T>)85 }8687 88 89 90 91 92 fn set_property(93 &mut self,94 caller: caller,95 token_id: uint256,96 key: string,97 value: bytes,98 ) -> Result<()> {99 let caller = T::CrossAccountId::from_eth(caller);100 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;101 let key = <Vec<u8>>::from(key)102 .try_into()103 .map_err(|_| "key too long")?;104 let value = value.0.try_into().map_err(|_| "value too long")?;105106 let nesting_budget = self107 .recorder108 .weight_calls_budget(<StructureWeight<T>>::find_parent());109110 <Pallet<T>>::set_token_property(111 self,112 &caller,113 TokenId(token_id),114 Property { key, value },115 &nesting_budget,116 )117 .map_err(dispatch_to_evm::<T>)118 }119120 121 122 123 124 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {125 let caller = T::CrossAccountId::from_eth(caller);126 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;127 let key = <Vec<u8>>::from(key)128 .try_into()129 .map_err(|_| "key too long")?;130131 let nesting_budget = self132 .recorder133 .weight_calls_budget(<StructureWeight<T>>::find_parent());134135 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)136 .map_err(dispatch_to_evm::<T>)137 }138139 140 141 142 143 144 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {145 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;146 let key = <Vec<u8>>::from(key)147 .try_into()148 .map_err(|_| "key too long")?;149150 let props = <TokenProperties<T>>::get((self.id, token_id));151 let prop = props.get(&key).ok_or("key not found")?;152153 Ok(prop.to_vec().into())154 }155}156157#[derive(ToLog)]158pub enum ERC721Events {159 160 161 162 Transfer {163 #[indexed]164 from: address,165 #[indexed]166 to: address,167 #[indexed]168 token_id: uint256,169 },170 171 Approval {172 #[indexed]173 owner: address,174 #[indexed]175 approved: address,176 #[indexed]177 token_id: uint256,178 },179 180 #[allow(dead_code)]181 ApprovalForAll {182 #[indexed]183 owner: address,184 #[indexed]185 operator: address,186 approved: bool,187 },188}189190#[derive(ToLog)]191pub enum ERC721UniqueMintableEvents {192 193 #[allow(dead_code)]194 MintingFinished {},195}196197#[solidity_interface(name = ERC721Metadata)]198impl<T: Config> RefungibleHandle<T>199where200 T::AccountId: From<[u8; 32]>,201{202 203 204 #[solidity(hide, rename_selector = "name")]205 fn name_proxy(&self) -> Result<string> {206 self.name()207 }208209 210 211 #[solidity(hide, rename_selector = "symbol")]212 fn symbol_proxy(&self) -> Result<string> {213 self.symbol()214 }215216 217 218 219 220 221 222 223 224 225 #[solidity(rename_selector = "tokenURI")]226 fn token_uri(&self, token_id: uint256) -> Result<string> {227 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;228229 match get_token_property(self, token_id_u32, &key::url()).as_deref() {230 Err(_) | Ok("") => (),231 Ok(url) => {232 return Ok(url.into());233 }234 };235236 let base_uri =237 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())238 .map(BoundedVec::into_inner)239 .map(string::from_utf8)240 .transpose()241 .map_err(|e| {242 Error::Revert(alloc::format!(243 "Can not convert value \"baseURI\" to string with error \"{}\"",244 e245 ))246 })?;247248 let base_uri = match base_uri.as_deref() {249 None | Some("") => {250 return Ok("".into());251 }252 Some(base_uri) => base_uri.into(),253 };254255 Ok(256 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {257 Err(_) | Ok("") => base_uri,258 Ok(suffix) => base_uri + suffix,259 },260 )261 }262}263264265266#[solidity_interface(name = ERC721Enumerable)]267impl<T: Config> RefungibleHandle<T> {268 269 270 271 272 fn token_by_index(&self, index: uint256) -> Result<uint256> {273 Ok(index)274 }275276 277 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {278 279 Err("not implemented".into())280 }281282 283 284 285 fn total_supply(&self) -> Result<uint256> {286 self.consume_store_reads(1)?;287 Ok(<Pallet<T>>::total_supply(self).into())288 }289}290291292293#[solidity_interface(name = ERC721, events(ERC721Events))]294impl<T: Config> RefungibleHandle<T> {295 296 297 298 299 300 fn balance_of(&self, owner: address) -> Result<uint256> {301 self.consume_store_reads(1)?;302 let owner = T::CrossAccountId::from_eth(owner);303 let balance = <AccountBalance<T>>::get((self.id, owner));304 Ok(balance.into())305 }306307 308 309 310 311 312 313 314 fn owner_of(&self, token_id: uint256) -> Result<address> {315 self.consume_store_reads(2)?;316 let token = token_id.try_into()?;317 let owner = <Pallet<T>>::token_owner(self.id, token);318 Ok(owner319 .map(|address| *address.as_eth())320 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))321 }322323 324 fn safe_transfer_from_with_data(325 &mut self,326 _from: address,327 _to: address,328 _token_id: uint256,329 _data: bytes,330 ) -> Result<void> {331 332 Err("not implemented".into())333 }334335 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 356 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]357 fn transfer_from(358 &mut self,359 caller: caller,360 from: address,361 to: address,362 token_id: uint256,363 ) -> Result<void> {364 let caller = T::CrossAccountId::from_eth(caller);365 let from = T::CrossAccountId::from_eth(from);366 let to = T::CrossAccountId::from_eth(to);367 let token = token_id.try_into()?;368 let budget = self369 .recorder370 .weight_calls_budget(<StructureWeight<T>>::find_parent());371372 let balance = balance(&self, token, &from)?;373 ensure_single_owner(&self, token, balance)?;374375 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)376 .map_err(dispatch_to_evm::<T>)?;377378 Ok(())379 }380381 382 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {383 Err("not implemented".into())384 }385386 387 fn set_approval_for_all(388 &mut self,389 _caller: caller,390 _operator: address,391 _approved: bool,392 ) -> Result<void> {393 394 Err("not implemented".into())395 }396397 398 fn get_approved(&self, _token_id: uint256) -> Result<address> {399 400 Err("not implemented".into())401 }402403 404 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {405 406 Err("not implemented".into())407 }408}409410411pub fn balance<T: Config>(412 collection: &RefungibleHandle<T>,413 token: TokenId,414 owner: &T::CrossAccountId,415) -> Result<u128> {416 collection.consume_store_reads(1)?;417 let balance = <Balance<T>>::get((collection.id, token, &owner));418 Ok(balance)419}420421422pub fn ensure_single_owner<T: Config>(423 collection: &RefungibleHandle<T>,424 token: TokenId,425 owner_balance: u128,426) -> Result<()> {427 collection.consume_store_reads(1)?;428 let total_supply = <TotalSupply<T>>::get((collection.id, token));429 if total_supply != owner_balance {430 return Err("token has multiple owners".into());431 }432 Ok(())433}434435436#[solidity_interface(name = ERC721Burnable)]437impl<T: Config> RefungibleHandle<T> {438 439 440 441 442 #[weight(<SelfWeightOf<T>>::burn_item_fully())]443 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {444 let caller = T::CrossAccountId::from_eth(caller);445 let token = token_id.try_into()?;446447 let balance = balance(&self, token, &caller)?;448 ensure_single_owner(&self, token, balance)?;449450 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;451 Ok(())452 }453}454455456#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]457impl<T: Config> RefungibleHandle<T> {458 fn minting_finished(&self) -> Result<bool> {459 Ok(false)460 }461462 463 464 465 #[weight(<SelfWeightOf<T>>::create_item())]466 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {467 let token_id: uint256 = <TokensMinted<T>>::get(self.id)468 .checked_add(1)469 .ok_or("item id overflow")?470 .into();471 self.mint_check_id(caller, to, token_id)?;472 Ok(token_id)473 }474475 476 477 478 479 480 #[solidity(hide, rename_selector = "mint")]481 #[weight(<SelfWeightOf<T>>::create_item())]482 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {483 let caller = T::CrossAccountId::from_eth(caller);484 let to = T::CrossAccountId::from_eth(to);485 let token_id: u32 = token_id.try_into()?;486 let budget = self487 .recorder488 .weight_calls_budget(<StructureWeight<T>>::find_parent());489490 if <TokensMinted<T>>::get(self.id)491 .checked_add(1)492 .ok_or("item id overflow")?493 != token_id494 {495 return Err("item id should be next".into());496 }497498 let users = [(to.clone(), 1)]499 .into_iter()500 .collect::<BTreeMap<_, _>>()501 .try_into()502 .unwrap();503 <Pallet<T>>::create_item(504 self,505 &caller,506 CreateItemData::<T::CrossAccountId> {507 users,508 properties: CollectionPropertiesVec::default(),509 },510 &budget,511 )512 .map_err(dispatch_to_evm::<T>)?;513514 Ok(true)515 }516517 518 519 520 521 #[solidity(rename_selector = "mintWithTokenURI")]522 #[weight(<SelfWeightOf<T>>::create_item())]523 fn mint_with_token_uri(524 &mut self,525 caller: caller,526 to: address,527 token_uri: string,528 ) -> Result<uint256> {529 let token_id: uint256 = <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 .into();533 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;534 Ok(token_id)535 }536537 538 539 540 541 542 543 #[solidity(hide, rename_selector = "mintWithTokenURI")]544 #[weight(<SelfWeightOf<T>>::create_item())]545 fn mint_with_token_uri_check_id(546 &mut self,547 caller: caller,548 to: address,549 token_id: uint256,550 token_uri: string,551 ) -> Result<bool> {552 let key = key::url();553 let permission = get_token_permission::<T>(self.id, &key)?;554 if !permission.collection_admin {555 return Err("Operation is not allowed".into());556 }557558 let caller = T::CrossAccountId::from_eth(caller);559 let to = T::CrossAccountId::from_eth(to);560 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;561 let budget = self562 .recorder563 .weight_calls_budget(<StructureWeight<T>>::find_parent());564565 if <TokensMinted<T>>::get(self.id)566 .checked_add(1)567 .ok_or("item id overflow")?568 != token_id569 {570 return Err("item id should be next".into());571 }572573 let mut properties = CollectionPropertiesVec::default();574 properties575 .try_push(Property {576 key,577 value: token_uri578 .into_bytes()579 .try_into()580 .map_err(|_| "token uri is too long")?,581 })582 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;583584 let users = [(to.clone(), 1)]585 .into_iter()586 .collect::<BTreeMap<_, _>>()587 .try_into()588 .unwrap();589 <Pallet<T>>::create_item(590 self,591 &caller,592 CreateItemData::<T::CrossAccountId> { users, properties },593 &budget,594 )595 .map_err(dispatch_to_evm::<T>)?;596 Ok(true)597 }598599 600 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {601 Err("not implementable".into())602 }603}604605fn get_token_property<T: Config>(606 collection: &CollectionHandle<T>,607 token_id: u32,608 key: &up_data_structs::PropertyKey,609) -> Result<string> {610 collection.consume_store_reads(1)?;611 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))612 .map_err(|_| Error::Revert("Token properties not found".into()))?;613 if let Some(property) = properties.get(key) {614 return Ok(string::from_utf8_lossy(property).into());615 }616617 Err("Property tokenURI not found".into())618}619620fn get_token_permission<T: Config>(621 collection_id: CollectionId,622 key: &PropertyKey,623) -> Result<PropertyPermission> {624 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)625 .map_err(|_| Error::Revert("No permissions for collection".into()))?;626 let a = token_property_permissions627 .get(key)628 .map(Clone::clone)629 .ok_or_else(|| {630 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();631 Error::Revert(alloc::format!("No permission for key {}", key))632 })?;633 Ok(a)634}635636637#[solidity_interface(name = ERC721UniqueExtensions)]638impl<T: Config> RefungibleHandle<T>639where640 T::AccountId: From<[u8; 32]>,641{642 643 fn name(&self) -> Result<string> {644 Ok(decode_utf16(self.name.iter().copied())645 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))646 .collect::<string>())647 }648649 650 fn symbol(&self) -> Result<string> {651 Ok(string::from_utf8_lossy(&self.token_prefix).into())652 }653654 655 656 657 658 659 660 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]661 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {662 let caller = T::CrossAccountId::from_eth(caller);663 let to = T::CrossAccountId::from_eth(to);664 let token = token_id.try_into()?;665 let budget = self666 .recorder667 .weight_calls_budget(<StructureWeight<T>>::find_parent());668669 let balance = balance(self, token, &caller)?;670 ensure_single_owner(self, token, balance)?;671672 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)673 .map_err(dispatch_to_evm::<T>)?;674 Ok(())675 }676677 678 679 680 681 682 683 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]684 fn transfer_from_cross(685 &mut self,686 caller: caller,687 from: (address, uint256),688 to: (address, uint256),689 token_id: uint256,690 ) -> Result<void> {691 let caller = T::CrossAccountId::from_eth(caller);692 let from = convert_tuple_to_cross_account::<T>(from)?;693 let to = convert_tuple_to_cross_account::<T>(to)?;694 let token_id = token_id.try_into()?;695 let budget = self696 .recorder697 .weight_calls_budget(<StructureWeight<T>>::find_parent());698699 let balance = balance(self, token_id, &from)?;700 ensure_single_owner(self, token_id, balance)?;701702 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)703 .map_err(dispatch_to_evm::<T>)?;704 Ok(())705 }706707 708 709 710 711 712 713 714 #[weight(<SelfWeightOf<T>>::burn_from())]715 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {716 let caller = T::CrossAccountId::from_eth(caller);717 let from = T::CrossAccountId::from_eth(from);718 let token = token_id.try_into()?;719 let budget = self720 .recorder721 .weight_calls_budget(<StructureWeight<T>>::find_parent());722723 let balance = balance(self, token, &from)?;724 ensure_single_owner(self, token, balance)?;725726 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)727 .map_err(dispatch_to_evm::<T>)?;728 Ok(())729 }730731 732 733 734 735 736 737 738 #[weight(<SelfWeightOf<T>>::burn_from())]739 fn burn_from_cross(740 &mut self,741 caller: caller,742 from: (address, uint256),743 token_id: uint256,744 ) -> Result<void> {745 let caller = T::CrossAccountId::from_eth(caller);746 let from = convert_tuple_to_cross_account::<T>(from)?;747 let token = token_id.try_into()?;748 let budget = self749 .recorder750 .weight_calls_budget(<StructureWeight<T>>::find_parent());751752 let balance = balance(self, token, &from)?;753 ensure_single_owner(self, token, balance)?;754755 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)756 .map_err(dispatch_to_evm::<T>)?;757 Ok(())758 }759760 761 fn next_token_id(&self) -> Result<uint256> {762 self.consume_store_reads(1)?;763 Ok(<TokensMinted<T>>::get(self.id)764 .checked_add(1)765 .ok_or("item id overflow")?766 .into())767 }768769 770 771 772 773 774 #[solidity(hide)]775 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]776 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {777 let caller = T::CrossAccountId::from_eth(caller);778 let to = T::CrossAccountId::from_eth(to);779 let mut expected_index = <TokensMinted<T>>::get(self.id)780 .checked_add(1)781 .ok_or("item id overflow")?;782 let budget = self783 .recorder784 .weight_calls_budget(<StructureWeight<T>>::find_parent());785786 let total_tokens = token_ids.len();787 for id in token_ids.into_iter() {788 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;789 if id != expected_index {790 return Err("item id should be next".into());791 }792 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;793 }794 let users = [(to.clone(), 1)]795 .into_iter()796 .collect::<BTreeMap<_, _>>()797 .try_into()798 .unwrap();799 let create_item_data = CreateItemData::<T::CrossAccountId> {800 users,801 properties: CollectionPropertiesVec::default(),802 };803 let data = (0..total_tokens)804 .map(|_| create_item_data.clone())805 .collect();806807 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)808 .map_err(dispatch_to_evm::<T>)?;809 Ok(true)810 }811812 813 814 815 816 817 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]818 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]819 fn mint_bulk_with_token_uri(820 &mut self,821 caller: caller,822 to: address,823 tokens: Vec<(uint256, string)>,824 ) -> Result<bool> {825 let key = key::url();826 let caller = T::CrossAccountId::from_eth(caller);827 let to = T::CrossAccountId::from_eth(to);828 let mut expected_index = <TokensMinted<T>>::get(self.id)829 .checked_add(1)830 .ok_or("item id overflow")?;831 let budget = self832 .recorder833 .weight_calls_budget(<StructureWeight<T>>::find_parent());834835 let mut data = Vec::with_capacity(tokens.len());836 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]837 .into_iter()838 .collect::<BTreeMap<_, _>>()839 .try_into()840 .unwrap();841 for (id, token_uri) in tokens {842 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;843 if id != expected_index {844 return Err("item id should be next".into());845 }846 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;847848 let mut properties = CollectionPropertiesVec::default();849 properties850 .try_push(Property {851 key: key.clone(),852 value: token_uri853 .into_bytes()854 .try_into()855 .map_err(|_| "token uri is too long")?,856 })857 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;858859 let create_item_data = CreateItemData::<T::CrossAccountId> {860 users: users.clone(),861 properties,862 };863 data.push(create_item_data);864 }865866 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)867 .map_err(dispatch_to_evm::<T>)?;868 Ok(true)869 }870871 872 873 874 fn token_contract_address(&self, token: uint256) -> Result<address> {875 Ok(T::EvmTokenAddressMapping::token_to_address(876 self.id,877 token.try_into().map_err(|_| "token id overflow")?,878 ))879 }880}881882#[solidity_interface(883 name = UniqueRefungible,884 is(885 ERC721,886 ERC721Enumerable,887 ERC721UniqueExtensions,888 ERC721UniqueMintable,889 ERC721Burnable,890 ERC721Metadata(if(this.flags.erc721metadata)),891 Collection(via(common_mut returns CollectionHandle<T>)),892 TokenProperties,893 )894)]895impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}896897898generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);899generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);900901impl<T: Config> CommonEvmHandler for RefungibleHandle<T>902where903 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,904{905 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");906 fn call(907 self,908 handle: &mut impl PrecompileHandle,909 ) -> Option<pallet_common::erc::PrecompileResult> {910 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)911 }912}