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::{37 CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key,38 static_property::value,39 },40 CollectionHandle, CollectionPropertyPermissions,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4546use crate::{47 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,48 SelfWeightOf, weights::WeightInfo, TokenProperties,49};505152#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<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_property_permission(70 self,71 &caller,72 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 162 163 Transfer {164 #[indexed]165 from: address,166 #[indexed]167 to: address,168 #[indexed]169 token_id: uint256,170 },171 172 173 174 175 Approval {176 #[indexed]177 owner: address,178 #[indexed]179 approved: address,180 #[indexed]181 token_id: uint256,182 },183 184 185 #[allow(dead_code)]186 ApprovalForAll {187 #[indexed]188 owner: address,189 #[indexed]190 operator: address,191 approved: bool,192 },193}194195#[derive(ToLog)]196pub enum ERC721MintableEvents {197 #[allow(dead_code)]198 MintingFinished {},199}200201202203#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]204impl<T: Config> NonfungibleHandle<T> {205 206 fn name(&self) -> Result<string> {207 Ok(decode_utf16(self.name.iter().copied())208 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))209 .collect::<string>())210 }211212 213 fn symbol(&self) -> Result<string> {214 Ok(string::from_utf8_lossy(&self.token_prefix).into())215 }216217 218 219 220 221 222 223 224 225 226 #[solidity(rename_selector = "tokenURI")]227 fn token_uri(&self, token_id: uint256) -> Result<string> {228 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;229230 match get_token_property(self, token_id_u32, &key::url()).as_deref() {231 Err(_) | Ok("") => (),232 Ok(url) => {233 return Ok(url.into());234 }235 };236237 let base_uri =238 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())239 .map(BoundedVec::into_inner)240 .map(string::from_utf8)241 .transpose()242 .map_err(|e| {243 Error::Revert(alloc::format!(244 "Can not convert value \"baseURI\" to string with error \"{}\"",245 e246 ))247 })?;248249 let base_uri = match base_uri.as_deref() {250 None | Some("") => {251 return Ok("".into());252 }253 Some(base_uri) => base_uri.into(),254 };255256 Ok(257 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {258 Err(_) | Ok("") => base_uri,259 Ok(suffix) => base_uri + suffix,260 },261 )262 }263}264265266267#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]268impl<T: Config> NonfungibleHandle<T> {269 270 271 272 273 fn token_by_index(&self, index: uint256) -> Result<uint256> {274 Ok(index)275 }276277 278 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {279 280 Err("not implemented".into())281 }282283 284 285 286 fn total_supply(&self) -> Result<uint256> {287 self.consume_store_reads(1)?;288 Ok(<Pallet<T>>::total_supply(self).into())289 }290}291292293294#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]295impl<T: Config> NonfungibleHandle<T> {296 297 298 299 300 301 fn balance_of(&self, owner: address) -> Result<uint256> {302 self.consume_store_reads(1)?;303 let owner = T::CrossAccountId::from_eth(owner);304 let balance = <AccountBalance<T>>::get((self.id, owner));305 Ok(balance.into())306 }307 308 309 310 311 312 fn owner_of(&self, token_id: uint256) -> Result<address> {313 self.consume_store_reads(1)?;314 let token: TokenId = token_id.try_into()?;315 Ok(*<TokenData<T>>::get((self.id, token))316 .ok_or("token not found")?317 .owner318 .as_eth())319 }320 321 #[solidity(rename_selector = "safeTransferFrom")]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 }332 333 fn safe_transfer_from(334 &mut self,335 _from: address,336 _to: address,337 _token_id: uint256,338 ) -> Result<void> {339 340 Err("not implemented".into())341 }342343 344 345 346 347 348 349 350 351 352 #[weight(<SelfWeightOf<T>>::transfer_from())]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 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)369 .map_err(dispatch_to_evm::<T>)?;370 Ok(())371 }372373 374 375 376 377 378 379 #[weight(<SelfWeightOf<T>>::approve())]380 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {381 let caller = T::CrossAccountId::from_eth(caller);382 let approved = T::CrossAccountId::from_eth(approved);383 let token = token_id.try_into()?;384385 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))386 .map_err(dispatch_to_evm::<T>)?;387 Ok(())388 }389390 391 fn set_approval_for_all(392 &mut self,393 _caller: caller,394 _operator: address,395 _approved: bool,396 ) -> Result<void> {397 398 Err("not implemented".into())399 }400401 402 fn get_approved(&self, _token_id: uint256) -> Result<address> {403 404 Err("not implemented".into())405 }406407 408 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {409 410 Err("not implemented".into())411 }412}413414415#[solidity_interface(name = ERC721Burnable)]416impl<T: Config> NonfungibleHandle<T> {417 418 419 420 421 #[weight(<SelfWeightOf<T>>::burn_item())]422 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {423 let caller = T::CrossAccountId::from_eth(caller);424 let token = token_id.try_into()?;425426 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;427 Ok(())428 }429}430431432#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]433impl<T: Config> NonfungibleHandle<T> {434 fn minting_finished(&self) -> Result<bool> {435 Ok(false)436 }437438 439 440 441 442 443 #[weight(<SelfWeightOf<T>>::create_item())]444 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {445 let caller = T::CrossAccountId::from_eth(caller);446 let to = T::CrossAccountId::from_eth(to);447 let token_id: u32 = token_id.try_into()?;448 let budget = self449 .recorder450 .weight_calls_budget(<StructureWeight<T>>::find_parent());451452 if <TokensMinted<T>>::get(self.id)453 .checked_add(1)454 .ok_or("item id overflow")?455 != token_id456 {457 return Err("item id should be next".into());458 }459460 <Pallet<T>>::create_item(461 self,462 &caller,463 CreateItemData::<T> {464 properties: BoundedVec::default(),465 owner: to,466 },467 &budget,468 )469 .map_err(dispatch_to_evm::<T>)?;470471 Ok(true)472 }473474 475 476 477 478 479 480 #[solidity(rename_selector = "mintWithTokenURI")]481 #[weight(<SelfWeightOf<T>>::create_item())]482 fn mint_with_token_uri(483 &mut self,484 caller: caller,485 to: address,486 token_id: uint256,487 token_uri: string,488 ) -> Result<bool> {489 let key = key::url();490 let permission = get_token_permission::<T>(self.id, &key)?;491 if !permission.collection_admin {492 return Err("Operation is not allowed".into());493 }494495 let caller = T::CrossAccountId::from_eth(caller);496 let to = T::CrossAccountId::from_eth(to);497 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;498 let budget = self499 .recorder500 .weight_calls_budget(<StructureWeight<T>>::find_parent());501502 if <TokensMinted<T>>::get(self.id)503 .checked_add(1)504 .ok_or("item id overflow")?505 != token_id506 {507 return Err("item id should be next".into());508 }509510 let mut properties = CollectionPropertiesVec::default();511 properties512 .try_push(Property {513 key,514 value: token_uri515 .into_bytes()516 .try_into()517 .map_err(|_| "token uri is too long")?,518 })519 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;520521 <Pallet<T>>::create_item(522 self,523 &caller,524 CreateItemData::<T> {525 properties,526 owner: to,527 },528 &budget,529 )530 .map_err(dispatch_to_evm::<T>)?;531 Ok(true)532 }533534 535 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {536 Err("not implementable".into())537 }538}539540fn get_token_property<T: Config>(541 collection: &CollectionHandle<T>,542 token_id: u32,543 key: &up_data_structs::PropertyKey,544) -> Result<string> {545 collection.consume_store_reads(1)?;546 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))547 .map_err(|_| Error::Revert("Token properties not found".into()))?;548 if let Some(property) = properties.get(key) {549 return Ok(string::from_utf8_lossy(property).into());550 }551552 Err("Property tokenURI not found".into())553}554555fn get_token_permission<T: Config>(556 collection_id: CollectionId,557 key: &PropertyKey,558) -> Result<PropertyPermission> {559 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)560 .map_err(|_| Error::Revert("No permissions for collection".into()))?;561 let a = token_property_permissions562 .get(key)563 .map(Clone::clone)564 .ok_or_else(|| {565 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();566 Error::Revert(alloc::format!("No permission for key {}", key))567 })?;568 Ok(a)569}570571572#[solidity_interface(name = ERC721UniqueExtensions)]573impl<T: Config> NonfungibleHandle<T> {574 575 576 577 578 579 #[weight(<SelfWeightOf<T>>::transfer())]580 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {581 let caller = T::CrossAccountId::from_eth(caller);582 let to = T::CrossAccountId::from_eth(to);583 let token = token_id.try_into()?;584 let budget = self585 .recorder586 .weight_calls_budget(<StructureWeight<T>>::find_parent());587588 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;589 Ok(())590 }591592 593 594 595 596 597 598 #[weight(<SelfWeightOf<T>>::burn_from())]599 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {600 let caller = T::CrossAccountId::from_eth(caller);601 let from = T::CrossAccountId::from_eth(from);602 let token = token_id.try_into()?;603 let budget = self604 .recorder605 .weight_calls_budget(<StructureWeight<T>>::find_parent());606607 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)608 .map_err(dispatch_to_evm::<T>)?;609 Ok(())610 }611612 613 fn next_token_id(&self) -> Result<uint256> {614 self.consume_store_reads(1)?;615 Ok(<TokensMinted<T>>::get(self.id)616 .checked_add(1)617 .ok_or("item id overflow")?618 .into())619 }620621 622 623 624 625 626 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]627 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {628 let caller = T::CrossAccountId::from_eth(caller);629 let to = T::CrossAccountId::from_eth(to);630 let mut expected_index = <TokensMinted<T>>::get(self.id)631 .checked_add(1)632 .ok_or("item id overflow")?;633 let budget = self634 .recorder635 .weight_calls_budget(<StructureWeight<T>>::find_parent());636637 let total_tokens = token_ids.len();638 for id in token_ids.into_iter() {639 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;640 if id != expected_index {641 return Err("item id should be next".into());642 }643 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;644 }645 let data = (0..total_tokens)646 .map(|_| CreateItemData::<T> {647 properties: BoundedVec::default(),648 owner: to.clone(),649 })650 .collect();651652 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)653 .map_err(dispatch_to_evm::<T>)?;654 Ok(true)655 }656657 658 659 660 661 662 #[solidity(rename_selector = "mintBulkWithTokenURI")]663 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]664 fn mint_bulk_with_token_uri(665 &mut self,666 caller: caller,667 to: address,668 tokens: Vec<(uint256, string)>,669 ) -> Result<bool> {670 let key = key::url();671 let caller = T::CrossAccountId::from_eth(caller);672 let to = T::CrossAccountId::from_eth(to);673 let mut expected_index = <TokensMinted<T>>::get(self.id)674 .checked_add(1)675 .ok_or("item id overflow")?;676 let budget = self677 .recorder678 .weight_calls_budget(<StructureWeight<T>>::find_parent());679680 let mut data = Vec::with_capacity(tokens.len());681 for (id, token_uri) in tokens {682 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;683 if id != expected_index {684 return Err("item id should be next".into());685 }686 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;687688 let mut properties = CollectionPropertiesVec::default();689 properties690 .try_push(Property {691 key: key.clone(),692 value: token_uri693 .into_bytes()694 .try_into()695 .map_err(|_| "token uri is too long")?,696 })697 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;698699 data.push(CreateItemData::<T> {700 properties,701 owner: to.clone(),702 });703 }704705 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)706 .map_err(dispatch_to_evm::<T>)?;707 Ok(true)708 }709}710711impl<T: Config> NonfungibleHandle<T> {712 pub fn supports_metadata(&self) -> bool {713 let has_metadata_support_enabled = if let Some(erc721_metadata) =714 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())715 {716 *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED717 } else {718 false719 };720721 let has_url_property_permissions = get_token_permission::<T>(self.id, &key::url()).is_ok();722723 has_metadata_support_enabled && has_url_property_permissions724 }725}726727#[solidity_interface(728 name = UniqueNFT,729 is(730 ERC721,731 ERC721Enumerable,732 ERC721UniqueExtensions,733 ERC721Mintable,734 ERC721Burnable,735 Collection(via(common_mut returns CollectionHandle<T>)),736 TokenProperties,737 ERC721Metadata(if(this.supports_metadata())),738 )739)]740impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}741742743generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);744generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);745746impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>747where748 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,749{750 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");751752 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {753 call::<T, UniqueNFTCall<T>, _, _>(handle, self)754 }755}