difftreelog
fix some clippy warns
in: master
3 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -181,7 +181,7 @@
"aura": {
"authorities": invulnerables.into_iter()
- .map(|name| get_from_seed::<AuraId>(name))
+ .map(get_from_seed::<AuraId>)
.collect::<Vec<_>>(),
},
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;2324use alloc::string::ToString;25use core::{26 char::{decode_utf16, REPLACEMENT_CHARACTER},27 convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::BoundedVec;32use pallet_common::{33 erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34 eth::{self, TokenUri},35 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39 call, dispatch_to_evm,40 execution::{Error, PreDispatch, Result},41 frontier_contract, SubstrateRecorder,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47 budget::Budget, CollectionId, CollectionPropertiesVec, Property, PropertyKey,48 PropertyKeyPermission, PropertyPermission, TokenId,49};5051use crate::{52 common::{mint_with_props_weight, CommonWeights},53 weights::WeightInfo,54 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, SelfWeightOf, TokenData,55 TokenProperties, TokensMinted,56};5758/// Nft events.59#[derive(ToLog)]60pub enum ERC721TokenEvent {61 /// The token has been changed.62 TokenChanged {63 /// Token ID.64 #[indexed]65 token_id: U256,66 },67}6869/// Token minting parameters70#[derive(AbiCoder, Default, Debug)]71pub struct MintTokenData {72 /// Minted token owner73 pub owner: eth::CrossAddress,74 /// Minted token properties75 pub properties: Vec<eth::Property>,76}7778frontier_contract! {79 macro_rules! NonfungibleHandle_result {...}80 impl<T: Config> Contract for NonfungibleHandle<T> {...}81}8283fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {84 recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())85}8687/// @title A contract that allows to set and delete token properties and change token property permissions.88#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]89impl<T: Config> NonfungibleHandle<T> {90 /// @notice Set permissions for token property.91 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.92 /// @param key Property key.93 /// @param isMutable Permission to mutate property.94 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.95 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.96 #[solidity(hide)]97 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]98 fn set_token_property_permission(99 &mut self,100 caller: Caller,101 key: String,102 is_mutable: bool,103 collection_admin: bool,104 token_owner: bool,105 ) -> Result<()> {106 let caller = T::CrossAccountId::from_eth(caller);107 <Pallet<T>>::set_token_property_permissions(108 self,109 &caller,110 vec![PropertyKeyPermission {111 key: <Vec<u8>>::from(key)112 .try_into()113 .map_err(|_| "too long key")?,114 permission: PropertyPermission {115 mutable: is_mutable,116 collection_admin,117 token_owner,118 },119 }],120 )121 .map_err(dispatch_to_evm::<T>)122 }123124 /// @notice Set permissions for token property.125 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.126 /// @param permissions Permissions for keys.127 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]128 fn set_token_property_permissions(129 &mut self,130 caller: Caller,131 permissions: Vec<eth::TokenPropertyPermission>,132 ) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;135136 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)137 .map_err(dispatch_to_evm::<T>)138 }139140 /// @notice Get permissions for token properties.141 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {142 let perms = <Pallet<T>>::token_property_permission(self.id);143 Ok(perms144 .into_iter()145 .map(eth::TokenPropertyPermission::from)146 .collect())147 }148149 /// @notice Set token property value.150 /// @dev Throws error if `msg.sender` has no permission to edit the property.151 /// @param tokenId ID of the token.152 /// @param key Property key.153 /// @param value Property value.154 #[solidity(hide)]155 #[weight(<CommonWeights<T>>::set_token_properties(1))]156 fn set_property(157 &mut self,158 caller: Caller,159 token_id: U256,160 key: String,161 value: Bytes,162 ) -> Result<()> {163 let caller = T::CrossAccountId::from_eth(caller);164 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too long")?;168 let value = value.0.try_into().map_err(|_| "value too long")?;169170 <Pallet<T>>::set_token_property(171 self,172 &caller,173 TokenId(token_id),174 Property { key, value },175 &nesting_budget(&self.recorder),176 )177 .map_err(dispatch_to_evm::<T>)178 }179180 /// @notice Set token properties value.181 /// @dev Throws error if `msg.sender` has no permission to edit the property.182 /// @param tokenId ID of the token.183 /// @param properties settable properties184 #[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]185 fn set_properties(186 &mut self,187 caller: Caller,188 token_id: U256,189 properties: Vec<eth::Property>,190 ) -> Result<()> {191 let caller = T::CrossAccountId::from_eth(caller);192 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193194 let properties = properties195 .into_iter()196 .map(eth::Property::try_into)197 .collect::<Result<Vec<_>>>()?;198199 <Pallet<T>>::set_token_properties(200 self,201 &caller,202 TokenId(token_id),203 properties.into_iter(),204 &nesting_budget(&self.recorder),205 )206 .map_err(dispatch_to_evm::<T>)207 }208209 /// @notice Delete token property value.210 /// @dev Throws error if `msg.sender` has no permission to edit the property.211 /// @param tokenId ID of the token.212 /// @param key Property key.213 #[solidity(hide)]214 #[weight(<CommonWeights<T>>::delete_token_properties(1))]215 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {216 let caller = T::CrossAccountId::from_eth(caller);217 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;218 let key = <Vec<u8>>::from(key)219 .try_into()220 .map_err(|_| "key too long")?;221222 <Pallet<T>>::delete_token_property(223 self,224 &caller,225 TokenId(token_id),226 key,227 &nesting_budget(&self.recorder),228 )229 .map_err(dispatch_to_evm::<T>)230 }231232 /// @notice Delete token properties value.233 /// @dev Throws error if `msg.sender` has no permission to edit the property.234 /// @param tokenId ID of the token.235 /// @param keys Properties key.236 #[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]237 fn delete_properties(238 &mut self,239 token_id: U256,240 caller: Caller,241 keys: Vec<String>,242 ) -> Result<()> {243 let caller = T::CrossAccountId::from_eth(caller);244 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;245 let keys = keys246 .into_iter()247 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))248 .collect::<Result<Vec<_>>>()?;249250 <Pallet<T>>::delete_token_properties(251 self,252 &caller,253 TokenId(token_id),254 keys.into_iter(),255 &nesting_budget(&self.recorder),256 )257 .map_err(dispatch_to_evm::<T>)258 }259260 /// @notice Get token property value.261 /// @dev Throws error if key not found262 /// @param tokenId ID of the token.263 /// @param key Property key.264 /// @return Property value bytes265 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {266 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;267 let key = <Vec<u8>>::from(key)268 .try_into()269 .map_err(|_| "key too long")?;270271 let props =272 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;273 let prop = props.get(&key).ok_or("key not found")?;274275 Ok(prop.to_vec().into())276 }277}278279#[derive(ToLog)]280pub enum ERC721Events {281 /// @dev This emits when ownership of any NFT changes by any mechanism.282 /// This event emits when NFTs are created (`from` == 0) and destroyed283 /// (`to` == 0). Exception: during contract creation, any number of NFTs284 /// may be created and assigned without emitting Transfer. At the time of285 /// any transfer, the approved address for that NFT (if any) is reset to none.286 Transfer {287 #[indexed]288 from: Address,289 #[indexed]290 to: Address,291 #[indexed]292 token_id: U256,293 },294 /// @dev This emits when the approved address for an NFT is changed or295 /// reaffirmed. The zero address indicates there is no approved address.296 /// When a Transfer event emits, this also indicates that the approved297 /// address for that NFT (if any) is reset to none.298 Approval {299 #[indexed]300 owner: Address,301 #[indexed]302 approved: Address,303 #[indexed]304 token_id: U256,305 },306 /// @dev This emits when an operator is enabled or disabled for an owner.307 /// The operator can manage all NFTs of the owner.308 #[allow(dead_code)]309 ApprovalForAll {310 #[indexed]311 owner: Address,312 #[indexed]313 operator: Address,314 approved: bool,315 },316}317318/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension319/// @dev See https://eips.ethereum.org/EIPS/eip-721320#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]321impl<T: Config> NonfungibleHandle<T>322where323 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,324{325 /// @notice A descriptive name for a collection of NFTs in this contract326 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`327 #[solidity(hide, rename_selector = "name")]328 fn name_proxy(&self) -> String {329 self.name()330 }331332 /// @notice An abbreviated name for NFTs in this contract333 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`334 #[solidity(hide, rename_selector = "symbol")]335 fn symbol_proxy(&self) -> String {336 self.symbol()337 }338339 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.340 ///341 /// @dev If the token has a `url` property and it is not empty, it is returned.342 /// 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`.343 /// If the collection property `baseURI` is empty or absent, return "" (empty string)344 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix345 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).346 ///347 /// @return token's const_metadata348 #[solidity(rename_selector = "tokenURI")]349 fn token_uri(&self, token_id: U256) -> Result<String> {350 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;351352 match get_token_property(self, token_id_u32, &key::url()).as_deref() {353 Err(_) | Ok("") => (),354 Ok(url) => {355 return Ok(url.into());356 }357 };358359 let base_uri =360 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())361 .map(BoundedVec::into_inner)362 .map(String::from_utf8)363 .transpose()364 .map_err(|e| {365 Error::Revert(alloc::format!(366 "can not convert value \"baseURI\" to string with error \"{e}\""367 ))368 })?;369370 let base_uri = match base_uri.as_deref() {371 None | Some("") => {372 return Ok("".into());373 }374 Some(base_uri) => base_uri.into(),375 };376377 Ok(378 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {379 Err(_) | Ok("") => base_uri,380 Ok(suffix) => base_uri + suffix,381 },382 )383 }384}385386/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension387/// @dev See https://eips.ethereum.org/EIPS/eip-721388#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]389impl<T: Config> NonfungibleHandle<T> {390 /// @notice Enumerate valid NFTs391 /// @param index A counter less than `totalSupply()`392 /// @return The token identifier for the `index`th NFT,393 /// (sort order not specified)394 fn token_by_index(&self, index: U256) -> U256 {395 index396 }397398 /// @dev Not implemented399 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403404 /// @notice Count NFTs tracked by this contract405 /// @return A count of valid NFTs tracked by this contract, where each one of406 /// them has an assigned and queryable owner not equal to the zero address407 fn total_supply(&self) -> Result<U256> {408 self.consume_store_reads(1)?;409 Ok(<Pallet<T>>::total_supply(self).into())410 }411}412413/// @title ERC-721 Non-Fungible Token Standard414/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md415#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]416impl<T: Config> NonfungibleHandle<T> {417 /// @notice Count all NFTs assigned to an owner418 /// @dev NFTs assigned to the zero address are considered invalid, and this419 /// function throws for queries about the zero address.420 /// @param owner An address for whom to query the balance421 /// @return The number of NFTs owned by `owner`, possibly zero422 fn balance_of(&self, owner: Address) -> Result<U256> {423 self.consume_store_reads(1)?;424 let owner = T::CrossAccountId::from_eth(owner);425 let balance = <AccountBalance<T>>::get((self.id, owner));426 Ok(balance.into())427 }428 /// @notice Find the owner of an NFT429 /// @dev NFTs assigned to zero address are considered invalid, and queries430 /// about them do throw.431 /// @param tokenId The identifier for an NFT432 /// @return The address of the owner of the NFT433 fn owner_of(&self, token_id: U256) -> Result<Address> {434 self.consume_store_reads(1)?;435 let token: TokenId = token_id.try_into()?;436 Ok(*<TokenData<T>>::get((self.id, token))437 .ok_or("token not found")?438 .owner439 .as_eth())440 }441 /// @dev Not implemented442 #[solidity(rename_selector = "safeTransferFrom")]443 fn safe_transfer_from_with_data(444 &mut self,445 _from: Address,446 _to: Address,447 _token_id: U256,448 _data: Bytes,449 ) -> Result<()> {450 // TODO: Not implemetable451 Err("not implemented".into())452 }453 /// @dev Not implemented454 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458459 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE460 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE461 /// THEY MAY BE PERMANENTLY LOST462 /// @dev Throws unless `msg.sender` is the current owner or an authorized463 /// operator for this NFT. Throws if `from` is not the current owner. Throws464 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.465 /// @param from The current owner of the NFT466 /// @param to The new owner467 /// @param tokenId The NFT to transfer468 #[weight(<CommonWeights<T>>::transfer_from())]469 fn transfer_from(470 &mut self,471 caller: Caller,472 from: Address,473 to: Address,474 token_id: U256,475 ) -> Result<()> {476 let caller = T::CrossAccountId::from_eth(caller);477 let from = T::CrossAccountId::from_eth(from);478 let to = T::CrossAccountId::from_eth(to);479 let token = token_id.try_into()?;480481 <Pallet<T>>::transfer_from(482 self,483 &caller,484 &from,485 &to,486 token,487 &nesting_budget(&self.recorder),488 )489 .map_err(|e| dispatch_to_evm::<T>(e.error))?;490 Ok(())491 }492493 /// @notice Set or reaffirm the approved address for an NFT494 /// @dev The zero address indicates there is no approved address.495 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized496 /// operator of the current owner.497 /// @param approved The new approved NFT controller498 /// @param tokenId The NFT to approve499 #[weight(<SelfWeightOf<T>>::approve())]500 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {501 let caller = T::CrossAccountId::from_eth(caller);502 let approved = T::CrossAccountId::from_eth(approved);503 let token = token_id.try_into()?;504505 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))506 .map_err(dispatch_to_evm::<T>)?;507 Ok(())508 }509510 /// @notice Sets or unsets the approval of a given operator.511 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.512 /// @param operator Operator513 /// @param approved Should operator status be granted or revoked?514 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]515 fn set_approval_for_all(516 &mut self,517 caller: Caller,518 operator: Address,519 approved: bool,520 ) -> Result<()> {521 let caller = T::CrossAccountId::from_eth(caller);522 let operator = T::CrossAccountId::from_eth(operator);523524 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)525 .map_err(dispatch_to_evm::<T>)?;526 Ok(())527 }528529 /// @notice Get the approved address for a single NFT530 /// @dev Throws if `tokenId` is not a valid NFT531 /// @param tokenId The NFT to find the approved address for532 /// @return The approved address for this NFT, or the zero address if there is none533 fn get_approved(&self, token_id: U256) -> Result<Address> {534 let token_id = token_id.try_into()?;535 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;536 Ok(if let Some(operator) = operator {537 *operator.as_eth()538 } else {539 Address::zero()540 })541 }542543 /// @notice Tells whether the given `owner` approves the `operator`.544 #[weight(<SelfWeightOf<T>>::allowance_for_all())]545 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {546 let owner = T::CrossAccountId::from_eth(owner);547 let operator = T::CrossAccountId::from_eth(operator);548549 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))550 }551}552553/// @title ERC721 Token that can be irreversibly burned (destroyed).554#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556 /// @notice Burns a specific ERC721 token.557 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized558 /// operator of the current owner.559 /// @param tokenId The NFT to approve560 #[weight(<SelfWeightOf<T>>::burn_item())]561 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {562 let caller = T::CrossAccountId::from_eth(caller);563 let token = token_id.try_into()?;564565 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;566 Ok(())567 }568}569570/// @title ERC721 minting logic.571#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]572impl<T: Config> NonfungibleHandle<T> {573 /// @notice Function to mint a token.574 /// @param to The new owner575 /// @return uint256 The id of the newly minted token576 #[weight(<SelfWeightOf<T>>::create_item())]577 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {578 let token_id: U256 = <TokensMinted<T>>::get(self.id)579 .checked_add(1)580 .ok_or("item id overflow")?581 .into();582 self.mint_check_id(caller, to, token_id)?;583 Ok(token_id)584 }585586 /// @notice Function to mint a token.587 /// @dev `tokenId` should be obtained with `nextTokenId` method,588 /// unlike standard, you can't specify it manually589 /// @param to The new owner590 /// @param tokenId ID of the minted NFT591 #[solidity(hide, rename_selector = "mint")]592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {594 let caller = T::CrossAccountId::from_eth(caller);595 let to = T::CrossAccountId::from_eth(to);596 let token_id: u32 = token_id.try_into()?;597598 if <TokensMinted<T>>::get(self.id)599 .checked_add(1)600 .ok_or("item id overflow")?601 != token_id602 {603 return Err("item id should be next".into());604 }605606 <Pallet<T>>::create_item(607 self,608 &caller,609 CreateItemData::<T> {610 properties: BoundedVec::default(),611 owner: to,612 },613 &nesting_budget(&self.recorder),614 )615 .map_err(dispatch_to_evm::<T>)?;616617 Ok(true)618 }619620 /// @notice Function to mint token with the given tokenUri.621 /// @param to The new owner622 /// @param tokenUri Token URI that would be stored in the NFT properties623 /// @return uint256 The id of the newly minted token624 #[solidity(rename_selector = "mintWithTokenURI")]625 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]626 fn mint_with_token_uri(627 &mut self,628 caller: Caller,629 to: Address,630 token_uri: String,631 ) -> Result<U256> {632 let token_id: U256 = <TokensMinted<T>>::get(self.id)633 .checked_add(1)634 .ok_or("item id overflow")?635 .into();636 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;637 Ok(token_id)638 }639640 /// @notice Function to mint token with the given tokenUri.641 /// @dev `tokenId` should be obtained with `nextTokenId` method,642 /// unlike standard, you can't specify it manually643 /// @param to The new owner644 /// @param tokenId ID of the minted NFT645 /// @param tokenUri Token URI that would be stored in the NFT properties646 #[solidity(hide, rename_selector = "mintWithTokenURI")]647 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]648 fn mint_with_token_uri_check_id(649 &mut self,650 caller: Caller,651 to: Address,652 token_id: U256,653 token_uri: String,654 ) -> Result<bool> {655 let key = key::url();656 let permission = get_token_permission::<T>(self.id, &key)?;657 if !permission.collection_admin {658 return Err("operation is not allowed".into());659 }660661 let caller = T::CrossAccountId::from_eth(caller);662 let to = T::CrossAccountId::from_eth(to);663 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;664665 if <TokensMinted<T>>::get(self.id)666 .checked_add(1)667 .ok_or("item id overflow")?668 != token_id669 {670 return Err("item id should be next".into());671 }672673 let mut properties = CollectionPropertiesVec::default();674 properties675 .try_push(Property {676 key,677 value: token_uri678 .into_bytes()679 .try_into()680 .map_err(|_| "token uri is too long")?,681 })682 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;683684 <Pallet<T>>::create_item(685 self,686 &caller,687 CreateItemData::<T> {688 properties,689 owner: to,690 },691 &nesting_budget(&self.recorder),692 )693 .map_err(dispatch_to_evm::<T>)?;694 Ok(true)695 }696}697698fn get_token_property<T: Config>(699 collection: &CollectionHandle<T>,700 token_id: u32,701 key: &up_data_structs::PropertyKey,702) -> Result<String> {703 collection.consume_store_reads(1)?;704 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))705 .map_err(|_| Error::Revert("token properties not found".into()))?;706 if let Some(property) = properties.get(key) {707 return Ok(String::from_utf8_lossy(property).into());708 }709710 Err("property tokenURI not found".into())711}712713fn get_token_permission<T: Config>(714 collection_id: CollectionId,715 key: &PropertyKey,716) -> Result<PropertyPermission> {717 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)718 .map_err(|_| Error::Revert("no permissions for collection".into()))?;719 let a = token_property_permissions720 .get(key)721 .cloned()722 .ok_or_else(|| {723 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();724 Error::Revert(alloc::format!("no permission for key {key}"))725 })?;726 Ok(a)727}728729/// @title Unique extensions for ERC721.730#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]731impl<T: Config> NonfungibleHandle<T>732where733 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,734{735 /// @notice A descriptive name for a collection of NFTs in this contract736 fn name(&self) -> String {737 decode_utf16(self.name.iter().copied())738 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))739 .collect::<String>()740 }741742 /// @notice An abbreviated name for NFTs in this contract743 fn symbol(&self) -> String {744 String::from_utf8_lossy(&self.token_prefix).into()745 }746747 /// @notice A description for the collection.748 fn description(&self) -> String {749 decode_utf16(self.description.iter().copied())750 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))751 .collect::<String>()752 }753754 /// Returns the owner (in cross format) of the token.755 ///756 /// @param tokenId Id for the token.757 #[solidity(hide)]758 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {759 Self::owner_of_cross(self, token_id)760 }761762 /// Returns the owner (in cross format) of the token.763 ///764 /// @param tokenId Id for the token.765 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {766 Self::token_owner(self, token_id.try_into()?)767 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))768 .map_err(|_| Error::Revert("token not found".into()))769 }770771 /// @notice Count all NFTs assigned to an owner772 /// @param owner An cross address for whom to query the balance773 /// @return The number of NFTs owned by `owner`, possibly zero774 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {775 self.consume_store_reads(1)?;776 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));777 Ok(balance.into())778 }779780 /// Returns the token properties.781 ///782 /// @param tokenId Id for the token.783 /// @param keys Properties keys. Empty keys for all propertyes.784 /// @return Vector of properties key/value pairs.785 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {786 let keys = keys787 .into_iter()788 .map(|key| {789 <Vec<u8>>::from(key)790 .try_into()791 .map_err(|_| Error::Revert("key too large".into()))792 })793 .collect::<Result<Vec<_>>>()?;794795 <Self as CommonCollectionOperations<T>>::token_properties(796 self,797 token_id.try_into()?,798 if keys.is_empty() { None } else { Some(keys) },799 )800 .into_iter()801 .map(eth::Property::try_from)802 .collect::<Result<Vec<_>>>()803 }804805 /// @notice Set or reaffirm the approved address for an NFT806 /// @dev The zero address indicates there is no approved address.807 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized808 /// operator of the current owner.809 /// @param approved The new substrate address approved NFT controller810 /// @param tokenId The NFT to approve811 #[weight(<SelfWeightOf<T>>::approve())]812 fn approve_cross(813 &mut self,814 caller: Caller,815 approved: eth::CrossAddress,816 token_id: U256,817 ) -> Result<()> {818 let caller = T::CrossAccountId::from_eth(caller);819 let approved = approved.into_sub_cross_account::<T>()?;820 let token = token_id.try_into()?;821822 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))823 .map_err(dispatch_to_evm::<T>)?;824 Ok(())825 }826827 /// @notice Transfer ownership of an NFT828 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`829 /// is the zero address. Throws if `tokenId` is not a valid NFT.830 /// @param to The new owner831 /// @param tokenId The NFT to transfer832 #[weight(<CommonWeights<T>>::transfer())]833 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {834 let caller = T::CrossAccountId::from_eth(caller);835 let to = T::CrossAccountId::from_eth(to);836 let token = token_id.try_into()?;837838 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))839 .map_err(|e| dispatch_to_evm::<T>(e.error))?;840 Ok(())841 }842843 /// @notice Transfer ownership of an NFT844 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`845 /// is the zero address. Throws if `tokenId` is not a valid NFT.846 /// @param to The new owner847 /// @param tokenId The NFT to transfer848 #[weight(<CommonWeights<T>>::transfer())]849 fn transfer_cross(850 &mut self,851 caller: Caller,852 to: eth::CrossAddress,853 token_id: U256,854 ) -> Result<()> {855 let caller = T::CrossAccountId::from_eth(caller);856 let to = to.into_sub_cross_account::<T>()?;857 let token = token_id.try_into()?;858859 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))860 .map_err(|e| dispatch_to_evm::<T>(e.error))?;861 Ok(())862 }863864 /// @notice Transfer ownership of an NFT from cross account address to cross account address865 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`866 /// is the zero address. Throws if `tokenId` is not a valid NFT.867 /// @param from Cross acccount address of current owner868 /// @param to Cross acccount address of new owner869 /// @param tokenId The NFT to transfer870 #[weight(<CommonWeights<T>>::transfer_from())]871 fn transfer_from_cross(872 &mut self,873 caller: Caller,874 from: eth::CrossAddress,875 to: eth::CrossAddress,876 token_id: U256,877 ) -> Result<()> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let to = to.into_sub_cross_account::<T>()?;881 let token_id = token_id.try_into()?;882883 Pallet::<T>::transfer_from(884 self,885 &caller,886 &from,887 &to,888 token_id,889 &nesting_budget(&self.recorder),890 )891 .map_err(|e| dispatch_to_evm::<T>(e.error))?;892 Ok(())893 }894895 /// @notice Burns a specific ERC721 token.896 /// @dev Throws unless `msg.sender` is the current owner or an authorized897 /// operator for this NFT. Throws if `from` is not the current owner. Throws898 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.899 /// @param from The current owner of the NFT900 /// @param tokenId The NFT to transfer901 #[solidity(hide)]902 #[weight(<SelfWeightOf<T>>::burn_from())]903 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {904 let caller = T::CrossAccountId::from_eth(caller);905 let from = T::CrossAccountId::from_eth(from);906 let token = token_id.try_into()?;907908 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))909 .map_err(dispatch_to_evm::<T>)?;910 Ok(())911 }912913 /// @notice Burns a specific ERC721 token.914 /// @dev Throws unless `msg.sender` is the current owner or an authorized915 /// operator for this NFT. Throws if `from` is not the current owner. Throws916 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.917 /// @param from The current owner of the NFT918 /// @param tokenId The NFT to transfer919 #[weight(<SelfWeightOf<T>>::burn_from())]920 fn burn_from_cross(921 &mut self,922 caller: Caller,923 from: eth::CrossAddress,924 token_id: U256,925 ) -> Result<()> {926 let caller = T::CrossAccountId::from_eth(caller);927 let from = from.into_sub_cross_account::<T>()?;928 let token = token_id.try_into()?;929930 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))931 .map_err(dispatch_to_evm::<T>)?;932 Ok(())933 }934935 /// @notice Returns next free NFT ID.936 fn next_token_id(&self) -> Result<U256> {937 self.consume_store_reads(1)?;938 Ok(<Pallet<T>>::next_token_id(self)939 .map_err(dispatch_to_evm::<T>)?940 .into())941 }942943 /// @notice Function to mint multiple tokens.944 /// @dev `tokenIds` should be an array of consecutive numbers and first number945 /// should be obtained with `nextTokenId` method946 /// @param to The new owner947 /// @param tokenIds IDs of the minted NFTs948 #[solidity(hide)]949 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]950 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {951 let caller = T::CrossAccountId::from_eth(caller);952 let to = T::CrossAccountId::from_eth(to);953 let mut expected_index = <TokensMinted<T>>::get(self.id)954 .checked_add(1)955 .ok_or("item id overflow")?;956957 let total_tokens = token_ids.len();958 for id in token_ids.into_iter() {959 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;960 if id != expected_index {961 return Err("item id should be next".into());962 }963 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;964 }965 let data = (0..total_tokens)966 .map(|_| CreateItemData::<T> {967 properties: BoundedVec::default(),968 owner: to.clone(),969 })970 .collect();971972 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))973 .map_err(dispatch_to_evm::<T>)?;974 Ok(true)975 }976977 /// @notice Function to mint a token.978 /// @param data Array of pairs of token owner and token's properties for minted token979 #[weight(980 mint_with_props_weight::<T>(981 <SelfWeightOf<T>>::create_multiple_items_ex(data.len() as u32),982 data.iter().map(|d| d.properties.len() as u32),983 )984 )]985 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {986 let caller = T::CrossAccountId::from_eth(caller);987988 let mut create_nft_data = Vec::with_capacity(data.len());989 for MintTokenData { owner, properties } in data {990 let owner = owner.into_sub_cross_account::<T>()?;991 create_nft_data.push(CreateItemData::<T> {992 properties: properties993 .into_iter()994 .map(|property| property.try_into())995 .collect::<Result<Vec<_>>>()?996 .try_into()997 .map_err(|_| "too many properties")?,998 owner,999 });1000 }10011002 <Pallet<T>>::create_multiple_items(1003 self,1004 &caller,1005 create_nft_data,1006 &nesting_budget(&self.recorder),1007 )1008 .map_err(dispatch_to_evm::<T>)?;1009 Ok(true)1010 }10111012 /// @notice Function to mint multiple tokens with the given tokenUris.1013 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1014 /// numbers and first number should be obtained with `nextTokenId` method1015 /// @param to The new owner1016 /// @param tokens array of pairs of token ID and token URI for minted tokens1017 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1018 #[weight(1019 mint_with_props_weight::<T>(1020 <SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32),1021 tokens.iter().map(|_| 1),1022 )1023 )]1024 fn mint_bulk_with_token_uri(1025 &mut self,1026 caller: Caller,1027 to: Address,1028 tokens: Vec<TokenUri>,1029 ) -> Result<bool> {1030 let key = key::url();1031 let caller = T::CrossAccountId::from_eth(caller);1032 let to = T::CrossAccountId::from_eth(to);1033 let mut expected_index = <TokensMinted<T>>::get(self.id)1034 .checked_add(1)1035 .ok_or("item id overflow")?;10361037 let mut data = Vec::with_capacity(tokens.len());1038 for TokenUri { id, uri } in tokens {1039 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1040 if id != expected_index {1041 return Err("item id should be next".into());1042 }1043 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10441045 let mut properties = CollectionPropertiesVec::default();1046 properties1047 .try_push(Property {1048 key: key.clone(),1049 value: uri1050 .into_bytes()1051 .try_into()1052 .map_err(|_| "token uri is too long")?,1053 })1054 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10551056 data.push(CreateItemData::<T> {1057 properties,1058 owner: to.clone(),1059 });1060 }10611062 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1063 .map_err(dispatch_to_evm::<T>)?;1064 Ok(true)1065 }10661067 /// @notice Function to mint a token.1068 /// @param to The new owner crossAccountId1069 /// @param properties Properties of minted token1070 /// @return uint256 The id of the newly minted token1071 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [properties.len() as u32].into_iter()))]1072 fn mint_cross(1073 &mut self,1074 caller: Caller,1075 to: eth::CrossAddress,1076 properties: Vec<eth::Property>,1077 ) -> Result<U256> {1078 let token_id = <TokensMinted<T>>::get(self.id)1079 .checked_add(1)1080 .ok_or("item id overflow")?;10811082 let to = to.into_sub_cross_account::<T>()?;10831084 let properties = properties1085 .into_iter()1086 .map(eth::Property::try_into)1087 .collect::<Result<Vec<_>>>()?1088 .try_into()1089 .map_err(|_| Error::Revert("too many properties".to_string()))?;10901091 let caller = T::CrossAccountId::from_eth(caller);10921093 <Pallet<T>>::create_item(1094 self,1095 &caller,1096 CreateItemData::<T> {1097 properties,1098 owner: to,1099 },1100 &nesting_budget(&self.recorder),1101 )1102 .map_err(dispatch_to_evm::<T>)?;11031104 Ok(token_id.into())1105 }11061107 /// @notice Returns collection helper contract address1108 fn collection_helper_address(&self) -> Address {1109 T::ContractAddress::get()1110 }1111}11121113#[solidity_interface(1114 name = UniqueNFT,1115 is(1116 ERC721,1117 ERC721Enumerable,1118 ERC721UniqueExtensions,1119 ERC721UniqueMintable,1120 ERC721Burnable,1121 ERC721Metadata(if(this.flags.erc721metadata)),1122 Collection(via(common_mut returns CollectionHandle<T>)),1123 TokenProperties,1124 ),1125 enum(derive(PreDispatch)),1126)]1127impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11281129// Not a tests, but code generators1130generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1131generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11321133impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1134where1135 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1136{1137 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11381139 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1140 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1141 }1142}pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -761,7 +761,7 @@
.map_err(|_| Error::Revert("no permissions for collection".into()))?;
let a = token_property_permissions
.get(key)
- .map(Clone::clone)
+ .cloned()
.ok_or_else(|| {
let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();
Error::Revert(alloc::format!("no permission for key {key}"))