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, static_property::value},33};34use pallet_evm::{account::CrossAccountId, PrecompileHandle};35use pallet_evm_coder_substrate::{call, dispatch_to_evm};36use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};37use sp_core::H160;38use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};39use up_data_structs::{40 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,41 PropertyKeyPermission, PropertyPermission, TokenId,42};4344use crate::{45 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,46 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,47};4849pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);505152#[solidity_interface(name = TokenProperties)]53impl<T: Config> RefungibleHandle<T> {54 55 56 57 58 59 60 fn set_token_property_permission(61 &mut self,62 caller: caller,63 key: string,64 is_mutable: bool,65 collection_admin: bool,66 token_owner: bool,67 ) -> Result<()> {68 let caller = T::CrossAccountId::from_eth(caller);69 <Pallet<T>>::set_token_property_permissions(70 self,71 &caller,72 vec![PropertyKeyPermission {73 key: <Vec<u8>>::from(key)74 .try_into()75 .map_err(|_| "too long key")?,76 permission: PropertyPermission {77 mutable: is_mutable,78 collection_admin,79 token_owner,80 },81 }],82 )83 .map_err(dispatch_to_evm::<T>)84 }8586 87 88 89 90 91 fn set_property(92 &mut self,93 caller: caller,94 token_id: uint256,95 key: string,96 value: bytes,97 ) -> Result<()> {98 let caller = T::CrossAccountId::from_eth(caller);99 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too long")?;103 let value = value.try_into().map_err(|_| "value too long")?;104105 let nesting_budget = self106 .recorder107 .weight_calls_budget(<StructureWeight<T>>::find_parent());108109 <Pallet<T>>::set_token_property(110 self,111 &caller,112 TokenId(token_id),113 Property { key, value },114 &nesting_budget,115 )116 .map_err(dispatch_to_evm::<T>)117 }118119 120 121 122 123 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {124 let caller = T::CrossAccountId::from_eth(caller);125 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;126 let key = <Vec<u8>>::from(key)127 .try_into()128 .map_err(|_| "key too long")?;129130 let nesting_budget = self131 .recorder132 .weight_calls_budget(<StructureWeight<T>>::find_parent());133134 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)135 .map_err(dispatch_to_evm::<T>)136 }137138 139 140 141 142 143 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {144 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;145 let key = <Vec<u8>>::from(key)146 .try_into()147 .map_err(|_| "key too long")?;148149 let props = <TokenProperties<T>>::get((self.id, token_id));150 let prop = props.get(&key).ok_or("key not found")?;151152 Ok(prop.to_vec())153 }154}155156#[derive(ToLog)]157pub enum ERC721Events {158 159 160 161 Transfer {162 #[indexed]163 from: address,164 #[indexed]165 to: address,166 #[indexed]167 token_id: uint256,168 },169 170 Approval {171 #[indexed]172 owner: address,173 #[indexed]174 approved: address,175 #[indexed]176 token_id: uint256,177 },178 179 #[allow(dead_code)]180 ApprovalForAll {181 #[indexed]182 owner: address,183 #[indexed]184 operator: address,185 approved: bool,186 },187}188189#[derive(ToLog)]190pub enum ERC721MintableEvents {191 192 #[allow(dead_code)]193 MintingFinished {},194}195196#[solidity_interface(name = ERC721Metadata)]197impl<T: Config> RefungibleHandle<T> {198 199 200 #[solidity(hide, rename_selector = "name")]201 fn name_proxy(&self) -> Result<string> {202 self.name()203 }204205 206 207 #[solidity(hide, rename_selector = "symbol")]208 fn symbol_proxy(&self) -> Result<string> {209 self.symbol()210 }211212 213 214 215 216 217 218 219 220 221 #[solidity(rename_selector = "tokenURI")]222 fn token_uri(&self, token_id: uint256) -> Result<string> {223 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;224225 match get_token_property(self, token_id_u32, &key::url()).as_deref() {226 Err(_) | Ok("") => (),227 Ok(url) => {228 return Ok(url.into());229 }230 };231232 let base_uri =233 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())234 .map(BoundedVec::into_inner)235 .map(string::from_utf8)236 .transpose()237 .map_err(|e| {238 Error::Revert(alloc::format!(239 "Can not convert value \"baseURI\" to string with error \"{}\"",240 e241 ))242 })?;243244 let base_uri = match base_uri.as_deref() {245 None | Some("") => {246 return Ok("".into());247 }248 Some(base_uri) => base_uri.into(),249 };250251 Ok(252 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {253 Err(_) | Ok("") => base_uri,254 Ok(suffix) => base_uri + suffix,255 },256 )257 }258}259260261262#[solidity_interface(name = ERC721Enumerable)]263impl<T: Config> RefungibleHandle<T> {264 265 266 267 268 fn token_by_index(&self, index: uint256) -> Result<uint256> {269 Ok(index)270 }271272 273 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {274 275 Err("not implemented".into())276 }277278 279 280 281 fn total_supply(&self) -> Result<uint256> {282 self.consume_store_reads(1)?;283 Ok(<Pallet<T>>::total_supply(self).into())284 }285}286287288289#[solidity_interface(name = ERC721, events(ERC721Events))]290impl<T: Config> RefungibleHandle<T> {291 292 293 294 295 296 fn balance_of(&self, owner: address) -> Result<uint256> {297 self.consume_store_reads(1)?;298 let owner = T::CrossAccountId::from_eth(owner);299 let balance = <AccountBalance<T>>::get((self.id, owner));300 Ok(balance.into())301 }302303 304 305 306 307 308 309 310 fn owner_of(&self, token_id: uint256) -> Result<address> {311 self.consume_store_reads(2)?;312 let token = token_id.try_into()?;313 let owner = <Pallet<T>>::token_owner(self.id, token);314 Ok(owner315 .map(|address| *address.as_eth())316 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))317 }318319 320 fn safe_transfer_from_with_data(321 &mut self,322 _from: address,323 _to: address,324 _token_id: uint256,325 _data: bytes,326 ) -> Result<void> {327 328 Err("not implemented".into())329 }330331 332 fn safe_transfer_from(333 &mut self,334 _from: address,335 _to: address,336 _token_id: uint256,337 ) -> Result<void> {338 339 Err("not implemented".into())340 }341342 343 344 345 346 347 348 349 350 351 352 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]353 fn transfer_from(354 &mut self,355 caller: caller,356 from: address,357 to: address,358 token_id: uint256,359 ) -> Result<void> {360 let caller = T::CrossAccountId::from_eth(caller);361 let from = T::CrossAccountId::from_eth(from);362 let to = T::CrossAccountId::from_eth(to);363 let token = token_id.try_into()?;364 let budget = self365 .recorder366 .weight_calls_budget(<StructureWeight<T>>::find_parent());367368 let balance = balance(&self, token, &from)?;369 ensure_single_owner(&self, token, balance)?;370371 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)372 .map_err(dispatch_to_evm::<T>)?;373374 Ok(())375 }376377 378 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {379 Err("not implemented".into())380 }381382 383 fn set_approval_for_all(384 &mut self,385 _caller: caller,386 _operator: address,387 _approved: bool,388 ) -> Result<void> {389 390 Err("not implemented".into())391 }392393 394 fn get_approved(&self, _token_id: uint256) -> Result<address> {395 396 Err("not implemented".into())397 }398399 400 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {401 402 Err("not implemented".into())403 }404}405406407pub fn balance<T: Config>(408 collection: &RefungibleHandle<T>,409 token: TokenId,410 owner: &T::CrossAccountId,411) -> Result<u128> {412 collection.consume_store_reads(1)?;413 let balance = <Balance<T>>::get((collection.id, token, &owner));414 Ok(balance)415}416417418pub fn ensure_single_owner<T: Config>(419 collection: &RefungibleHandle<T>,420 token: TokenId,421 owner_balance: u128,422) -> Result<()> {423 collection.consume_store_reads(1)?;424 let total_supply = <TotalSupply<T>>::get((collection.id, token));425 if total_supply != owner_balance {426 return Err("token has multiple owners".into());427 }428 Ok(())429}430431432#[solidity_interface(name = ERC721Burnable)]433impl<T: Config> RefungibleHandle<T> {434 435 436 437 438 #[weight(<SelfWeightOf<T>>::burn_item_fully())]439 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {440 let caller = T::CrossAccountId::from_eth(caller);441 let token = token_id.try_into()?;442443 let balance = balance(&self, token, &caller)?;444 ensure_single_owner(&self, token, balance)?;445446 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;447 Ok(())448 }449}450451452#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]453impl<T: Config> RefungibleHandle<T> {454 fn minting_finished(&self) -> Result<bool> {455 Ok(false)456 }457458 459 460 461 462 463 #[weight(<SelfWeightOf<T>>::create_item())]464 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {465 let caller = T::CrossAccountId::from_eth(caller);466 let to = T::CrossAccountId::from_eth(to);467 let token_id: u32 = token_id.try_into()?;468 let budget = self469 .recorder470 .weight_calls_budget(<StructureWeight<T>>::find_parent());471472 if <TokensMinted<T>>::get(self.id)473 .checked_add(1)474 .ok_or("item id overflow")?475 != token_id476 {477 return Err("item id should be next".into());478 }479480 let users = [(to.clone(), 1)]481 .into_iter()482 .collect::<BTreeMap<_, _>>()483 .try_into()484 .unwrap();485 <Pallet<T>>::create_item(486 self,487 &caller,488 CreateItemData::<T::CrossAccountId> {489 users,490 properties: CollectionPropertiesVec::default(),491 },492 &budget,493 )494 .map_err(dispatch_to_evm::<T>)?;495496 Ok(true)497 }498499 500 501 502 503 504 505 #[solidity(rename_selector = "mintWithTokenURI")]506 #[weight(<SelfWeightOf<T>>::create_item())]507 fn mint_with_token_uri(508 &mut self,509 caller: caller,510 to: address,511 token_id: uint256,512 token_uri: string,513 ) -> Result<bool> {514 let key = key::url();515 let permission = get_token_permission::<T>(self.id, &key)?;516 if !permission.collection_admin {517 return Err("Operation is not allowed".into());518 }519520 let caller = T::CrossAccountId::from_eth(caller);521 let to = T::CrossAccountId::from_eth(to);522 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;523 let budget = self524 .recorder525 .weight_calls_budget(<StructureWeight<T>>::find_parent());526527 if <TokensMinted<T>>::get(self.id)528 .checked_add(1)529 .ok_or("item id overflow")?530 != token_id531 {532 return Err("item id should be next".into());533 }534535 let mut properties = CollectionPropertiesVec::default();536 properties537 .try_push(Property {538 key,539 value: token_uri540 .into_bytes()541 .try_into()542 .map_err(|_| "token uri is too long")?,543 })544 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;545546 let users = [(to.clone(), 1)]547 .into_iter()548 .collect::<BTreeMap<_, _>>()549 .try_into()550 .unwrap();551 <Pallet<T>>::create_item(552 self,553 &caller,554 CreateItemData::<T::CrossAccountId> { users, properties },555 &budget,556 )557 .map_err(dispatch_to_evm::<T>)?;558 Ok(true)559 }560561 562 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {563 Err("not implementable".into())564 }565}566567fn get_token_property<T: Config>(568 collection: &CollectionHandle<T>,569 token_id: u32,570 key: &up_data_structs::PropertyKey,571) -> Result<string> {572 collection.consume_store_reads(1)?;573 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))574 .map_err(|_| Error::Revert("Token properties not found".into()))?;575 if let Some(property) = properties.get(key) {576 return Ok(string::from_utf8_lossy(property).into());577 }578579 Err("Property tokenURI not found".into())580}581582fn get_token_permission<T: Config>(583 collection_id: CollectionId,584 key: &PropertyKey,585) -> Result<PropertyPermission> {586 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)587 .map_err(|_| Error::Revert("No permissions for collection".into()))?;588 let a = token_property_permissions589 .get(key)590 .map(Clone::clone)591 .ok_or_else(|| {592 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();593 Error::Revert(alloc::format!("No permission for key {}", key))594 })?;595 Ok(a)596}597598599#[solidity_interface(name = ERC721UniqueExtensions)]600impl<T: Config> RefungibleHandle<T> {601 602 fn name(&self) -> Result<string> {603 Ok(decode_utf16(self.name.iter().copied())604 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))605 .collect::<string>())606 }607608 609 fn symbol(&self) -> Result<string> {610 Ok(string::from_utf8_lossy(&self.token_prefix).into())611 }612613 614 615 616 617 618 619 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]620 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {621 let caller = T::CrossAccountId::from_eth(caller);622 let to = T::CrossAccountId::from_eth(to);623 let token = token_id.try_into()?;624 let budget = self625 .recorder626 .weight_calls_budget(<StructureWeight<T>>::find_parent());627628 let balance = balance(&self, token, &caller)?;629 ensure_single_owner(&self, token, balance)?;630631 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)632 .map_err(dispatch_to_evm::<T>)?;633 Ok(())634 }635636 637 638 639 640 641 642 643 #[weight(<SelfWeightOf<T>>::burn_from())]644 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {645 let caller = T::CrossAccountId::from_eth(caller);646 let from = T::CrossAccountId::from_eth(from);647 let token = token_id.try_into()?;648 let budget = self649 .recorder650 .weight_calls_budget(<StructureWeight<T>>::find_parent());651652 let balance = balance(&self, token, &caller)?;653 ensure_single_owner(&self, token, balance)?;654655 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)656 .map_err(dispatch_to_evm::<T>)?;657 Ok(())658 }659660 661 fn next_token_id(&self) -> Result<uint256> {662 self.consume_store_reads(1)?;663 Ok(<TokensMinted<T>>::get(self.id)664 .checked_add(1)665 .ok_or("item id overflow")?666 .into())667 }668669 670 671 672 673 674 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]675 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {676 let caller = T::CrossAccountId::from_eth(caller);677 let to = T::CrossAccountId::from_eth(to);678 let mut expected_index = <TokensMinted<T>>::get(self.id)679 .checked_add(1)680 .ok_or("item id overflow")?;681 let budget = self682 .recorder683 .weight_calls_budget(<StructureWeight<T>>::find_parent());684685 let total_tokens = token_ids.len();686 for id in token_ids.into_iter() {687 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;688 if id != expected_index {689 return Err("item id should be next".into());690 }691 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;692 }693 let users = [(to.clone(), 1)]694 .into_iter()695 .collect::<BTreeMap<_, _>>()696 .try_into()697 .unwrap();698 let create_item_data = CreateItemData::<T::CrossAccountId> {699 users,700 properties: CollectionPropertiesVec::default(),701 };702 let data = (0..total_tokens)703 .map(|_| create_item_data.clone())704 .collect();705706 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)707 .map_err(dispatch_to_evm::<T>)?;708 Ok(true)709 }710711 712 713 714 715 716 #[solidity(rename_selector = "mintBulkWithTokenURI")]717 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]718 fn mint_bulk_with_token_uri(719 &mut self,720 caller: caller,721 to: address,722 tokens: Vec<(uint256, string)>,723 ) -> Result<bool> {724 let key = key::url();725 let caller = T::CrossAccountId::from_eth(caller);726 let to = T::CrossAccountId::from_eth(to);727 let mut expected_index = <TokensMinted<T>>::get(self.id)728 .checked_add(1)729 .ok_or("item id overflow")?;730 let budget = self731 .recorder732 .weight_calls_budget(<StructureWeight<T>>::find_parent());733734 let mut data = Vec::with_capacity(tokens.len());735 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]736 .into_iter()737 .collect::<BTreeMap<_, _>>()738 .try_into()739 .unwrap();740 for (id, token_uri) in tokens {741 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;742 if id != expected_index {743 return Err("item id should be next".into());744 }745 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;746747 let mut properties = CollectionPropertiesVec::default();748 properties749 .try_push(Property {750 key: key.clone(),751 value: token_uri752 .into_bytes()753 .try_into()754 .map_err(|_| "token uri is too long")?,755 })756 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;757758 let create_item_data = CreateItemData::<T::CrossAccountId> {759 users: users.clone(),760 properties,761 };762 data.push(create_item_data);763 }764765 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)766 .map_err(dispatch_to_evm::<T>)?;767 Ok(true)768 }769770 771 772 773 fn token_contract_address(&self, token: uint256) -> Result<address> {774 Ok(T::EvmTokenAddressMapping::token_to_address(775 self.id,776 token.try_into().map_err(|_| "token id overflow")?,777 ))778 }779}780781#[solidity_interface(782 name = UniqueRefungible,783 is(784 ERC721,785 ERC721Enumerable,786 ERC721UniqueExtensions,787 ERC721Mintable,788 ERC721Burnable,789 ERC721Metadata(if(this.flags.erc721metadata)),790 Collection(via(common_mut returns CollectionHandle<T>)),791 TokenProperties,792 )793)]794impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}795796797generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);798generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);799800impl<T: Config> CommonEvmHandler for RefungibleHandle<T>801where802 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,803{804 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");805 fn call(806 self,807 handle: &mut impl PrecompileHandle,808 ) -> Option<pallet_common::erc::PrecompileResult> {809 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)810 }811}