difftreelog
Merge pull request #778 from UniqueNetwork/fix/add-weight-to-nft-and-rft-evm-methods
in: master
2 files changed
pallets/nonfungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::EthCrossAccount,42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::Get;4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 SelfWeightOf, weights::WeightInfo, TokenProperties,51};5253/// @title A contract that allows to set and delete token properties and change token property permissions.54#[solidity_interface(name = TokenProperties)]55impl<T: Config> NonfungibleHandle<T> {56 /// @notice Set permissions for token property.57 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.58 /// @param key Property key.59 /// @param isMutable Permission to mutate property.60 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.61 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.62 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]63 fn set_token_property_permission(64 &mut self,65 caller: caller,66 key: string,67 is_mutable: bool,68 collection_admin: bool,69 token_owner: bool,70 ) -> Result<()> {71 let caller = T::CrossAccountId::from_eth(caller);72 <Pallet<T>>::set_property_permission(73 self,74 &caller,75 PropertyKeyPermission {76 key: <Vec<u8>>::from(key)77 .try_into()78 .map_err(|_| "too long key")?,79 permission: PropertyPermission {80 mutable: is_mutable,81 collection_admin,82 token_owner,83 },84 },85 )86 .map_err(dispatch_to_evm::<T>)87 }8889 /// @notice Set token property value.90 /// @dev Throws error if `msg.sender` has no permission to edit the property.91 /// @param tokenId ID of the token.92 /// @param key Property key.93 /// @param value Property value.94 #[solidity(hide)]95 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]96 fn set_property(97 &mut self,98 caller: caller,99 token_id: uint256,100 key: string,101 value: bytes,102 ) -> Result<()> {103 let caller = T::CrossAccountId::from_eth(caller);104 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105 let key = <Vec<u8>>::from(key)106 .try_into()107 .map_err(|_| "key too long")?;108 let value = value.0.try_into().map_err(|_| "value too long")?;109110 let nesting_budget = self111 .recorder112 .weight_calls_budget(<StructureWeight<T>>::find_parent());113114 <Pallet<T>>::set_token_property(115 self,116 &caller,117 TokenId(token_id),118 Property { key, value },119 &nesting_budget,120 )121 .map_err(dispatch_to_evm::<T>)122 }123124 /// @notice Set token properties value.125 /// @dev Throws error if `msg.sender` has no permission to edit the property.126 /// @param tokenId ID of the token.127 /// @param properties settable properties128 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]129 fn set_properties(130 &mut self,131 caller: caller,132 token_id: uint256,133 properties: Vec<PropertyStruct>,134 ) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;137138 let nesting_budget = self139 .recorder140 .weight_calls_budget(<StructureWeight<T>>::find_parent());141142 let properties = properties143 .into_iter()144 .map(|PropertyStruct { key, value }| {145 let key = <Vec<u8>>::from(key)146 .try_into()147 .map_err(|_| "key too large")?;148149 let value = value.0.try_into().map_err(|_| "value too large")?;150151 Ok(Property { key, value })152 })153 .collect::<Result<Vec<_>>>()?;154155 <Pallet<T>>::set_token_properties(156 self,157 &caller,158 TokenId(token_id),159 properties.into_iter(),160 false,161 &nesting_budget,162 )163 .map_err(dispatch_to_evm::<T>)164 }165166 /// @notice Delete token property value.167 /// @dev Throws error if `msg.sender` has no permission to edit the property.168 /// @param tokenId ID of the token.169 /// @param key Property key.170 #[solidity(hide)]171 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]172 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {173 let caller = T::CrossAccountId::from_eth(caller);174 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;175 let key = <Vec<u8>>::from(key)176 .try_into()177 .map_err(|_| "key too long")?;178179 let nesting_budget = self180 .recorder181 .weight_calls_budget(<StructureWeight<T>>::find_parent());182183 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)184 .map_err(dispatch_to_evm::<T>)185 }186187 /// @notice Delete token properties value.188 /// @dev Throws error if `msg.sender` has no permission to edit the property.189 /// @param tokenId ID of the token.190 /// @param keys Properties key.191 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]192 fn delete_properties(193 &mut self,194 token_id: uint256,195 caller: caller,196 keys: Vec<string>,197 ) -> Result<()> {198 let caller = T::CrossAccountId::from_eth(caller);199 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;200 let keys = keys201 .into_iter()202 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))203 .collect::<Result<Vec<_>>>()?;204205 let nesting_budget = self206 .recorder207 .weight_calls_budget(<StructureWeight<T>>::find_parent());208209 <Pallet<T>>::delete_token_properties(210 self,211 &caller,212 TokenId(token_id),213 keys.into_iter(),214 &nesting_budget,215 )216 .map_err(dispatch_to_evm::<T>)217 }218219 /// @notice Get token property value.220 /// @dev Throws error if key not found221 /// @param tokenId ID of the token.222 /// @param key Property key.223 /// @return Property value bytes224 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {225 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226 let key = <Vec<u8>>::from(key)227 .try_into()228 .map_err(|_| "key too long")?;229230 let props = <TokenProperties<T>>::get((self.id, token_id));231 let prop = props.get(&key).ok_or("key not found")?;232233 Ok(prop.to_vec().into())234 }235}236237#[derive(ToLog)]238pub enum ERC721Events {239 /// @dev This emits when ownership of any NFT changes by any mechanism.240 /// This event emits when NFTs are created (`from` == 0) and destroyed241 /// (`to` == 0). Exception: during contract creation, any number of NFTs242 /// may be created and assigned without emitting Transfer. At the time of243 /// any transfer, the approved address for that NFT (if any) is reset to none.244 Transfer {245 #[indexed]246 from: address,247 #[indexed]248 to: address,249 #[indexed]250 token_id: uint256,251 },252 /// @dev This emits when the approved address for an NFT is changed or253 /// reaffirmed. The zero address indicates there is no approved address.254 /// When a Transfer event emits, this also indicates that the approved255 /// address for that NFT (if any) is reset to none.256 Approval {257 #[indexed]258 owner: address,259 #[indexed]260 approved: address,261 #[indexed]262 token_id: uint256,263 },264 /// @dev This emits when an operator is enabled or disabled for an owner.265 /// The operator can manage all NFTs of the owner.266 #[allow(dead_code)]267 ApprovalForAll {268 #[indexed]269 owner: address,270 #[indexed]271 operator: address,272 approved: bool,273 },274}275276#[derive(ToLog)]277pub enum ERC721UniqueMintableEvents {278 #[allow(dead_code)]279 MintingFinished {},280}281282/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension283/// @dev See https://eips.ethereum.org/EIPS/eip-721284#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]285impl<T: Config> NonfungibleHandle<T>286where287 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,288{289 /// @notice A descriptive name for a collection of NFTs in this contract290 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`291 #[solidity(hide, rename_selector = "name")]292 fn name_proxy(&self) -> Result<string> {293 self.name()294 }295296 /// @notice An abbreviated name for NFTs in this contract297 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`298 #[solidity(hide, rename_selector = "symbol")]299 fn symbol_proxy(&self) -> Result<string> {300 self.symbol()301 }302303 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.304 ///305 /// @dev If the token has a `url` property and it is not empty, it is returned.306 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.307 /// If the collection property `baseURI` is empty or absent, return "" (empty string)308 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix309 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).310 ///311 /// @return token's const_metadata312 #[solidity(rename_selector = "tokenURI")]313 fn token_uri(&self, token_id: uint256) -> Result<string> {314 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;315316 match get_token_property(self, token_id_u32, &key::url()).as_deref() {317 Err(_) | Ok("") => (),318 Ok(url) => {319 return Ok(url.into());320 }321 };322323 let base_uri =324 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())325 .map(BoundedVec::into_inner)326 .map(string::from_utf8)327 .transpose()328 .map_err(|e| {329 Error::Revert(alloc::format!(330 "Can not convert value \"baseURI\" to string with error \"{}\"",331 e332 ))333 })?;334335 let base_uri = match base_uri.as_deref() {336 None | Some("") => {337 return Ok("".into());338 }339 Some(base_uri) => base_uri.into(),340 };341342 Ok(343 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {344 Err(_) | Ok("") => base_uri,345 Ok(suffix) => base_uri + suffix,346 },347 )348 }349}350351/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension352/// @dev See https://eips.ethereum.org/EIPS/eip-721353#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]354impl<T: Config> NonfungibleHandle<T> {355 /// @notice Enumerate valid NFTs356 /// @param index A counter less than `totalSupply()`357 /// @return The token identifier for the `index`th NFT,358 /// (sort order not specified)359 fn token_by_index(&self, index: uint256) -> Result<uint256> {360 Ok(index)361 }362363 /// @dev Not implemented364 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {365 // TODO: Not implemetable366 Err("not implemented".into())367 }368369 /// @notice Count NFTs tracked by this contract370 /// @return A count of valid NFTs tracked by this contract, where each one of371 /// them has an assigned and queryable owner not equal to the zero address372 fn total_supply(&self) -> Result<uint256> {373 self.consume_store_reads(1)?;374 Ok(<Pallet<T>>::total_supply(self).into())375 }376}377378/// @title ERC-721 Non-Fungible Token Standard379/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md380#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]381impl<T: Config> NonfungibleHandle<T> {382 /// @notice Count all NFTs assigned to an owner383 /// @dev NFTs assigned to the zero address are considered invalid, and this384 /// function throws for queries about the zero address.385 /// @param owner An address for whom to query the balance386 /// @return The number of NFTs owned by `owner`, possibly zero387 fn balance_of(&self, owner: address) -> Result<uint256> {388 self.consume_store_reads(1)?;389 let owner = T::CrossAccountId::from_eth(owner);390 let balance = <AccountBalance<T>>::get((self.id, owner));391 Ok(balance.into())392 }393 /// @notice Find the owner of an NFT394 /// @dev NFTs assigned to zero address are considered invalid, and queries395 /// about them do throw.396 /// @param tokenId The identifier for an NFT397 /// @return The address of the owner of the NFT398 fn owner_of(&self, token_id: uint256) -> Result<address> {399 self.consume_store_reads(1)?;400 let token: TokenId = token_id.try_into()?;401 Ok(*<TokenData<T>>::get((self.id, token))402 .ok_or("token not found")?403 .owner404 .as_eth())405 }406 /// @dev Not implemented407 #[solidity(rename_selector = "safeTransferFrom")]408 fn safe_transfer_from_with_data(409 &mut self,410 _from: address,411 _to: address,412 _token_id: uint256,413 _data: bytes,414 ) -> Result<void> {415 // TODO: Not implemetable416 Err("not implemented".into())417 }418 /// @dev Not implemented419 fn safe_transfer_from(420 &mut self,421 _from: address,422 _to: address,423 _token_id: uint256,424 ) -> Result<void> {425 // TODO: Not implemetable426 Err("not implemented".into())427 }428429 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE430 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE431 /// THEY MAY BE PERMANENTLY LOST432 /// @dev Throws unless `msg.sender` is the current owner or an authorized433 /// operator for this NFT. Throws if `from` is not the current owner. Throws434 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.435 /// @param from The current owner of the NFT436 /// @param to The new owner437 /// @param tokenId The NFT to transfer438 #[weight(<SelfWeightOf<T>>::transfer_from())]439 fn transfer_from(440 &mut self,441 caller: caller,442 from: address,443 to: address,444 token_id: uint256,445 ) -> Result<void> {446 let caller = T::CrossAccountId::from_eth(caller);447 let from = T::CrossAccountId::from_eth(from);448 let to = T::CrossAccountId::from_eth(to);449 let token = token_id.try_into()?;450 let budget = self451 .recorder452 .weight_calls_budget(<StructureWeight<T>>::find_parent());453454 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)455 .map_err(dispatch_to_evm::<T>)?;456 Ok(())457 }458459 /// @notice Set or reaffirm the approved address for an NFT460 /// @dev The zero address indicates there is no approved address.461 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized462 /// operator of the current owner.463 /// @param approved The new approved NFT controller464 /// @param tokenId The NFT to approve465 #[weight(<SelfWeightOf<T>>::approve())]466 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {467 let caller = T::CrossAccountId::from_eth(caller);468 let approved = T::CrossAccountId::from_eth(approved);469 let token = token_id.try_into()?;470471 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))472 .map_err(dispatch_to_evm::<T>)?;473 Ok(())474 }475476 /// @notice Sets or unsets the approval of a given operator.477 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.478 /// @param operator Operator479 /// @param approved Should operator status be granted or revoked?480 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]481 fn set_approval_for_all(482 &mut self,483 caller: caller,484 operator: address,485 approved: bool,486 ) -> Result<void> {487 let caller = T::CrossAccountId::from_eth(caller);488 let operator = T::CrossAccountId::from_eth(operator);489490 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)491 .map_err(dispatch_to_evm::<T>)?;492 Ok(())493 }494495 /// @dev Not implemented496 fn get_approved(&self, _token_id: uint256) -> Result<address> {497 // TODO: Not implemetable498 Err("not implemented".into())499 }500501 /// @notice Tells whether the given `owner` approves the `operator`.502 #[weight(<SelfWeightOf<T>>::allowance_for_all())]503 fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {504 let owner = T::CrossAccountId::from_eth(owner);505 let operator = T::CrossAccountId::from_eth(operator);506507 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))508 }509510 /// @notice Returns collection helper contract address511 fn collection_helper_address(&self) -> Result<address> {512 Ok(T::ContractAddress::get())513 }514}515516/// @title ERC721 Token that can be irreversibly burned (destroyed).517#[solidity_interface(name = ERC721Burnable)]518impl<T: Config> NonfungibleHandle<T> {519 /// @notice Burns a specific ERC721 token.520 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized521 /// operator of the current owner.522 /// @param tokenId The NFT to approve523 #[weight(<SelfWeightOf<T>>::burn_item())]524 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {525 let caller = T::CrossAccountId::from_eth(caller);526 let token = token_id.try_into()?;527528 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;529 Ok(())530 }531}532533/// @title ERC721 minting logic.534#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]535impl<T: Config> NonfungibleHandle<T> {536 fn minting_finished(&self) -> Result<bool> {537 Ok(false)538 }539540 /// @notice Function to mint token.541 /// @param to The new owner542 /// @return uint256 The id of the newly minted token543 #[weight(<SelfWeightOf<T>>::create_item())]544 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {545 let token_id: uint256 = <TokensMinted<T>>::get(self.id)546 .checked_add(1)547 .ok_or("item id overflow")?548 .into();549 self.mint_check_id(caller, to, token_id)?;550 Ok(token_id)551 }552553 /// @notice Function to mint token.554 /// @dev `tokenId` should be obtained with `nextTokenId` method,555 /// unlike standard, you can't specify it manually556 /// @param to The new owner557 /// @param tokenId ID of the minted NFT558 #[solidity(hide, rename_selector = "mint")]559 #[weight(<SelfWeightOf<T>>::create_item())]560 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {561 let caller = T::CrossAccountId::from_eth(caller);562 let to = T::CrossAccountId::from_eth(to);563 let token_id: u32 = token_id.try_into()?;564 let budget = self565 .recorder566 .weight_calls_budget(<StructureWeight<T>>::find_parent());567568 if <TokensMinted<T>>::get(self.id)569 .checked_add(1)570 .ok_or("item id overflow")?571 != token_id572 {573 return Err("item id should be next".into());574 }575576 <Pallet<T>>::create_item(577 self,578 &caller,579 CreateItemData::<T> {580 properties: BoundedVec::default(),581 owner: to,582 },583 &budget,584 )585 .map_err(dispatch_to_evm::<T>)?;586587 Ok(true)588 }589590 /// @notice Function to mint token with the given tokenUri.591 /// @param to The new owner592 /// @param tokenUri Token URI that would be stored in the NFT properties593 /// @return uint256 The id of the newly minted token594 #[solidity(rename_selector = "mintWithTokenURI")]595 #[weight(<SelfWeightOf<T>>::create_item())]596 fn mint_with_token_uri(597 &mut self,598 caller: caller,599 to: address,600 token_uri: string,601 ) -> Result<uint256> {602 let token_id: uint256 = <TokensMinted<T>>::get(self.id)603 .checked_add(1)604 .ok_or("item id overflow")?605 .into();606 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;607 Ok(token_id)608 }609610 /// @notice Function to mint token with the given tokenUri.611 /// @dev `tokenId` should be obtained with `nextTokenId` method,612 /// unlike standard, you can't specify it manually613 /// @param to The new owner614 /// @param tokenId ID of the minted NFT615 /// @param tokenUri Token URI that would be stored in the NFT properties616 #[solidity(hide, rename_selector = "mintWithTokenURI")]617 #[weight(<SelfWeightOf<T>>::create_item())]618 fn mint_with_token_uri_check_id(619 &mut self,620 caller: caller,621 to: address,622 token_id: uint256,623 token_uri: string,624 ) -> Result<bool> {625 let key = key::url();626 let permission = get_token_permission::<T>(self.id, &key)?;627 if !permission.collection_admin {628 return Err("Operation is not allowed".into());629 }630631 let caller = T::CrossAccountId::from_eth(caller);632 let to = T::CrossAccountId::from_eth(to);633 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;634 let budget = self635 .recorder636 .weight_calls_budget(<StructureWeight<T>>::find_parent());637638 if <TokensMinted<T>>::get(self.id)639 .checked_add(1)640 .ok_or("item id overflow")?641 != token_id642 {643 return Err("item id should be next".into());644 }645646 let mut properties = CollectionPropertiesVec::default();647 properties648 .try_push(Property {649 key,650 value: token_uri651 .into_bytes()652 .try_into()653 .map_err(|_| "token uri is too long")?,654 })655 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;656657 <Pallet<T>>::create_item(658 self,659 &caller,660 CreateItemData::<T> {661 properties,662 owner: to,663 },664 &budget,665 )666 .map_err(dispatch_to_evm::<T>)?;667 Ok(true)668 }669670 /// @dev Not implemented671 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {672 Err("not implementable".into())673 }674}675676fn get_token_property<T: Config>(677 collection: &CollectionHandle<T>,678 token_id: u32,679 key: &up_data_structs::PropertyKey,680) -> Result<string> {681 collection.consume_store_reads(1)?;682 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))683 .map_err(|_| Error::Revert("Token properties not found".into()))?;684 if let Some(property) = properties.get(key) {685 return Ok(string::from_utf8_lossy(property).into());686 }687688 Err("Property tokenURI not found".into())689}690691fn get_token_permission<T: Config>(692 collection_id: CollectionId,693 key: &PropertyKey,694) -> Result<PropertyPermission> {695 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)696 .map_err(|_| Error::Revert("No permissions for collection".into()))?;697 let a = token_property_permissions698 .get(key)699 .map(Clone::clone)700 .ok_or_else(|| {701 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();702 Error::Revert(alloc::format!("No permission for key {}", key))703 })?;704 Ok(a)705}706707/// @title Unique extensions for ERC721.708#[solidity_interface(name = ERC721UniqueExtensions)]709impl<T: Config> NonfungibleHandle<T>710where711 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,712{713 /// @notice A descriptive name for a collection of NFTs in this contract714 fn name(&self) -> Result<string> {715 Ok(decode_utf16(self.name.iter().copied())716 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))717 .collect::<string>())718 }719720 /// @notice An abbreviated name for NFTs in this contract721 fn symbol(&self) -> Result<string> {722 Ok(string::from_utf8_lossy(&self.token_prefix).into())723 }724725 /// @notice A description for the collection.726 fn description(&self) -> Result<string> {727 Ok(decode_utf16(self.description.iter().copied())728 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))729 .collect::<string>())730 }731732 /// Returns the owner (in cross format) of the token.733 ///734 /// @param tokenId Id for the token.735 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {736 Self::token_owner(&self, token_id.try_into()?)737 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))738 .ok_or(Error::Revert("key too large".into()))739 }740741 /// Returns the token properties.742 ///743 /// @param tokenId Id for the token.744 /// @param keys Properties keys. Empty keys for all propertyes.745 /// @return Vector of properties key/value pairs.746 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {747 let keys = keys748 .into_iter()749 .map(|key| {750 <Vec<u8>>::from(key)751 .try_into()752 .map_err(|_| Error::Revert("key too large".into()))753 })754 .collect::<Result<Vec<_>>>()?;755756 <Self as CommonCollectionOperations<T>>::token_properties(757 &self,758 token_id.try_into()?,759 if keys.is_empty() { None } else { Some(keys) },760 )761 .into_iter()762 .map(|p| {763 let key = string::from_utf8(p.key.to_vec())764 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;765 let value = bytes(p.value.to_vec());766 Ok(PropertyStruct { key, value })767 })768 .collect::<Result<Vec<_>>>()769 }770771 /// @notice Set or reaffirm the approved address for an NFT772 /// @dev The zero address indicates there is no approved address.773 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized774 /// operator of the current owner.775 /// @param approved The new substrate address approved NFT controller776 /// @param tokenId The NFT to approve777 #[weight(<SelfWeightOf<T>>::approve())]778 fn approve_cross(779 &mut self,780 caller: caller,781 approved: EthCrossAccount,782 token_id: uint256,783 ) -> Result<void> {784 let caller = T::CrossAccountId::from_eth(caller);785 let approved = approved.into_sub_cross_account::<T>()?;786 let token = token_id.try_into()?;787788 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))789 .map_err(dispatch_to_evm::<T>)?;790 Ok(())791 }792793 /// @notice Transfer ownership of an NFT794 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`795 /// is the zero address. Throws if `tokenId` is not a valid NFT.796 /// @param to The new owner797 /// @param tokenId The NFT to transfer798 #[weight(<SelfWeightOf<T>>::transfer())]799 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {800 let caller = T::CrossAccountId::from_eth(caller);801 let to = T::CrossAccountId::from_eth(to);802 let token = token_id.try_into()?;803 let budget = self804 .recorder805 .weight_calls_budget(<StructureWeight<T>>::find_parent());806807 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;808 Ok(())809 }810811 /// @notice Transfer ownership of an NFT812 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`813 /// is the zero address. Throws if `tokenId` is not a valid NFT.814 /// @param to The new owner815 /// @param tokenId The NFT to transfer816 #[weight(<SelfWeightOf<T>>::transfer())]817 fn transfer_cross(818 &mut self,819 caller: caller,820 to: EthCrossAccount,821 token_id: uint256,822 ) -> Result<void> {823 let caller = T::CrossAccountId::from_eth(caller);824 let to = to.into_sub_cross_account::<T>()?;825 let token = token_id.try_into()?;826 let budget = self827 .recorder828 .weight_calls_budget(<StructureWeight<T>>::find_parent());829830 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;831 Ok(())832 }833834 /// @notice Transfer ownership of an NFT from cross account address to cross account address835 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`836 /// is the zero address. Throws if `tokenId` is not a valid NFT.837 /// @param from Cross acccount address of current owner838 /// @param to Cross acccount address of new owner839 /// @param tokenId The NFT to transfer840 #[weight(<SelfWeightOf<T>>::transfer())]841 fn transfer_from_cross(842 &mut self,843 caller: caller,844 from: EthCrossAccount,845 to: EthCrossAccount,846 token_id: uint256,847 ) -> Result<void> {848 let caller = T::CrossAccountId::from_eth(caller);849 let from = from.into_sub_cross_account::<T>()?;850 let to = to.into_sub_cross_account::<T>()?;851 let token_id = token_id.try_into()?;852 let budget = self853 .recorder854 .weight_calls_budget(<StructureWeight<T>>::find_parent());855 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)856 .map_err(dispatch_to_evm::<T>)?;857 Ok(())858 }859860 /// @notice Burns a specific ERC721 token.861 /// @dev Throws unless `msg.sender` is the current owner or an authorized862 /// operator for this NFT. Throws if `from` is not the current owner. Throws863 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.864 /// @param from The current owner of the NFT865 /// @param tokenId The NFT to transfer866 #[solidity(hide)]867 #[weight(<SelfWeightOf<T>>::burn_from())]868 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {869 let caller = T::CrossAccountId::from_eth(caller);870 let from = T::CrossAccountId::from_eth(from);871 let token = token_id.try_into()?;872 let budget = self873 .recorder874 .weight_calls_budget(<StructureWeight<T>>::find_parent());875876 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)877 .map_err(dispatch_to_evm::<T>)?;878 Ok(())879 }880881 /// @notice Burns a specific ERC721 token.882 /// @dev Throws unless `msg.sender` is the current owner or an authorized883 /// operator for this NFT. Throws if `from` is not the current owner. Throws884 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.885 /// @param from The current owner of the NFT886 /// @param tokenId The NFT to transfer887 #[weight(<SelfWeightOf<T>>::burn_from())]888 fn burn_from_cross(889 &mut self,890 caller: caller,891 from: EthCrossAccount,892 token_id: uint256,893 ) -> Result<void> {894 let caller = T::CrossAccountId::from_eth(caller);895 let from = from.into_sub_cross_account::<T>()?;896 let token = token_id.try_into()?;897 let budget = self898 .recorder899 .weight_calls_budget(<StructureWeight<T>>::find_parent());900901 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)902 .map_err(dispatch_to_evm::<T>)?;903 Ok(())904 }905906 /// @notice Returns next free NFT ID.907 fn next_token_id(&self) -> Result<uint256> {908 self.consume_store_reads(1)?;909 Ok(<TokensMinted<T>>::get(self.id)910 .checked_add(1)911 .ok_or("item id overflow")?912 .into())913 }914915 /// @notice Function to mint multiple tokens.916 /// @dev `tokenIds` should be an array of consecutive numbers and first number917 /// should be obtained with `nextTokenId` method918 /// @param to The new owner919 /// @param tokenIds IDs of the minted NFTs920 #[solidity(hide)]921 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]922 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {923 let caller = T::CrossAccountId::from_eth(caller);924 let to = T::CrossAccountId::from_eth(to);925 let mut expected_index = <TokensMinted<T>>::get(self.id)926 .checked_add(1)927 .ok_or("item id overflow")?;928 let budget = self929 .recorder930 .weight_calls_budget(<StructureWeight<T>>::find_parent());931932 let total_tokens = token_ids.len();933 for id in token_ids.into_iter() {934 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;935 if id != expected_index {936 return Err("item id should be next".into());937 }938 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;939 }940 let data = (0..total_tokens)941 .map(|_| CreateItemData::<T> {942 properties: BoundedVec::default(),943 owner: to.clone(),944 })945 .collect();946947 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)948 .map_err(dispatch_to_evm::<T>)?;949 Ok(true)950 }951952 /// @notice Function to mint multiple tokens with the given tokenUris.953 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive954 /// numbers and first number should be obtained with `nextTokenId` method955 /// @param to The new owner956 /// @param tokens array of pairs of token ID and token URI for minted tokens957 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]958 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]959 fn mint_bulk_with_token_uri(960 &mut self,961 caller: caller,962 to: address,963 tokens: Vec<(uint256, string)>,964 ) -> Result<bool> {965 let key = key::url();966 let caller = T::CrossAccountId::from_eth(caller);967 let to = T::CrossAccountId::from_eth(to);968 let mut expected_index = <TokensMinted<T>>::get(self.id)969 .checked_add(1)970 .ok_or("item id overflow")?;971 let budget = self972 .recorder973 .weight_calls_budget(<StructureWeight<T>>::find_parent());974975 let mut data = Vec::with_capacity(tokens.len());976 for (id, token_uri) in tokens {977 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;978 if id != expected_index {979 return Err("item id should be next".into());980 }981 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;982983 let mut properties = CollectionPropertiesVec::default();984 properties985 .try_push(Property {986 key: key.clone(),987 value: token_uri988 .into_bytes()989 .try_into()990 .map_err(|_| "token uri is too long")?,991 })992 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;993994 data.push(CreateItemData::<T> {995 properties,996 owner: to.clone(),997 });998 }9991000 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1001 .map_err(dispatch_to_evm::<T>)?;1002 Ok(true)1003 }1004}10051006#[solidity_interface(1007 name = UniqueNFT,1008 is(1009 ERC721,1010 ERC721Enumerable,1011 ERC721UniqueExtensions,1012 ERC721UniqueMintable,1013 ERC721Burnable,1014 ERC721Metadata(if(this.flags.erc721metadata)),1015 Collection(via(common_mut returns CollectionHandle<T>)),1016 TokenProperties,1017 )1018)]1019impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10201021// Not a tests, but code generators1022generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1023generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10241025impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1026where1027 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1028{1029 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10301031 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1032 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1033 }1034}pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -62,6 +62,7 @@
/// @param isMutable Permission to mutate property.
/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.
/// @param tokenOwner Permission to mutate property by token owner if property is mutable.
+ #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]
fn set_token_property_permission(
&mut self,
caller: caller,
@@ -94,6 +95,7 @@
/// @param key Property key.
/// @param value Property value.
#[solidity(hide)]
+ #[weight(<SelfWeightOf<T>>::set_token_properties(1))]
fn set_property(
&mut self,
caller: caller,
@@ -126,6 +128,7 @@
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
/// @param properties settable properties
+ #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]
fn set_properties(
&mut self,
caller: caller,
@@ -168,6 +171,7 @@
/// @param tokenId ID of the token.
/// @param key Property key.
#[solidity(hide)]
+ #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]
fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
@@ -187,6 +191,7 @@
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
/// @param keys Properties key.
+ #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]
fn delete_properties(
&mut self,
token_id: uint256,