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 fn name(&self) -> Result<string> {200 Ok(decode_utf16(self.name.iter().copied())201 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))202 .collect::<string>())203 }204205 206 fn symbol(&self) -> Result<string> {207 Ok(string::from_utf8_lossy(&self.token_prefix).into())208 }209210 211 212 213 214 215 216 217 218 219 #[solidity(rename_selector = "tokenURI")]220 fn token_uri(&self, token_id: uint256) -> Result<string> {221 if !self.supports_metadata() {222 return Ok("".into());223 }224225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 match get_token_property(self, token_id_u32, &key::url()).as_deref() {228 Err(_) | Ok("") => (),229 Ok(url) => {230 return Ok(url.into());231 }232 };233234 let base_uri =235 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())236 .map(BoundedVec::into_inner)237 .map(string::from_utf8)238 .transpose()239 .map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245246 let base_uri = match base_uri.as_deref() {247 None | Some("") => {248 return Ok("".into());249 }250 Some(base_uri) => base_uri.into(),251 };252253 Ok(254 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {255 Err(_) | Ok("") => base_uri,256 Ok(suffix) => base_uri + suffix,257 },258 )259 }260}261262263264#[solidity_interface(name = ERC721Enumerable)]265impl<T: Config> RefungibleHandle<T> {266 267 268 269 270 fn token_by_index(&self, index: uint256) -> Result<uint256> {271 Ok(index)272 }273274 275 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {276 277 Err("not implemented".into())278 }279280 281 282 283 fn total_supply(&self) -> Result<uint256> {284 self.consume_store_reads(1)?;285 Ok(<Pallet<T>>::total_supply(self).into())286 }287}288289290291#[solidity_interface(name = ERC721, events(ERC721Events))]292impl<T: Config> RefungibleHandle<T> {293 294 295 296 297 298 fn balance_of(&self, owner: address) -> Result<uint256> {299 self.consume_store_reads(1)?;300 let owner = T::CrossAccountId::from_eth(owner);301 let balance = <AccountBalance<T>>::get((self.id, owner));302 Ok(balance.into())303 }304305 306 307 308 309 310 311 312 fn owner_of(&self, token_id: uint256) -> Result<address> {313 self.consume_store_reads(2)?;314 let token = token_id.try_into()?;315 let owner = <Pallet<T>>::token_owner(self.id, token);316 Ok(owner317 .map(|address| *address.as_eth())318 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))319 }320321 322 fn safe_transfer_from_with_data(323 &mut self,324 _from: address,325 _to: address,326 _token_id: uint256,327 _data: bytes,328 ) -> Result<void> {329 330 Err("not implemented".into())331 }332333 334 fn safe_transfer_from(335 &mut self,336 _from: address,337 _to: address,338 _token_id: uint256,339 ) -> Result<void> {340 341 Err("not implemented".into())342 }343344 345 346 347 348 349 350 351 352 353 354 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]355 fn transfer_from(356 &mut self,357 caller: caller,358 from: address,359 to: address,360 token_id: uint256,361 ) -> Result<void> {362 let caller = T::CrossAccountId::from_eth(caller);363 let from = T::CrossAccountId::from_eth(from);364 let to = T::CrossAccountId::from_eth(to);365 let token = token_id.try_into()?;366 let budget = self367 .recorder368 .weight_calls_budget(<StructureWeight<T>>::find_parent());369370 let balance = balance(&self, token, &from)?;371 ensure_single_owner(&self, token, balance)?;372373 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)374 .map_err(dispatch_to_evm::<T>)?;375376 Ok(())377 }378379 380 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {381 Err("not implemented".into())382 }383384 385 fn set_approval_for_all(386 &mut self,387 _caller: caller,388 _operator: address,389 _approved: bool,390 ) -> Result<void> {391 392 Err("not implemented".into())393 }394395 396 fn get_approved(&self, _token_id: uint256) -> Result<address> {397 398 Err("not implemented".into())399 }400401 402 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {403 404 Err("not implemented".into())405 }406}407408409pub fn balance<T: Config>(410 collection: &RefungibleHandle<T>,411 token: TokenId,412 owner: &T::CrossAccountId,413) -> Result<u128> {414 collection.consume_store_reads(1)?;415 let balance = <Balance<T>>::get((collection.id, token, &owner));416 Ok(balance)417}418419420pub fn ensure_single_owner<T: Config>(421 collection: &RefungibleHandle<T>,422 token: TokenId,423 owner_balance: u128,424) -> Result<()> {425 collection.consume_store_reads(1)?;426 let total_supply = <TotalSupply<T>>::get((collection.id, token));427 if total_supply != owner_balance {428 return Err("token has multiple owners".into());429 }430 Ok(())431}432433434#[solidity_interface(name = ERC721Burnable)]435impl<T: Config> RefungibleHandle<T> {436 437 438 439 440 #[weight(<SelfWeightOf<T>>::burn_item_fully())]441 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {442 let caller = T::CrossAccountId::from_eth(caller);443 let token = token_id.try_into()?;444445 let balance = balance(&self, token, &caller)?;446 ensure_single_owner(&self, token, balance)?;447448 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;449 Ok(())450 }451}452453454#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]455impl<T: Config> RefungibleHandle<T> {456 fn minting_finished(&self) -> Result<bool> {457 Ok(false)458 }459460 461 462 463 464 465 #[weight(<SelfWeightOf<T>>::create_item())]466 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {467 let caller = T::CrossAccountId::from_eth(caller);468 let to = T::CrossAccountId::from_eth(to);469 let token_id: u32 = token_id.try_into()?;470 let budget = self471 .recorder472 .weight_calls_budget(<StructureWeight<T>>::find_parent());473474 if <TokensMinted<T>>::get(self.id)475 .checked_add(1)476 .ok_or("item id overflow")?477 != token_id478 {479 return Err("item id should be next".into());480 }481482 let users = [(to.clone(), 1)]483 .into_iter()484 .collect::<BTreeMap<_, _>>()485 .try_into()486 .unwrap();487 <Pallet<T>>::create_item(488 self,489 &caller,490 CreateItemData::<T::CrossAccountId> {491 users,492 properties: CollectionPropertiesVec::default(),493 },494 &budget,495 )496 .map_err(dispatch_to_evm::<T>)?;497498 Ok(true)499 }500501 502 503 504 505 506 507 #[solidity(rename_selector = "mintWithTokenURI")]508 #[weight(<SelfWeightOf<T>>::create_item())]509 fn mint_with_token_uri(510 &mut self,511 caller: caller,512 to: address,513 token_id: uint256,514 token_uri: string,515 ) -> Result<bool> {516 let key = key::url();517 let permission = get_token_permission::<T>(self.id, &key)?;518 if !permission.collection_admin {519 return Err("Operation is not allowed".into());520 }521522 let caller = T::CrossAccountId::from_eth(caller);523 let to = T::CrossAccountId::from_eth(to);524 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;525 let budget = self526 .recorder527 .weight_calls_budget(<StructureWeight<T>>::find_parent());528529 if <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 != token_id533 {534 return Err("item id should be next".into());535 }536537 let mut properties = CollectionPropertiesVec::default();538 properties539 .try_push(Property {540 key,541 value: token_uri542 .into_bytes()543 .try_into()544 .map_err(|_| "token uri is too long")?,545 })546 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;547548 let users = [(to.clone(), 1)]549 .into_iter()550 .collect::<BTreeMap<_, _>>()551 .try_into()552 .unwrap();553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T::CrossAccountId> { users, properties },557 &budget,558 )559 .map_err(dispatch_to_evm::<T>)?;560 Ok(true)561 }562563 564 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {565 Err("not implementable".into())566 }567}568569fn get_token_property<T: Config>(570 collection: &CollectionHandle<T>,571 token_id: u32,572 key: &up_data_structs::PropertyKey,573) -> Result<string> {574 collection.consume_store_reads(1)?;575 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))576 .map_err(|_| Error::Revert("Token properties not found".into()))?;577 if let Some(property) = properties.get(key) {578 return Ok(string::from_utf8_lossy(property).into());579 }580581 Err("Property tokenURI not found".into())582}583584fn get_token_permission<T: Config>(585 collection_id: CollectionId,586 key: &PropertyKey,587) -> Result<PropertyPermission> {588 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)589 .map_err(|_| Error::Revert("No permissions for collection".into()))?;590 let a = token_property_permissions591 .get(key)592 .map(Clone::clone)593 .ok_or_else(|| {594 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();595 Error::Revert(alloc::format!("No permission for key {}", key))596 })?;597 Ok(a)598}599600601#[solidity_interface(name = ERC721UniqueExtensions)]602impl<T: Config> RefungibleHandle<T> {603 604 605 606 607 608 609 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]610 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {611 let caller = T::CrossAccountId::from_eth(caller);612 let to = T::CrossAccountId::from_eth(to);613 let token = token_id.try_into()?;614 let budget = self615 .recorder616 .weight_calls_budget(<StructureWeight<T>>::find_parent());617618 let balance = balance(&self, token, &caller)?;619 ensure_single_owner(&self, token, balance)?;620621 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)622 .map_err(dispatch_to_evm::<T>)?;623 Ok(())624 }625626 627 628 629 630 631 632 633 #[weight(<SelfWeightOf<T>>::burn_from())]634 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {635 let caller = T::CrossAccountId::from_eth(caller);636 let from = T::CrossAccountId::from_eth(from);637 let token = token_id.try_into()?;638 let budget = self639 .recorder640 .weight_calls_budget(<StructureWeight<T>>::find_parent());641642 let balance = balance(&self, token, &caller)?;643 ensure_single_owner(&self, token, balance)?;644645 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)646 .map_err(dispatch_to_evm::<T>)?;647 Ok(())648 }649650 651 fn next_token_id(&self) -> Result<uint256> {652 self.consume_store_reads(1)?;653 Ok(<TokensMinted<T>>::get(self.id)654 .checked_add(1)655 .ok_or("item id overflow")?656 .into())657 }658659 660 661 662 663 664 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]665 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {666 let caller = T::CrossAccountId::from_eth(caller);667 let to = T::CrossAccountId::from_eth(to);668 let mut expected_index = <TokensMinted<T>>::get(self.id)669 .checked_add(1)670 .ok_or("item id overflow")?;671 let budget = self672 .recorder673 .weight_calls_budget(<StructureWeight<T>>::find_parent());674675 let total_tokens = token_ids.len();676 for id in token_ids.into_iter() {677 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;678 if id != expected_index {679 return Err("item id should be next".into());680 }681 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;682 }683 let users = [(to.clone(), 1)]684 .into_iter()685 .collect::<BTreeMap<_, _>>()686 .try_into()687 .unwrap();688 let create_item_data = CreateItemData::<T::CrossAccountId> {689 users,690 properties: CollectionPropertiesVec::default(),691 };692 let data = (0..total_tokens)693 .map(|_| create_item_data.clone())694 .collect();695696 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)697 .map_err(dispatch_to_evm::<T>)?;698 Ok(true)699 }700701 702 703 704 705 706 #[solidity(rename_selector = "mintBulkWithTokenURI")]707 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]708 fn mint_bulk_with_token_uri(709 &mut self,710 caller: caller,711 to: address,712 tokens: Vec<(uint256, string)>,713 ) -> Result<bool> {714 let key = key::url();715 let caller = T::CrossAccountId::from_eth(caller);716 let to = T::CrossAccountId::from_eth(to);717 let mut expected_index = <TokensMinted<T>>::get(self.id)718 .checked_add(1)719 .ok_or("item id overflow")?;720 let budget = self721 .recorder722 .weight_calls_budget(<StructureWeight<T>>::find_parent());723724 let mut data = Vec::with_capacity(tokens.len());725 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]726 .into_iter()727 .collect::<BTreeMap<_, _>>()728 .try_into()729 .unwrap();730 for (id, token_uri) in tokens {731 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;732 if id != expected_index {733 return Err("item id should be next".into());734 }735 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;736737 let mut properties = CollectionPropertiesVec::default();738 properties739 .try_push(Property {740 key: key.clone(),741 value: token_uri742 .into_bytes()743 .try_into()744 .map_err(|_| "token uri is too long")?,745 })746 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;747748 let create_item_data = CreateItemData::<T::CrossAccountId> {749 users: users.clone(),750 properties,751 };752 data.push(create_item_data);753 }754755 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)756 .map_err(dispatch_to_evm::<T>)?;757 Ok(true)758 }759760 761 762 763 fn token_contract_address(&self, token: uint256) -> Result<address> {764 Ok(T::EvmTokenAddressMapping::token_to_address(765 self.id,766 token.try_into().map_err(|_| "token id overflow")?,767 ))768 }769}770771impl<T: Config> RefungibleHandle<T> {772 pub fn supports_metadata(&self) -> bool {773 if let Some(erc721_metadata) =774 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())775 {776 *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED777 } else {778 false779 }780 }781}782783#[solidity_interface(784 name = UniqueRefungible,785 is(786 ERC721,787 ERC721Enumerable,788 ERC721UniqueExtensions,789 ERC721Mintable,790 ERC721Burnable,791 Collection(via(common_mut returns CollectionHandle<T>)),792 TokenProperties,793 ERC721Metadata(if(this.supports_metadata())),794 )795)]796impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}797798799generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);800generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);801802impl<T: Config> CommonEvmHandler for RefungibleHandle<T>803where804 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,805{806 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");807 fn call(808 self,809 handle: &mut impl PrecompileHandle,810 ) -> Option<pallet_common::erc::PrecompileResult> {811 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)812 }813}