difftreelog
feature: Added `delete_properties` in eth functions. The `delete_property` function is now deprecated.
in: master
15 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6376,7 +6376,7 @@
[[package]]
name = "pallet-nonfungible"
-version = "0.1.7"
+version = "0.1.8"
dependencies = [
"ethereum",
"evm-coder",
@@ -6498,7 +6498,7 @@
[[package]]
name = "pallet-refungible"
-version = "0.2.6"
+version = "0.2.7"
dependencies = [
"derivative",
"ethereum",
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -3,10 +3,19 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+
+## [v0.1.8] - 2022-11-11
+
+### Changed
+
+- Added `delete_properties` in eth functions. The `delete_property` function is now deprecated.
+
## [v0.1.7] - 2022-11-02
+
### Changed
- - Use named structure `EthCrossAccount` in eth functions.
+- Use named structure `EthCrossAccount` in eth functions.
+
## [v0.1.6] - 2022-20-10
### Change
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-nonfungible"
-version = "0.1.7"
+version = "0.1.8"
license = "GPLv3"
edition = "2021"
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 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 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},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};5051/// @title A contract that allows to set and delete token properties and change token property permissions.52#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54 /// @notice Set permissions for token property.55 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.56 /// @param key Property key.57 /// @param isMutable Permission to mutate property.58 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.59 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.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 /// @notice Set token property value.87 /// @dev Throws error if `msg.sender` has no permission to edit the property.88 /// @param tokenId ID of the token.89 /// @param key Property key.90 /// @param value Property value.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.0.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 /// @notice Set token properties value.120 /// @dev Throws error if `msg.sender` has no permission to edit the property.121 /// @param tokenId ID of the token.122 /// @param properties settable properties123 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124 fn set_properties(125 &mut self,126 caller: caller,127 token_id: uint256,128 properties: Vec<(string, bytes)>,129 ) -> Result<()> {130 let caller = T::CrossAccountId::from_eth(caller);131 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133 let nesting_budget = self134 .recorder135 .weight_calls_budget(<StructureWeight<T>>::find_parent());136137 let properties = properties138 .into_iter()139 .map(|(key, value)| {140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too large")?;143144 let value = value.0.try_into().map_err(|_| "value too large")?;145146 Ok(Property { key, value })147 })148 .collect::<Result<Vec<_>>>()?;149150 <Pallet<T>>::set_token_properties(151 self,152 &caller,153 TokenId(token_id),154 properties.into_iter(),155 <Pallet<T>>::token_exists(&self, TokenId(token_id)),156 &nesting_budget,157 )158 .map_err(dispatch_to_evm::<T>)159 }160161 /// @notice Delete token property value.162 /// @dev Throws error if `msg.sender` has no permission to edit the property.163 /// @param tokenId ID of the token.164 /// @param key Property key.165 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {166 let caller = T::CrossAccountId::from_eth(caller);167 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;168 let key = <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| "key too long")?;171172 let nesting_budget = self173 .recorder174 .weight_calls_budget(<StructureWeight<T>>::find_parent());175176 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)177 .map_err(dispatch_to_evm::<T>)178 }179180 /// @notice Get token property value.181 /// @dev Throws error if key not found182 /// @param tokenId ID of the token.183 /// @param key Property key.184 /// @return Property value bytes185 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {186 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;187 let key = <Vec<u8>>::from(key)188 .try_into()189 .map_err(|_| "key too long")?;190191 let props = <TokenProperties<T>>::get((self.id, token_id));192 let prop = props.get(&key).ok_or("key not found")?;193194 Ok(prop.to_vec().into())195 }196}197198#[derive(ToLog)]199pub enum ERC721Events {200 /// @dev This emits when ownership of any NFT changes by any mechanism.201 /// This event emits when NFTs are created (`from` == 0) and destroyed202 /// (`to` == 0). Exception: during contract creation, any number of NFTs203 /// may be created and assigned without emitting Transfer. At the time of204 /// any transfer, the approved address for that NFT (if any) is reset to none.205 Transfer {206 #[indexed]207 from: address,208 #[indexed]209 to: address,210 #[indexed]211 token_id: uint256,212 },213 /// @dev This emits when the approved address for an NFT is changed or214 /// reaffirmed. The zero address indicates there is no approved address.215 /// When a Transfer event emits, this also indicates that the approved216 /// address for that NFT (if any) is reset to none.217 Approval {218 #[indexed]219 owner: address,220 #[indexed]221 approved: address,222 #[indexed]223 token_id: uint256,224 },225 /// @dev This emits when an operator is enabled or disabled for an owner.226 /// The operator can manage all NFTs of the owner.227 #[allow(dead_code)]228 ApprovalForAll {229 #[indexed]230 owner: address,231 #[indexed]232 operator: address,233 approved: bool,234 },235}236237#[derive(ToLog)]238pub enum ERC721UniqueMintableEvents {239 #[allow(dead_code)]240 MintingFinished {},241}242243/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension244/// @dev See https://eips.ethereum.org/EIPS/eip-721245#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]246impl<T: Config> NonfungibleHandle<T>247where248 T::AccountId: From<[u8; 32]>,249{250 /// @notice A descriptive name for a collection of NFTs in this contract251 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`252 #[solidity(hide, rename_selector = "name")]253 fn name_proxy(&self) -> Result<string> {254 self.name()255 }256257 /// @notice An abbreviated name for NFTs in this contract258 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`259 #[solidity(hide, rename_selector = "symbol")]260 fn symbol_proxy(&self) -> Result<string> {261 self.symbol()262 }263264 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.265 ///266 /// @dev If the token has a `url` property and it is not empty, it is returned.267 /// 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`.268 /// If the collection property `baseURI` is empty or absent, return "" (empty string)269 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix270 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).271 ///272 /// @return token's const_metadata273 #[solidity(rename_selector = "tokenURI")]274 fn token_uri(&self, token_id: uint256) -> Result<string> {275 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;276277 match get_token_property(self, token_id_u32, &key::url()).as_deref() {278 Err(_) | Ok("") => (),279 Ok(url) => {280 return Ok(url.into());281 }282 };283284 let base_uri =285 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())286 .map(BoundedVec::into_inner)287 .map(string::from_utf8)288 .transpose()289 .map_err(|e| {290 Error::Revert(alloc::format!(291 "Can not convert value \"baseURI\" to string with error \"{}\"",292 e293 ))294 })?;295296 let base_uri = match base_uri.as_deref() {297 None | Some("") => {298 return Ok("".into());299 }300 Some(base_uri) => base_uri.into(),301 };302303 Ok(304 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {305 Err(_) | Ok("") => base_uri,306 Ok(suffix) => base_uri + suffix,307 },308 )309 }310}311312/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension313/// @dev See https://eips.ethereum.org/EIPS/eip-721314#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]315impl<T: Config> NonfungibleHandle<T> {316 /// @notice Enumerate valid NFTs317 /// @param index A counter less than `totalSupply()`318 /// @return The token identifier for the `index`th NFT,319 /// (sort order not specified)320 fn token_by_index(&self, index: uint256) -> Result<uint256> {321 Ok(index)322 }323324 /// @dev Not implemented325 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {326 // TODO: Not implemetable327 Err("not implemented".into())328 }329330 /// @notice Count NFTs tracked by this contract331 /// @return A count of valid NFTs tracked by this contract, where each one of332 /// them has an assigned and queryable owner not equal to the zero address333 fn total_supply(&self) -> Result<uint256> {334 self.consume_store_reads(1)?;335 Ok(<Pallet<T>>::total_supply(self).into())336 }337}338339/// @title ERC-721 Non-Fungible Token Standard340/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md341#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]342impl<T: Config> NonfungibleHandle<T> {343 /// @notice Count all NFTs assigned to an owner344 /// @dev NFTs assigned to the zero address are considered invalid, and this345 /// function throws for queries about the zero address.346 /// @param owner An address for whom to query the balance347 /// @return The number of NFTs owned by `owner`, possibly zero348 fn balance_of(&self, owner: address) -> Result<uint256> {349 self.consume_store_reads(1)?;350 let owner = T::CrossAccountId::from_eth(owner);351 let balance = <AccountBalance<T>>::get((self.id, owner));352 Ok(balance.into())353 }354 /// @notice Find the owner of an NFT355 /// @dev NFTs assigned to zero address are considered invalid, and queries356 /// about them do throw.357 /// @param tokenId The identifier for an NFT358 /// @return The address of the owner of the NFT359 fn owner_of(&self, token_id: uint256) -> Result<address> {360 self.consume_store_reads(1)?;361 let token: TokenId = token_id.try_into()?;362 Ok(*<TokenData<T>>::get((self.id, token))363 .ok_or("token not found")?364 .owner365 .as_eth())366 }367 /// @dev Not implemented368 #[solidity(rename_selector = "safeTransferFrom")]369 fn safe_transfer_from_with_data(370 &mut self,371 _from: address,372 _to: address,373 _token_id: uint256,374 _data: bytes,375 ) -> Result<void> {376 // TODO: Not implemetable377 Err("not implemented".into())378 }379 /// @dev Not implemented380 fn safe_transfer_from(381 &mut self,382 _from: address,383 _to: address,384 _token_id: uint256,385 ) -> Result<void> {386 // TODO: Not implemetable387 Err("not implemented".into())388 }389390 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE391 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE392 /// THEY MAY BE PERMANENTLY LOST393 /// @dev Throws unless `msg.sender` is the current owner or an authorized394 /// operator for this NFT. Throws if `from` is not the current owner. Throws395 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.396 /// @param from The current owner of the NFT397 /// @param to The new owner398 /// @param tokenId The NFT to transfer399 #[weight(<SelfWeightOf<T>>::transfer_from())]400 fn transfer_from(401 &mut self,402 caller: caller,403 from: address,404 to: address,405 token_id: uint256,406 ) -> Result<void> {407 let caller = T::CrossAccountId::from_eth(caller);408 let from = T::CrossAccountId::from_eth(from);409 let to = T::CrossAccountId::from_eth(to);410 let token = token_id.try_into()?;411 let budget = self412 .recorder413 .weight_calls_budget(<StructureWeight<T>>::find_parent());414415 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)416 .map_err(dispatch_to_evm::<T>)?;417 Ok(())418 }419420 /// @notice Set or reaffirm the approved address for an NFT421 /// @dev The zero address indicates there is no approved address.422 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized423 /// operator of the current owner.424 /// @param approved The new approved NFT controller425 /// @param tokenId The NFT to approve426 #[weight(<SelfWeightOf<T>>::approve())]427 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {428 let caller = T::CrossAccountId::from_eth(caller);429 let approved = T::CrossAccountId::from_eth(approved);430 let token = token_id.try_into()?;431432 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))433 .map_err(dispatch_to_evm::<T>)?;434 Ok(())435 }436437 /// @dev Not implemented438 fn set_approval_for_all(439 &mut self,440 _caller: caller,441 _operator: address,442 _approved: bool,443 ) -> Result<void> {444 // TODO: Not implemetable445 Err("not implemented".into())446 }447448 /// @dev Not implemented449 fn get_approved(&self, _token_id: uint256) -> Result<address> {450 // TODO: Not implemetable451 Err("not implemented".into())452 }453454 /// @dev Not implemented455 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {456 // TODO: Not implemetable457 Err("not implemented".into())458 }459}460461/// @title ERC721 Token that can be irreversibly burned (destroyed).462#[solidity_interface(name = ERC721Burnable)]463impl<T: Config> NonfungibleHandle<T> {464 /// @notice Burns a specific ERC721 token.465 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized466 /// operator of the current owner.467 /// @param tokenId The NFT to approve468 #[weight(<SelfWeightOf<T>>::burn_item())]469 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {470 let caller = T::CrossAccountId::from_eth(caller);471 let token = token_id.try_into()?;472473 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;474 Ok(())475 }476}477478/// @title ERC721 minting logic.479#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]480impl<T: Config> NonfungibleHandle<T> {481 fn minting_finished(&self) -> Result<bool> {482 Ok(false)483 }484485 /// @notice Function to mint token.486 /// @param to The new owner487 /// @return uint256 The id of the newly minted token488 #[weight(<SelfWeightOf<T>>::create_item())]489 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {490 let token_id: uint256 = <TokensMinted<T>>::get(self.id)491 .checked_add(1)492 .ok_or("item id overflow")?493 .into();494 self.mint_check_id(caller, to, token_id)?;495 Ok(token_id)496 }497498 /// @notice Function to mint token.499 /// @dev `tokenId` should be obtained with `nextTokenId` method,500 /// unlike standard, you can't specify it manually501 /// @param to The new owner502 /// @param tokenId ID of the minted NFT503 #[solidity(hide, rename_selector = "mint")]504 #[weight(<SelfWeightOf<T>>::create_item())]505 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {506 let caller = T::CrossAccountId::from_eth(caller);507 let to = T::CrossAccountId::from_eth(to);508 let token_id: u32 = token_id.try_into()?;509 let budget = self510 .recorder511 .weight_calls_budget(<StructureWeight<T>>::find_parent());512513 if <TokensMinted<T>>::get(self.id)514 .checked_add(1)515 .ok_or("item id overflow")?516 != token_id517 {518 return Err("item id should be next".into());519 }520521 <Pallet<T>>::create_item(522 self,523 &caller,524 CreateItemData::<T> {525 properties: BoundedVec::default(),526 owner: to,527 },528 &budget,529 )530 .map_err(dispatch_to_evm::<T>)?;531532 Ok(true)533 }534535 /// @notice Function to mint token with the given tokenUri.536 /// @param to The new owner537 /// @param tokenUri Token URI that would be stored in the NFT properties538 /// @return uint256 The id of the newly minted token539 #[solidity(rename_selector = "mintWithTokenURI")]540 #[weight(<SelfWeightOf<T>>::create_item())]541 fn mint_with_token_uri(542 &mut self,543 caller: caller,544 to: address,545 token_uri: string,546 ) -> Result<uint256> {547 let token_id: uint256 = <TokensMinted<T>>::get(self.id)548 .checked_add(1)549 .ok_or("item id overflow")?550 .into();551 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;552 Ok(token_id)553 }554555 /// @notice Function to mint token with the given tokenUri.556 /// @dev `tokenId` should be obtained with `nextTokenId` method,557 /// unlike standard, you can't specify it manually558 /// @param to The new owner559 /// @param tokenId ID of the minted NFT560 /// @param tokenUri Token URI that would be stored in the NFT properties561 #[solidity(hide, rename_selector = "mintWithTokenURI")]562 #[weight(<SelfWeightOf<T>>::create_item())]563 fn mint_with_token_uri_check_id(564 &mut self,565 caller: caller,566 to: address,567 token_id: uint256,568 token_uri: string,569 ) -> Result<bool> {570 let key = key::url();571 let permission = get_token_permission::<T>(self.id, &key)?;572 if !permission.collection_admin {573 return Err("Operation is not allowed".into());574 }575576 let caller = T::CrossAccountId::from_eth(caller);577 let to = T::CrossAccountId::from_eth(to);578 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;579 let budget = self580 .recorder581 .weight_calls_budget(<StructureWeight<T>>::find_parent());582583 if <TokensMinted<T>>::get(self.id)584 .checked_add(1)585 .ok_or("item id overflow")?586 != token_id587 {588 return Err("item id should be next".into());589 }590591 let mut properties = CollectionPropertiesVec::default();592 properties593 .try_push(Property {594 key,595 value: token_uri596 .into_bytes()597 .try_into()598 .map_err(|_| "token uri is too long")?,599 })600 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;601602 <Pallet<T>>::create_item(603 self,604 &caller,605 CreateItemData::<T> {606 properties,607 owner: to,608 },609 &budget,610 )611 .map_err(dispatch_to_evm::<T>)?;612 Ok(true)613 }614615 /// @dev Not implemented616 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {617 Err("not implementable".into())618 }619}620621fn get_token_property<T: Config>(622 collection: &CollectionHandle<T>,623 token_id: u32,624 key: &up_data_structs::PropertyKey,625) -> Result<string> {626 collection.consume_store_reads(1)?;627 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))628 .map_err(|_| Error::Revert("Token properties not found".into()))?;629 if let Some(property) = properties.get(key) {630 return Ok(string::from_utf8_lossy(property).into());631 }632633 Err("Property tokenURI not found".into())634}635636fn get_token_permission<T: Config>(637 collection_id: CollectionId,638 key: &PropertyKey,639) -> Result<PropertyPermission> {640 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)641 .map_err(|_| Error::Revert("No permissions for collection".into()))?;642 let a = token_property_permissions643 .get(key)644 .map(Clone::clone)645 .ok_or_else(|| {646 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();647 Error::Revert(alloc::format!("No permission for key {}", key))648 })?;649 Ok(a)650}651652/// @title Unique extensions for ERC721.653#[solidity_interface(name = ERC721UniqueExtensions)]654impl<T: Config> NonfungibleHandle<T>655where656 T::AccountId: From<[u8; 32]>,657{658 /// @notice A descriptive name for a collection of NFTs in this contract659 fn name(&self) -> Result<string> {660 Ok(decode_utf16(self.name.iter().copied())661 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))662 .collect::<string>())663 }664665 /// @notice An abbreviated name for NFTs in this contract666 fn symbol(&self) -> Result<string> {667 Ok(string::from_utf8_lossy(&self.token_prefix).into())668 }669670 /// @notice Set or reaffirm the approved address for an NFT671 /// @dev The zero address indicates there is no approved address.672 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized673 /// operator of the current owner.674 /// @param approved The new substrate address approved NFT controller675 /// @param tokenId The NFT to approve676 #[weight(<SelfWeightOf<T>>::approve())]677 fn approve_cross(678 &mut self,679 caller: caller,680 approved: EthCrossAccount,681 token_id: uint256,682 ) -> Result<void> {683 let caller = T::CrossAccountId::from_eth(caller);684 let approved = approved.into_sub_cross_account::<T>()?;685 let token = token_id.try_into()?;686687 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))688 .map_err(dispatch_to_evm::<T>)?;689 Ok(())690 }691692 /// @notice Transfer ownership of an NFT693 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`694 /// is the zero address. Throws if `tokenId` is not a valid NFT.695 /// @param to The new owner696 /// @param tokenId The NFT to transfer697 #[weight(<SelfWeightOf<T>>::transfer())]698 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {699 let caller = T::CrossAccountId::from_eth(caller);700 let to = T::CrossAccountId::from_eth(to);701 let token = token_id.try_into()?;702 let budget = self703 .recorder704 .weight_calls_budget(<StructureWeight<T>>::find_parent());705706 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;707 Ok(())708 }709710 /// @notice Transfer ownership of an NFT from cross account address to cross account address711 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`712 /// is the zero address. Throws if `tokenId` is not a valid NFT.713 /// @param from Cross acccount address of current owner714 /// @param to Cross acccount address of new owner715 /// @param tokenId The NFT to transfer716 #[weight(<SelfWeightOf<T>>::transfer())]717 fn transfer_from_cross(718 &mut self,719 caller: caller,720 from: EthCrossAccount,721 to: EthCrossAccount,722 token_id: uint256,723 ) -> Result<void> {724 let caller = T::CrossAccountId::from_eth(caller);725 let from = from.into_sub_cross_account::<T>()?;726 let to = to.into_sub_cross_account::<T>()?;727 let token_id = token_id.try_into()?;728 let budget = self729 .recorder730 .weight_calls_budget(<StructureWeight<T>>::find_parent());731 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)732 .map_err(dispatch_to_evm::<T>)?;733 Ok(())734 }735736 /// @notice Burns a specific ERC721 token.737 /// @dev Throws unless `msg.sender` is the current owner or an authorized738 /// operator for this NFT. Throws if `from` is not the current owner. Throws739 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.740 /// @param from The current owner of the NFT741 /// @param tokenId The NFT to transfer742 #[weight(<SelfWeightOf<T>>::burn_from())]743 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {744 let caller = T::CrossAccountId::from_eth(caller);745 let from = T::CrossAccountId::from_eth(from);746 let token = token_id.try_into()?;747 let budget = self748 .recorder749 .weight_calls_budget(<StructureWeight<T>>::find_parent());750751 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)752 .map_err(dispatch_to_evm::<T>)?;753 Ok(())754 }755756 /// @notice Burns a specific ERC721 token.757 /// @dev Throws unless `msg.sender` is the current owner or an authorized758 /// operator for this NFT. Throws if `from` is not the current owner. Throws759 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.760 /// @param from The current owner of the NFT761 /// @param tokenId The NFT to transfer762 #[weight(<SelfWeightOf<T>>::burn_from())]763 fn burn_from_cross(764 &mut self,765 caller: caller,766 from: EthCrossAccount,767 token_id: uint256,768 ) -> Result<void> {769 let caller = T::CrossAccountId::from_eth(caller);770 let from = from.into_sub_cross_account::<T>()?;771 let token = token_id.try_into()?;772 let budget = self773 .recorder774 .weight_calls_budget(<StructureWeight<T>>::find_parent());775776 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)777 .map_err(dispatch_to_evm::<T>)?;778 Ok(())779 }780781 /// @notice Returns next free NFT ID.782 fn next_token_id(&self) -> Result<uint256> {783 self.consume_store_reads(1)?;784 Ok(<TokensMinted<T>>::get(self.id)785 .checked_add(1)786 .ok_or("item id overflow")?787 .into())788 }789790 /// @notice Function to mint multiple tokens.791 /// @dev `tokenIds` should be an array of consecutive numbers and first number792 /// should be obtained with `nextTokenId` method793 /// @param to The new owner794 /// @param tokenIds IDs of the minted NFTs795 #[solidity(hide)]796 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]797 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {798 let caller = T::CrossAccountId::from_eth(caller);799 let to = T::CrossAccountId::from_eth(to);800 let mut expected_index = <TokensMinted<T>>::get(self.id)801 .checked_add(1)802 .ok_or("item id overflow")?;803 let budget = self804 .recorder805 .weight_calls_budget(<StructureWeight<T>>::find_parent());806807 let total_tokens = token_ids.len();808 for id in token_ids.into_iter() {809 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;810 if id != expected_index {811 return Err("item id should be next".into());812 }813 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;814 }815 let data = (0..total_tokens)816 .map(|_| CreateItemData::<T> {817 properties: BoundedVec::default(),818 owner: to.clone(),819 })820 .collect();821822 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)823 .map_err(dispatch_to_evm::<T>)?;824 Ok(true)825 }826827 /// @notice Function to mint multiple tokens with the given tokenUris.828 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive829 /// numbers and first number should be obtained with `nextTokenId` method830 /// @param to The new owner831 /// @param tokens array of pairs of token ID and token URI for minted tokens832 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]833 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]834 fn mint_bulk_with_token_uri(835 &mut self,836 caller: caller,837 to: address,838 tokens: Vec<(uint256, string)>,839 ) -> Result<bool> {840 let key = key::url();841 let caller = T::CrossAccountId::from_eth(caller);842 let to = T::CrossAccountId::from_eth(to);843 let mut expected_index = <TokensMinted<T>>::get(self.id)844 .checked_add(1)845 .ok_or("item id overflow")?;846 let budget = self847 .recorder848 .weight_calls_budget(<StructureWeight<T>>::find_parent());849850 let mut data = Vec::with_capacity(tokens.len());851 for (id, token_uri) in tokens {852 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;853 if id != expected_index {854 return Err("item id should be next".into());855 }856 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;857858 let mut properties = CollectionPropertiesVec::default();859 properties860 .try_push(Property {861 key: key.clone(),862 value: token_uri863 .into_bytes()864 .try_into()865 .map_err(|_| "token uri is too long")?,866 })867 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;868869 data.push(CreateItemData::<T> {870 properties,871 owner: to.clone(),872 });873 }874875 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)876 .map_err(dispatch_to_evm::<T>)?;877 Ok(true)878 }879}880881#[solidity_interface(882 name = UniqueNFT,883 is(884 ERC721,885 ERC721Enumerable,886 ERC721UniqueExtensions,887 ERC721UniqueMintable,888 ERC721Burnable,889 ERC721Metadata(if(this.flags.erc721metadata)),890 Collection(via(common_mut returns CollectionHandle<T>)),891 TokenProperties,892 )893)]894impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}895896// Not a tests, but code generators897generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);898generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);899900impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>901where902 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,903{904 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");905906 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {907 call::<T, UniqueNFTCall<T>, _, _>(handle, self)908 }909}1// 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 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 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},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};5051/// @title A contract that allows to set and delete token properties and change token property permissions.52#[solidity_interface(name = TokenProperties)]53impl<T: Config> NonfungibleHandle<T> {54 /// @notice Set permissions for token property.55 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.56 /// @param key Property key.57 /// @param isMutable Permission to mutate property.58 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.59 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.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 /// @notice Set token property value.87 /// @dev Throws error if `msg.sender` has no permission to edit the property.88 /// @param tokenId ID of the token.89 /// @param key Property key.90 /// @param value Property value.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.0.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 /// @notice Set token properties value.120 /// @dev Throws error if `msg.sender` has no permission to edit the property.121 /// @param tokenId ID of the token.122 /// @param properties settable properties123 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]124 fn set_properties(125 &mut self,126 caller: caller,127 token_id: uint256,128 properties: Vec<(string, bytes)>,129 ) -> Result<()> {130 let caller = T::CrossAccountId::from_eth(caller);131 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;132133 let nesting_budget = self134 .recorder135 .weight_calls_budget(<StructureWeight<T>>::find_parent());136137 let properties = properties138 .into_iter()139 .map(|(key, value)| {140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too large")?;143144 let value = value.0.try_into().map_err(|_| "value too large")?;145146 Ok(Property { key, value })147 })148 .collect::<Result<Vec<_>>>()?;149150 <Pallet<T>>::set_token_properties(151 self,152 &caller,153 TokenId(token_id),154 properties.into_iter(),155 <Pallet<T>>::token_exists(&self, TokenId(token_id)),156 &nesting_budget,157 )158 .map_err(dispatch_to_evm::<T>)159 }160161 /// @notice Delete token property value.162 /// @dev Throws error if `msg.sender` has no permission to edit the property.163 /// @param tokenId ID of the token.164 /// @param key Property key.165 #[solidity(hide)]166 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {167 let caller = T::CrossAccountId::from_eth(caller);168 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169 let key = <Vec<u8>>::from(key)170 .try_into()171 .map_err(|_| "key too long")?;172173 let nesting_budget = self174 .recorder175 .weight_calls_budget(<StructureWeight<T>>::find_parent());176177 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)178 .map_err(dispatch_to_evm::<T>)179 }180181 /// @notice Delete token properties value.182 /// @dev Throws error if `msg.sender` has no permission to edit the property.183 /// @param tokenId ID of the token.184 /// @param keys Properties key.185 fn delete_properties(186 &mut self,187 token_id: uint256,188 caller: caller,189 keys: Vec<string>,190 ) -> Result<()> {191 let caller = T::CrossAccountId::from_eth(caller);192 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193 let keys = keys194 .into_iter()195 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))196 .collect::<Result<Vec<_>>>()?;197198 let nesting_budget = self199 .recorder200 .weight_calls_budget(<StructureWeight<T>>::find_parent());201202 <Pallet<T>>::delete_token_properties(203 self,204 &caller,205 TokenId(token_id),206 keys.into_iter(),207 &nesting_budget,208 )209 .map_err(dispatch_to_evm::<T>)210 }211212 /// @notice Get token property value.213 /// @dev Throws error if key not found214 /// @param tokenId ID of the token.215 /// @param key Property key.216 /// @return Property value bytes217 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {218 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;219 let key = <Vec<u8>>::from(key)220 .try_into()221 .map_err(|_| "key too long")?;222223 let props = <TokenProperties<T>>::get((self.id, token_id));224 let prop = props.get(&key).ok_or("key not found")?;225226 Ok(prop.to_vec().into())227 }228}229230#[derive(ToLog)]231pub enum ERC721Events {232 /// @dev This emits when ownership of any NFT changes by any mechanism.233 /// This event emits when NFTs are created (`from` == 0) and destroyed234 /// (`to` == 0). Exception: during contract creation, any number of NFTs235 /// may be created and assigned without emitting Transfer. At the time of236 /// any transfer, the approved address for that NFT (if any) is reset to none.237 Transfer {238 #[indexed]239 from: address,240 #[indexed]241 to: address,242 #[indexed]243 token_id: uint256,244 },245 /// @dev This emits when the approved address for an NFT is changed or246 /// reaffirmed. The zero address indicates there is no approved address.247 /// When a Transfer event emits, this also indicates that the approved248 /// address for that NFT (if any) is reset to none.249 Approval {250 #[indexed]251 owner: address,252 #[indexed]253 approved: address,254 #[indexed]255 token_id: uint256,256 },257 /// @dev This emits when an operator is enabled or disabled for an owner.258 /// The operator can manage all NFTs of the owner.259 #[allow(dead_code)]260 ApprovalForAll {261 #[indexed]262 owner: address,263 #[indexed]264 operator: address,265 approved: bool,266 },267}268269#[derive(ToLog)]270pub enum ERC721UniqueMintableEvents {271 #[allow(dead_code)]272 MintingFinished {},273}274275/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension276/// @dev See https://eips.ethereum.org/EIPS/eip-721277#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]278impl<T: Config> NonfungibleHandle<T>279where280 T::AccountId: From<[u8; 32]>,281{282 /// @notice A descriptive name for a collection of NFTs in this contract283 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`284 #[solidity(hide, rename_selector = "name")]285 fn name_proxy(&self) -> Result<string> {286 self.name()287 }288289 /// @notice An abbreviated name for NFTs in this contract290 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`291 #[solidity(hide, rename_selector = "symbol")]292 fn symbol_proxy(&self) -> Result<string> {293 self.symbol()294 }295296 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.297 ///298 /// @dev If the token has a `url` property and it is not empty, it is returned.299 /// 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`.300 /// If the collection property `baseURI` is empty or absent, return "" (empty string)301 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix302 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).303 ///304 /// @return token's const_metadata305 #[solidity(rename_selector = "tokenURI")]306 fn token_uri(&self, token_id: uint256) -> Result<string> {307 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;308309 match get_token_property(self, token_id_u32, &key::url()).as_deref() {310 Err(_) | Ok("") => (),311 Ok(url) => {312 return Ok(url.into());313 }314 };315316 let base_uri =317 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())318 .map(BoundedVec::into_inner)319 .map(string::from_utf8)320 .transpose()321 .map_err(|e| {322 Error::Revert(alloc::format!(323 "Can not convert value \"baseURI\" to string with error \"{}\"",324 e325 ))326 })?;327328 let base_uri = match base_uri.as_deref() {329 None | Some("") => {330 return Ok("".into());331 }332 Some(base_uri) => base_uri.into(),333 };334335 Ok(336 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {337 Err(_) | Ok("") => base_uri,338 Ok(suffix) => base_uri + suffix,339 },340 )341 }342}343344/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension345/// @dev See https://eips.ethereum.org/EIPS/eip-721346#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]347impl<T: Config> NonfungibleHandle<T> {348 /// @notice Enumerate valid NFTs349 /// @param index A counter less than `totalSupply()`350 /// @return The token identifier for the `index`th NFT,351 /// (sort order not specified)352 fn token_by_index(&self, index: uint256) -> Result<uint256> {353 Ok(index)354 }355356 /// @dev Not implemented357 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {358 // TODO: Not implemetable359 Err("not implemented".into())360 }361362 /// @notice Count NFTs tracked by this contract363 /// @return A count of valid NFTs tracked by this contract, where each one of364 /// them has an assigned and queryable owner not equal to the zero address365 fn total_supply(&self) -> Result<uint256> {366 self.consume_store_reads(1)?;367 Ok(<Pallet<T>>::total_supply(self).into())368 }369}370371/// @title ERC-721 Non-Fungible Token Standard372/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md373#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]374impl<T: Config> NonfungibleHandle<T> {375 /// @notice Count all NFTs assigned to an owner376 /// @dev NFTs assigned to the zero address are considered invalid, and this377 /// function throws for queries about the zero address.378 /// @param owner An address for whom to query the balance379 /// @return The number of NFTs owned by `owner`, possibly zero380 fn balance_of(&self, owner: address) -> Result<uint256> {381 self.consume_store_reads(1)?;382 let owner = T::CrossAccountId::from_eth(owner);383 let balance = <AccountBalance<T>>::get((self.id, owner));384 Ok(balance.into())385 }386 /// @notice Find the owner of an NFT387 /// @dev NFTs assigned to zero address are considered invalid, and queries388 /// about them do throw.389 /// @param tokenId The identifier for an NFT390 /// @return The address of the owner of the NFT391 fn owner_of(&self, token_id: uint256) -> Result<address> {392 self.consume_store_reads(1)?;393 let token: TokenId = token_id.try_into()?;394 Ok(*<TokenData<T>>::get((self.id, token))395 .ok_or("token not found")?396 .owner397 .as_eth())398 }399 /// @dev Not implemented400 #[solidity(rename_selector = "safeTransferFrom")]401 fn safe_transfer_from_with_data(402 &mut self,403 _from: address,404 _to: address,405 _token_id: uint256,406 _data: bytes,407 ) -> Result<void> {408 // TODO: Not implemetable409 Err("not implemented".into())410 }411 /// @dev Not implemented412 fn safe_transfer_from(413 &mut self,414 _from: address,415 _to: address,416 _token_id: uint256,417 ) -> Result<void> {418 // TODO: Not implemetable419 Err("not implemented".into())420 }421422 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE423 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE424 /// THEY MAY BE PERMANENTLY LOST425 /// @dev Throws unless `msg.sender` is the current owner or an authorized426 /// operator for this NFT. Throws if `from` is not the current owner. Throws427 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.428 /// @param from The current owner of the NFT429 /// @param to The new owner430 /// @param tokenId The NFT to transfer431 #[weight(<SelfWeightOf<T>>::transfer_from())]432 fn transfer_from(433 &mut self,434 caller: caller,435 from: address,436 to: address,437 token_id: uint256,438 ) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let from = T::CrossAccountId::from_eth(from);441 let to = T::CrossAccountId::from_eth(to);442 let token = token_id.try_into()?;443 let budget = self444 .recorder445 .weight_calls_budget(<StructureWeight<T>>::find_parent());446447 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)448 .map_err(dispatch_to_evm::<T>)?;449 Ok(())450 }451452 /// @notice Set or reaffirm the approved address for an NFT453 /// @dev The zero address indicates there is no approved address.454 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized455 /// operator of the current owner.456 /// @param approved The new approved NFT controller457 /// @param tokenId The NFT to approve458 #[weight(<SelfWeightOf<T>>::approve())]459 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {460 let caller = T::CrossAccountId::from_eth(caller);461 let approved = T::CrossAccountId::from_eth(approved);462 let token = token_id.try_into()?;463464 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))465 .map_err(dispatch_to_evm::<T>)?;466 Ok(())467 }468469 /// @dev Not implemented470 fn set_approval_for_all(471 &mut self,472 _caller: caller,473 _operator: address,474 _approved: bool,475 ) -> Result<void> {476 // TODO: Not implemetable477 Err("not implemented".into())478 }479480 /// @dev Not implemented481 fn get_approved(&self, _token_id: uint256) -> Result<address> {482 // TODO: Not implemetable483 Err("not implemented".into())484 }485486 /// @dev Not implemented487 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {488 // TODO: Not implemetable489 Err("not implemented".into())490 }491}492493/// @title ERC721 Token that can be irreversibly burned (destroyed).494#[solidity_interface(name = ERC721Burnable)]495impl<T: Config> NonfungibleHandle<T> {496 /// @notice Burns a specific ERC721 token.497 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized498 /// operator of the current owner.499 /// @param tokenId The NFT to approve500 #[weight(<SelfWeightOf<T>>::burn_item())]501 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {502 let caller = T::CrossAccountId::from_eth(caller);503 let token = token_id.try_into()?;504505 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;506 Ok(())507 }508}509510/// @title ERC721 minting logic.511#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]512impl<T: Config> NonfungibleHandle<T> {513 fn minting_finished(&self) -> Result<bool> {514 Ok(false)515 }516517 /// @notice Function to mint token.518 /// @param to The new owner519 /// @return uint256 The id of the newly minted token520 #[weight(<SelfWeightOf<T>>::create_item())]521 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {522 let token_id: uint256 = <TokensMinted<T>>::get(self.id)523 .checked_add(1)524 .ok_or("item id overflow")?525 .into();526 self.mint_check_id(caller, to, token_id)?;527 Ok(token_id)528 }529530 /// @notice Function to mint token.531 /// @dev `tokenId` should be obtained with `nextTokenId` method,532 /// unlike standard, you can't specify it manually533 /// @param to The new owner534 /// @param tokenId ID of the minted NFT535 #[solidity(hide, rename_selector = "mint")]536 #[weight(<SelfWeightOf<T>>::create_item())]537 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {538 let caller = T::CrossAccountId::from_eth(caller);539 let to = T::CrossAccountId::from_eth(to);540 let token_id: u32 = token_id.try_into()?;541 let budget = self542 .recorder543 .weight_calls_budget(<StructureWeight<T>>::find_parent());544545 if <TokensMinted<T>>::get(self.id)546 .checked_add(1)547 .ok_or("item id overflow")?548 != token_id549 {550 return Err("item id should be next".into());551 }552553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T> {557 properties: BoundedVec::default(),558 owner: to,559 },560 &budget,561 )562 .map_err(dispatch_to_evm::<T>)?;563564 Ok(true)565 }566567 /// @notice Function to mint token with the given tokenUri.568 /// @param to The new owner569 /// @param tokenUri Token URI that would be stored in the NFT properties570 /// @return uint256 The id of the newly minted token571 #[solidity(rename_selector = "mintWithTokenURI")]572 #[weight(<SelfWeightOf<T>>::create_item())]573 fn mint_with_token_uri(574 &mut self,575 caller: caller,576 to: address,577 token_uri: string,578 ) -> Result<uint256> {579 let token_id: uint256 = <TokensMinted<T>>::get(self.id)580 .checked_add(1)581 .ok_or("item id overflow")?582 .into();583 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584 Ok(token_id)585 }586587 /// @notice Function to mint token with the given tokenUri.588 /// @dev `tokenId` should be obtained with `nextTokenId` method,589 /// unlike standard, you can't specify it manually590 /// @param to The new owner591 /// @param tokenId ID of the minted NFT592 /// @param tokenUri Token URI that would be stored in the NFT properties593 #[solidity(hide, rename_selector = "mintWithTokenURI")]594 #[weight(<SelfWeightOf<T>>::create_item())]595 fn mint_with_token_uri_check_id(596 &mut self,597 caller: caller,598 to: address,599 token_id: uint256,600 token_uri: string,601 ) -> Result<bool> {602 let key = key::url();603 let permission = get_token_permission::<T>(self.id, &key)?;604 if !permission.collection_admin {605 return Err("Operation is not allowed".into());606 }607608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 if <TokensMinted<T>>::get(self.id)616 .checked_add(1)617 .ok_or("item id overflow")?618 != token_id619 {620 return Err("item id should be next".into());621 }622623 let mut properties = CollectionPropertiesVec::default();624 properties625 .try_push(Property {626 key,627 value: token_uri628 .into_bytes()629 .try_into()630 .map_err(|_| "token uri is too long")?,631 })632 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634 <Pallet<T>>::create_item(635 self,636 &caller,637 CreateItemData::<T> {638 properties,639 owner: to,640 },641 &budget,642 )643 .map_err(dispatch_to_evm::<T>)?;644 Ok(true)645 }646647 /// @dev Not implemented648 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {649 Err("not implementable".into())650 }651}652653fn get_token_property<T: Config>(654 collection: &CollectionHandle<T>,655 token_id: u32,656 key: &up_data_structs::PropertyKey,657) -> Result<string> {658 collection.consume_store_reads(1)?;659 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))660 .map_err(|_| Error::Revert("Token properties not found".into()))?;661 if let Some(property) = properties.get(key) {662 return Ok(string::from_utf8_lossy(property).into());663 }664665 Err("Property tokenURI not found".into())666}667668fn get_token_permission<T: Config>(669 collection_id: CollectionId,670 key: &PropertyKey,671) -> Result<PropertyPermission> {672 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)673 .map_err(|_| Error::Revert("No permissions for collection".into()))?;674 let a = token_property_permissions675 .get(key)676 .map(Clone::clone)677 .ok_or_else(|| {678 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();679 Error::Revert(alloc::format!("No permission for key {}", key))680 })?;681 Ok(a)682}683684/// @title Unique extensions for ERC721.685#[solidity_interface(name = ERC721UniqueExtensions)]686impl<T: Config> NonfungibleHandle<T>687where688 T::AccountId: From<[u8; 32]>,689{690 /// @notice A descriptive name for a collection of NFTs in this contract691 fn name(&self) -> Result<string> {692 Ok(decode_utf16(self.name.iter().copied())693 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))694 .collect::<string>())695 }696697 /// @notice An abbreviated name for NFTs in this contract698 fn symbol(&self) -> Result<string> {699 Ok(string::from_utf8_lossy(&self.token_prefix).into())700 }701702 /// @notice Set or reaffirm the approved address for an NFT703 /// @dev The zero address indicates there is no approved address.704 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized705 /// operator of the current owner.706 /// @param approved The new substrate address approved NFT controller707 /// @param tokenId The NFT to approve708 #[weight(<SelfWeightOf<T>>::approve())]709 fn approve_cross(710 &mut self,711 caller: caller,712 approved: EthCrossAccount,713 token_id: uint256,714 ) -> Result<void> {715 let caller = T::CrossAccountId::from_eth(caller);716 let approved = approved.into_sub_cross_account::<T>()?;717 let token = token_id.try_into()?;718719 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))720 .map_err(dispatch_to_evm::<T>)?;721 Ok(())722 }723724 /// @notice Transfer ownership of an NFT725 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`726 /// is the zero address. Throws if `tokenId` is not a valid NFT.727 /// @param to The new owner728 /// @param tokenId The NFT to transfer729 #[weight(<SelfWeightOf<T>>::transfer())]730 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {731 let caller = T::CrossAccountId::from_eth(caller);732 let to = T::CrossAccountId::from_eth(to);733 let token = token_id.try_into()?;734 let budget = self735 .recorder736 .weight_calls_budget(<StructureWeight<T>>::find_parent());737738 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;739 Ok(())740 }741742 /// @notice Transfer ownership of an NFT from cross account address to cross account address743 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`744 /// is the zero address. Throws if `tokenId` is not a valid NFT.745 /// @param from Cross acccount address of current owner746 /// @param to Cross acccount address of new owner747 /// @param tokenId The NFT to transfer748 #[weight(<SelfWeightOf<T>>::transfer())]749 fn transfer_from_cross(750 &mut self,751 caller: caller,752 from: EthCrossAccount,753 to: EthCrossAccount,754 token_id: uint256,755 ) -> Result<void> {756 let caller = T::CrossAccountId::from_eth(caller);757 let from = from.into_sub_cross_account::<T>()?;758 let to = to.into_sub_cross_account::<T>()?;759 let token_id = token_id.try_into()?;760 let budget = self761 .recorder762 .weight_calls_budget(<StructureWeight<T>>::find_parent());763 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)764 .map_err(dispatch_to_evm::<T>)?;765 Ok(())766 }767768 /// @notice Burns a specific ERC721 token.769 /// @dev Throws unless `msg.sender` is the current owner or an authorized770 /// operator for this NFT. Throws if `from` is not the current owner. Throws771 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.772 /// @param from The current owner of the NFT773 /// @param tokenId The NFT to transfer774 #[weight(<SelfWeightOf<T>>::burn_from())]775 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {776 let caller = T::CrossAccountId::from_eth(caller);777 let from = T::CrossAccountId::from_eth(from);778 let token = token_id.try_into()?;779 let budget = self780 .recorder781 .weight_calls_budget(<StructureWeight<T>>::find_parent());782783 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)784 .map_err(dispatch_to_evm::<T>)?;785 Ok(())786 }787788 /// @notice Burns a specific ERC721 token.789 /// @dev Throws unless `msg.sender` is the current owner or an authorized790 /// operator for this NFT. Throws if `from` is not the current owner. Throws791 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.792 /// @param from The current owner of the NFT793 /// @param tokenId The NFT to transfer794 #[weight(<SelfWeightOf<T>>::burn_from())]795 fn burn_from_cross(796 &mut self,797 caller: caller,798 from: EthCrossAccount,799 token_id: uint256,800 ) -> Result<void> {801 let caller = T::CrossAccountId::from_eth(caller);802 let from = from.into_sub_cross_account::<T>()?;803 let token = token_id.try_into()?;804 let budget = self805 .recorder806 .weight_calls_budget(<StructureWeight<T>>::find_parent());807808 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)809 .map_err(dispatch_to_evm::<T>)?;810 Ok(())811 }812813 /// @notice Returns next free NFT ID.814 fn next_token_id(&self) -> Result<uint256> {815 self.consume_store_reads(1)?;816 Ok(<TokensMinted<T>>::get(self.id)817 .checked_add(1)818 .ok_or("item id overflow")?819 .into())820 }821822 /// @notice Function to mint multiple tokens.823 /// @dev `tokenIds` should be an array of consecutive numbers and first number824 /// should be obtained with `nextTokenId` method825 /// @param to The new owner826 /// @param tokenIds IDs of the minted NFTs827 #[solidity(hide)]828 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]829 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {830 let caller = T::CrossAccountId::from_eth(caller);831 let to = T::CrossAccountId::from_eth(to);832 let mut expected_index = <TokensMinted<T>>::get(self.id)833 .checked_add(1)834 .ok_or("item id overflow")?;835 let budget = self836 .recorder837 .weight_calls_budget(<StructureWeight<T>>::find_parent());838839 let total_tokens = token_ids.len();840 for id in token_ids.into_iter() {841 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;842 if id != expected_index {843 return Err("item id should be next".into());844 }845 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;846 }847 let data = (0..total_tokens)848 .map(|_| CreateItemData::<T> {849 properties: BoundedVec::default(),850 owner: to.clone(),851 })852 .collect();853854 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)855 .map_err(dispatch_to_evm::<T>)?;856 Ok(true)857 }858859 /// @notice Function to mint multiple tokens with the given tokenUris.860 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive861 /// numbers and first number should be obtained with `nextTokenId` method862 /// @param to The new owner863 /// @param tokens array of pairs of token ID and token URI for minted tokens864 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]865 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]866 fn mint_bulk_with_token_uri(867 &mut self,868 caller: caller,869 to: address,870 tokens: Vec<(uint256, string)>,871 ) -> Result<bool> {872 let key = key::url();873 let caller = T::CrossAccountId::from_eth(caller);874 let to = T::CrossAccountId::from_eth(to);875 let mut expected_index = <TokensMinted<T>>::get(self.id)876 .checked_add(1)877 .ok_or("item id overflow")?;878 let budget = self879 .recorder880 .weight_calls_budget(<StructureWeight<T>>::find_parent());881882 let mut data = Vec::with_capacity(tokens.len());883 for (id, token_uri) in tokens {884 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;885 if id != expected_index {886 return Err("item id should be next".into());887 }888 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;889890 let mut properties = CollectionPropertiesVec::default();891 properties892 .try_push(Property {893 key: key.clone(),894 value: token_uri895 .into_bytes()896 .try_into()897 .map_err(|_| "token uri is too long")?,898 })899 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;900901 data.push(CreateItemData::<T> {902 properties,903 owner: to.clone(),904 });905 }906907 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)908 .map_err(dispatch_to_evm::<T>)?;909 Ok(true)910 }911}912913#[solidity_interface(914 name = UniqueNFT,915 is(916 ERC721,917 ERC721Enumerable,918 ERC721UniqueExtensions,919 ERC721UniqueMintable,920 ERC721Burnable,921 ERC721Metadata(if(this.flags.erc721metadata)),922 Collection(via(common_mut returns CollectionHandle<T>)),923 TokenProperties,924 )925)]926impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}927928// Not a tests, but code generators929generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);930generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);931932impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>933where934 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,935{936 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");937938 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {939 call::<T, UniqueNFTCall<T>, _, _>(handle, self)940 }941}pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -18,7 +18,7 @@
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x55dba919
+/// @dev the ERC-165 identifier for this interface is 0x91a97a68
contract TokenProperties is Dummy, ERC165 {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -74,16 +74,29 @@
dummy = 0;
}
- /// @notice Delete token property value.
+ // /// @notice Delete token property value.
+ // /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ // /// @param tokenId ID of the token.
+ // /// @param key Property key.
+ // /// @dev EVM selector for this function is: 0x066111d1,
+ // /// or in textual repr: deleteProperty(uint256,string)
+ // function deleteProperty(uint256 tokenId, string memory key) public {
+ // require(false, stub_error);
+ // tokenId;
+ // key;
+ // dummy = 0;
+ // }
+
+ /// @notice Delete token properties value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
- /// @param key Property key.
- /// @dev EVM selector for this function is: 0x066111d1,
- /// or in textual repr: deleteProperty(uint256,string)
- function deleteProperty(uint256 tokenId, string memory key) public {
+ /// @param keys Properties key.
+ /// @dev EVM selector for this function is: 0xc472d371,
+ /// or in textual repr: deleteProperties(uint256,string[])
+ function deleteProperties(uint256 tokenId, string[] memory keys) public {
require(false, stub_error);
tokenId;
- key;
+ keys;
dummy = 0;
}
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -2,10 +2,20 @@
All notable changes to this project will be documented in this file.
+<!-- bureaucrate goes here -->
+
+## [v0.2.7] - 2022-11-11
+
+### Changed
+
+- Added `delete_properties` in eth functions. The `delete_property` function is now deprecated.
+
## [v0.2.6] - 2022-11-02
+
### Changed
- - Use named structure `EthCrossAccount` in eth functions.
+- Use named structure `EthCrossAccount` in eth functions.
+
## [v0.2.5] - 2022-20-10
### Change
@@ -17,8 +27,6 @@
### Change
- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
-
-<!-- bureaucrate goes here -->
## [v0.2.3] 2022-08-16
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-refungible"
-version = "0.2.6"
+version = "0.2.7"
license = "GPLv3"
edition = "2021"
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -164,6 +164,7 @@
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
/// @param key Property key.
+ #[solidity(hide)]
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")?;
@@ -179,6 +180,37 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// @notice Delete token properties value.
+ /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ /// @param tokenId ID of the token.
+ /// @param keys Properties key.
+ fn delete_properties(
+ &mut self,
+ token_id: uint256,
+ caller: caller,
+ keys: Vec<string>,
+ ) -> Result<()> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+ let keys = keys
+ .into_iter()
+ .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))
+ .collect::<Result<Vec<_>>>()?;
+
+ let nesting_budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::delete_token_properties(
+ self,
+ &caller,
+ TokenId(token_id),
+ keys.into_iter(),
+ &nesting_budget,
+ )
+ .map_err(dispatch_to_evm::<T>)
+ }
+
/// @notice Get token property value.
/// @dev Throws error if key not found
/// @param tokenId ID of the token.
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -18,7 +18,7 @@
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x55dba919
+/// @dev the ERC-165 identifier for this interface is 0x91a97a68
contract TokenProperties is Dummy, ERC165 {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -74,16 +74,29 @@
dummy = 0;
}
- /// @notice Delete token property value.
+ // /// @notice Delete token property value.
+ // /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ // /// @param tokenId ID of the token.
+ // /// @param key Property key.
+ // /// @dev EVM selector for this function is: 0x066111d1,
+ // /// or in textual repr: deleteProperty(uint256,string)
+ // function deleteProperty(uint256 tokenId, string memory key) public {
+ // require(false, stub_error);
+ // tokenId;
+ // key;
+ // dummy = 0;
+ // }
+
+ /// @notice Delete token properties value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
- /// @param key Property key.
- /// @dev EVM selector for this function is: 0x066111d1,
- /// or in textual repr: deleteProperty(uint256,string)
- function deleteProperty(uint256 tokenId, string memory key) public {
+ /// @param keys Properties key.
+ /// @dev EVM selector for this function is: 0xc472d371,
+ /// or in textual repr: deleteProperties(uint256,string[])
+ function deleteProperties(uint256 tokenId, string[] memory keys) public {
require(false, stub_error);
tokenId;
- key;
+ keys;
dummy = 0;
}
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -13,7 +13,7 @@
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x55dba919
+/// @dev the ERC-165 identifier for this interface is 0x91a97a68
interface TokenProperties is Dummy, ERC165 {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -51,13 +51,21 @@
/// or in textual repr: setProperties(uint256,(string,bytes)[])
function setProperties(uint256 tokenId, Tuple21[] memory properties) external;
- /// @notice Delete token property value.
+ // /// @notice Delete token property value.
+ // /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ // /// @param tokenId ID of the token.
+ // /// @param key Property key.
+ // /// @dev EVM selector for this function is: 0x066111d1,
+ // /// or in textual repr: deleteProperty(uint256,string)
+ // function deleteProperty(uint256 tokenId, string memory key) external;
+
+ /// @notice Delete token properties value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
- /// @param key Property key.
- /// @dev EVM selector for this function is: 0x066111d1,
- /// or in textual repr: deleteProperty(uint256,string)
- function deleteProperty(uint256 tokenId, string memory key) external;
+ /// @param keys Properties key.
+ /// @dev EVM selector for this function is: 0xc472d371,
+ /// or in textual repr: deleteProperties(uint256,string[])
+ function deleteProperties(uint256 tokenId, string[] memory keys) external;
/// @notice Get token property value.
/// @dev Throws error if key not found
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -13,7 +13,7 @@
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x55dba919
+/// @dev the ERC-165 identifier for this interface is 0x91a97a68
interface TokenProperties is Dummy, ERC165 {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -51,13 +51,21 @@
/// or in textual repr: setProperties(uint256,(string,bytes)[])
function setProperties(uint256 tokenId, Tuple20[] memory properties) external;
- /// @notice Delete token property value.
+ // /// @notice Delete token property value.
+ // /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ // /// @param tokenId ID of the token.
+ // /// @param key Property key.
+ // /// @dev EVM selector for this function is: 0x066111d1,
+ // /// or in textual repr: deleteProperty(uint256,string)
+ // function deleteProperty(uint256 tokenId, string memory key) external;
+
+ /// @notice Delete token properties value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
- /// @param key Property key.
- /// @dev EVM selector for this function is: 0x066111d1,
- /// or in textual repr: deleteProperty(uint256,string)
- function deleteProperty(uint256 tokenId, string memory key) external;
+ /// @param keys Properties key.
+ /// @dev EVM selector for this function is: 0xc472d371,
+ /// or in textual repr: deleteProperties(uint256,string[])
+ function deleteProperties(uint256 tokenId, string[] memory keys) external;
/// @notice Get token property value.
/// @dev Throws error if key not found
tests/src/eth/collectionProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -139,7 +139,7 @@
await contract.methods.setProperty(tokenId1, 'URI', Buffer.from(URI)).send();
expect(await contract.methods.tokenURI(tokenId1).call()).to.equal(URI);
- await contract.methods.deleteProperty(tokenId1, 'URI').send();
+ await contract.methods.deleteProperties(tokenId1, ['URI']).send();
expect(await contract.methods.tokenURI(tokenId1).call()).to.equal(BASE_URI + SUFFIX);
const token2Result = await contract.methods.mintWithTokenURI(bruh, URI).send();
@@ -147,7 +147,7 @@
expect(await contract.methods.tokenURI(tokenId2).call()).to.equal(URI);
- await contract.methods.deleteProperty(tokenId2, 'URI').send();
+ await contract.methods.deleteProperties(tokenId2, ['URI']).send();
expect(await contract.methods.tokenURI(tokenId2).call()).to.equal(BASE_URI);
await contract.methods.setProperty(tokenId2, 'URISuffix', Buffer.from(SUFFIX)).send();
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -334,9 +334,9 @@
{
"inputs": [
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" },
- { "internalType": "string", "name": "key", "type": "string" }
+ { "internalType": "string[]", "name": "keys", "type": "string[]" }
],
- "name": "deleteProperty",
+ "name": "deleteProperties",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
tests/src/eth/reFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -316,9 +316,9 @@
{
"inputs": [
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" },
- { "internalType": "string", "name": "key", "type": "string" }
+ { "internalType": "string[]", "name": "keys", "type": "string[]" }
],
- "name": "deleteProperty",
+ "name": "deleteProperties",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
tests/src/eth/tokenProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -138,18 +138,25 @@
mutable: true,
collectionAdmin: true,
},
+ },
+ {
+ key: 'testKey_1',
+ permission: {
+ mutable: true,
+ collectionAdmin: true,
+ },
}],
});
const token = await collection.mintToken(alice);
- await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);
+ await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]);
await collection.addAdmin(alice, {Ethereum: caller});
const address = helper.ethAddress.fromCollectionId(collection.collectionId);
const contract = helper.ethNativeContract.collection(address, 'nft', caller);
- await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});
+ await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller});
const result = await token.getProperties(['testKey']);
expect(result.length).to.equal(0);