difftreelog
feat add conditional supportInterface for ERC721Metadata
in: master
29 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -684,6 +684,11 @@
pub fn parent_nft() -> up_data_structs::PropertyKey {
property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)
}
+
+ /// Key "parentNft".
+ pub fn erc721_metadata() -> up_data_structs::PropertyKey {
+ property_key_from_bytes(b"ERC721Metadata").expect(EXPECT_CONVERT_ERROR)
+ }
}
/// Values.
@@ -693,10 +698,21 @@
/// Value "ERC721Metadata".
pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";
+ /// Value "1" ERC721 metadata supported.
+ pub const ERC721_METADATA_SUPPORTED: &[u8] = b"1";
+
+ /// Value "0" ERC721 metadata supported.
+ pub const ERC721_METADATA_UNSUPPORTED: &[u8] = b"0";
+
/// Value for [`ERC721_METADATA`].
pub fn erc721() -> up_data_structs::PropertyValue {
property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)
}
+
+ /// Value for [`ERC721_METADATA`].
+ pub fn erc721_metadata_supported() -> up_data_structs::PropertyValue {
+ property_value_from_bytes(ERC721_METADATA_SUPPORTED).expect(EXPECT_CONVERT_ERROR)
+ }
}
/// Convert `byte` to [`PropertyKey`].
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -232,7 +232,7 @@
if !url.is_empty() {
return Ok(url);
}
- } else if !is_erc721_metadata_compatible::<T>(self.id) {
+ } else if !self.supports_metadata() {
return Err("tokenURI not set".into());
}
@@ -548,17 +548,6 @@
}
Err("Property tokenURI not found".into())
-}
-
-fn is_erc721_metadata_compatible<T: Config>(collection_id: CollectionId) -> bool {
- if let Some(shema_name) =
- pallet_common::Pallet::<T>::get_collection_property(collection_id, &key::schema_name())
- {
- let shema_name = shema_name.into_inner();
- shema_name == property_value::ERC721_METADATA
- } else {
- false
- }
}
fn get_token_permission<T: Config>(
@@ -577,16 +566,6 @@
Ok(a)
}
-fn has_token_permission<T: Config>(collection_id: CollectionId, key: &PropertyKey) -> bool {
- if let Ok(token_property_permissions) =
- CollectionPropertyPermissions::<T>::try_get(collection_id)
- {
- return token_property_permissions.contains_key(key);
- }
-
- false
-}
-
/// @title Unique extensions for ERC721.
#[solidity_interface(name = ERC721UniqueExtensions)]
impl<T: Config> NonfungibleHandle<T> {
@@ -731,7 +710,7 @@
name = UniqueNFT,
is(
ERC721,
- ERC721Metadata,
+ ERC721Metadata(if(this.supports_metadata())),
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -108,6 +108,7 @@
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
+ erc::static_property::{key, value},
eth::collection_id_to_address,
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
@@ -295,6 +296,19 @@
&mut self.0
}
}
+
+impl<T: Config> NonfungibleHandle<T> {
+ pub fn supports_metadata(&self) -> bool {
+ if let Some(erc721_metadata) =
+ pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
+ {
+ *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
+ } else {
+ false
+ }
+ }
+}
+
impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {
fn recorder(&self) -> &SubstrateRecorder<T> {
self.0.recorder()
pallets/refungible/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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};30use frame_support::BoundedBTreeMap;31use pallet_common::{32 CollectionHandle, CollectionPropertyPermissions,33 erc::{34 CommonEvmHandler, CollectionCall,35 static_property::{key, value as property_value},36 },37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::H160;42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,45 PropertyKeyPermission, PropertyPermission, TokenId,46};4748use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5455/// @title A contract that allows to set and delete token properties and change token property permissions.56#[solidity_interface(name = TokenProperties)]57impl<T: Config> RefungibleHandle<T> {58 /// @notice Set permissions for token property.59 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.60 /// @param key Property key.61 /// @param isMutable Permission to mutate property.62 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.63 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.64 fn set_token_property_permission(65 &mut self,66 caller: caller,67 key: string,68 is_mutable: bool,69 collection_admin: bool,70 token_owner: bool,71 ) -> Result<()> {72 let caller = T::CrossAccountId::from_eth(caller);73 <Pallet<T>>::set_token_property_permissions(74 self,75 &caller,76 vec![PropertyKeyPermission {77 key: <Vec<u8>>::from(key)78 .try_into()79 .map_err(|_| "too long key")?,80 permission: PropertyPermission {81 mutable: is_mutable,82 collection_admin,83 token_owner,84 },85 }],86 )87 .map_err(dispatch_to_evm::<T>)88 }8990 /// @notice Set token property value.91 /// @dev Throws error if `msg.sender` has no permission to edit the property.92 /// @param tokenId ID of the token.93 /// @param key Property key.94 /// @param value Property value.95 fn set_property(96 &mut self,97 caller: caller,98 token_id: uint256,99 key: string,100 value: bytes,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;104 let key = <Vec<u8>>::from(key)105 .try_into()106 .map_err(|_| "key too long")?;107 let value = value.try_into().map_err(|_| "value too long")?;108109 let nesting_budget = self110 .recorder111 .weight_calls_budget(<StructureWeight<T>>::find_parent());112113 <Pallet<T>>::set_token_property(114 self,115 &caller,116 TokenId(token_id),117 Property { key, value },118 &nesting_budget,119 )120 .map_err(dispatch_to_evm::<T>)121 }122123 /// @notice Delete token property value.124 /// @dev Throws error if `msg.sender` has no permission to edit the property.125 /// @param tokenId ID of the token.126 /// @param key Property key.127 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too long")?;133134 let nesting_budget = self135 .recorder136 .weight_calls_budget(<StructureWeight<T>>::find_parent());137138 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)139 .map_err(dispatch_to_evm::<T>)140 }141142 /// @notice Get token property value.143 /// @dev Throws error if key not found144 /// @param tokenId ID of the token.145 /// @param key Property key.146 /// @return Property value bytes147 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {148 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too long")?;152153 let props = <TokenProperties<T>>::get((self.id, token_id));154 let prop = props.get(&key).ok_or("key not found")?;155156 Ok(prop.to_vec())157 }158}159160#[derive(ToLog)]161pub enum ERC721Events {162 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed163 /// (`to` == 0). Exception: during contract creation, any number of RFTs164 /// may be created and assigned without emitting Transfer.165 Transfer {166 #[indexed]167 from: address,168 #[indexed]169 to: address,170 #[indexed]171 token_id: uint256,172 },173 /// @dev Not supported174 Approval {175 #[indexed]176 owner: address,177 #[indexed]178 approved: address,179 #[indexed]180 token_id: uint256,181 },182 /// @dev Not supported183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721MintableEvents {195 /// @dev Not supported196 #[allow(dead_code)]197 MintingFinished {},198}199200#[solidity_interface(name = ERC721Metadata)]201impl<T: Config> RefungibleHandle<T> {202 /// @notice A descriptive name for a collection of RFTs in this contract203 fn name(&self) -> Result<string> {204 Ok(decode_utf16(self.name.iter().copied())205 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))206 .collect::<string>())207 }208209 /// @notice An abbreviated name for RFTs in this contract210 fn symbol(&self) -> Result<string> {211 Ok(string::from_utf8_lossy(&self.token_prefix).into())212 }213214 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.215 ///216 /// @dev If the token has a `url` property and it is not empty, it is returned.217 /// 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`.218 /// If the collection property `baseURI` is empty or absent, return "" (empty string)219 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix220 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).221 ///222 /// @return token's const_metadata223 #[solidity(rename_selector = "tokenURI")]224 fn token_uri(&self, token_id: uint256) -> Result<string> {225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 if let Ok(url) = get_token_property(self, token_id_u32, &key::url()) {228 if !url.is_empty() {229 return Ok(url);230 }231 } else if !is_erc721_metadata_compatible::<T>(self.id) {232 return Err("tokenURI not set".into());233 }234235 if let Some(base_uri) =236 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())237 {238 if !base_uri.is_empty() {239 let base_uri = string::from_utf8(base_uri.into_inner()).map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245 if let Ok(suffix) = get_token_property(self, token_id_u32, &key::suffix()) {246 if !suffix.is_empty() {247 return Ok(base_uri + suffix.as_str());248 }249 }250251 return Ok(base_uri + token_id.to_string().as_str());252 }253 }254255 Ok("".into())256 }257}258259/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension260/// @dev See https://eips.ethereum.org/EIPS/eip-721261#[solidity_interface(name = ERC721Enumerable)]262impl<T: Config> RefungibleHandle<T> {263 /// @notice Enumerate valid RFTs264 /// @param index A counter less than `totalSupply()`265 /// @return The token identifier for the `index`th NFT,266 /// (sort order not specified)267 fn token_by_index(&self, index: uint256) -> Result<uint256> {268 Ok(index)269 }270271 /// Not implemented272 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {273 // TODO: Not implemetable274 Err("not implemented".into())275 }276277 /// @notice Count RFTs tracked by this contract278 /// @return A count of valid RFTs tracked by this contract, where each one of279 /// them has an assigned and queryable owner not equal to the zero address280 fn total_supply(&self) -> Result<uint256> {281 self.consume_store_reads(1)?;282 Ok(<Pallet<T>>::total_supply(self).into())283 }284}285286/// @title ERC-721 Non-Fungible Token Standard287/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md288#[solidity_interface(name = ERC721, events(ERC721Events))]289impl<T: Config> RefungibleHandle<T> {290 /// @notice Count all RFTs assigned to an owner291 /// @dev RFTs assigned to the zero address are considered invalid, and this292 /// function throws for queries about the zero address.293 /// @param owner An address for whom to query the balance294 /// @return The number of RFTs owned by `owner`, possibly zero295 fn balance_of(&self, owner: address) -> Result<uint256> {296 self.consume_store_reads(1)?;297 let owner = T::CrossAccountId::from_eth(owner);298 let balance = <AccountBalance<T>>::get((self.id, owner));299 Ok(balance.into())300 }301302 /// @notice Find the owner of an RFT303 /// @dev RFTs assigned to zero address are considered invalid, and queries304 /// about them do throw.305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for306 /// the tokens that are partially owned.307 /// @param tokenId The identifier for an RFT308 /// @return The address of the owner of the RFT309 fn owner_of(&self, token_id: uint256) -> Result<address> {310 self.consume_store_reads(2)?;311 let token = token_id.try_into()?;312 let owner = <Pallet<T>>::token_owner(self.id, token);313 Ok(owner314 .map(|address| *address.as_eth())315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))316 }317318 /// @dev Not implemented319 fn safe_transfer_from_with_data(320 &mut self,321 _from: address,322 _to: address,323 _token_id: uint256,324 _data: bytes,325 ) -> Result<void> {326 // TODO: Not implemetable327 Err("not implemented".into())328 }329330 /// @dev Not implemented331 fn safe_transfer_from(332 &mut self,333 _from: address,334 _to: address,335 _token_id: uint256,336 ) -> Result<void> {337 // TODO: Not implemetable338 Err("not implemented".into())339 }340341 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE342 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE343 /// THEY MAY BE PERMANENTLY LOST344 /// @dev Throws unless `msg.sender` is the current owner or an authorized345 /// operator for this RFT. Throws if `from` is not the current owner. Throws346 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.347 /// Throws if RFT pieces have multiple owners.348 /// @param from The current owner of the NFT349 /// @param to The new owner350 /// @param tokenId The NFT to transfer351 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]352 fn transfer_from(353 &mut self,354 caller: caller,355 from: address,356 to: address,357 token_id: uint256,358 ) -> Result<void> {359 let caller = T::CrossAccountId::from_eth(caller);360 let from = T::CrossAccountId::from_eth(from);361 let to = T::CrossAccountId::from_eth(to);362 let token = token_id.try_into()?;363 let budget = self364 .recorder365 .weight_calls_budget(<StructureWeight<T>>::find_parent());366367 let balance = balance(&self, token, &from)?;368 ensure_single_owner(&self, token, balance)?;369370 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)371 .map_err(dispatch_to_evm::<T>)?;372373 Ok(())374 }375376 /// @dev Not implemented377 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {378 Err("not implemented".into())379 }380381 /// @dev Not implemented382 fn set_approval_for_all(383 &mut self,384 _caller: caller,385 _operator: address,386 _approved: bool,387 ) -> Result<void> {388 // TODO: Not implemetable389 Err("not implemented".into())390 }391392 /// @dev Not implemented393 fn get_approved(&self, _token_id: uint256) -> Result<address> {394 // TODO: Not implemetable395 Err("not implemented".into())396 }397398 /// @dev Not implemented399 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403}404405/// Returns amount of pieces of `token` that `owner` have406pub fn balance<T: Config>(407 collection: &RefungibleHandle<T>,408 token: TokenId,409 owner: &T::CrossAccountId,410) -> Result<u128> {411 collection.consume_store_reads(1)?;412 let balance = <Balance<T>>::get((collection.id, token, &owner));413 Ok(balance)414}415416/// Throws if `owner_balance` is lower than total amount of `token` pieces417pub fn ensure_single_owner<T: Config>(418 collection: &RefungibleHandle<T>,419 token: TokenId,420 owner_balance: u128,421) -> Result<()> {422 collection.consume_store_reads(1)?;423 let total_supply = <TotalSupply<T>>::get((collection.id, token));424 if total_supply != owner_balance {425 return Err("token has multiple owners".into());426 }427 Ok(())428}429430/// @title ERC721 Token that can be irreversibly burned (destroyed).431#[solidity_interface(name = ERC721Burnable)]432impl<T: Config> RefungibleHandle<T> {433 /// @notice Burns a specific ERC721 token.434 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized435 /// operator of the current owner.436 /// @param tokenId The RFT to approve437 #[weight(<SelfWeightOf<T>>::burn_item_fully())]438 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let token = token_id.try_into()?;441442 let balance = balance(&self, token, &caller)?;443 ensure_single_owner(&self, token, balance)?;444445 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;446 Ok(())447 }448}449450/// @title ERC721 minting logic.451#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]452impl<T: Config> RefungibleHandle<T> {453 fn minting_finished(&self) -> Result<bool> {454 Ok(false)455 }456457 /// @notice Function to mint token.458 /// @dev `tokenId` should be obtained with `nextTokenId` method,459 /// unlike standard, you can't specify it manually460 /// @param to The new owner461 /// @param tokenId ID of the minted RFT462 #[weight(<SelfWeightOf<T>>::create_item())]463 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {464 let caller = T::CrossAccountId::from_eth(caller);465 let to = T::CrossAccountId::from_eth(to);466 let token_id: u32 = token_id.try_into()?;467 let budget = self468 .recorder469 .weight_calls_budget(<StructureWeight<T>>::find_parent());470471 if <TokensMinted<T>>::get(self.id)472 .checked_add(1)473 .ok_or("item id overflow")?474 != token_id475 {476 return Err("item id should be next".into());477 }478479 let users = [(to.clone(), 1)]480 .into_iter()481 .collect::<BTreeMap<_, _>>()482 .try_into()483 .unwrap();484 <Pallet<T>>::create_item(485 self,486 &caller,487 CreateItemData::<T::CrossAccountId> {488 users,489 properties: CollectionPropertiesVec::default(),490 },491 &budget,492 )493 .map_err(dispatch_to_evm::<T>)?;494495 Ok(true)496 }497498 /// @notice Function to mint token with the given tokenUri.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 RFT503 /// @param tokenUri Token URI that would be stored in the RFT properties504 #[solidity(rename_selector = "mintWithTokenURI")]505 #[weight(<SelfWeightOf<T>>::create_item())]506 fn mint_with_token_uri(507 &mut self,508 caller: caller,509 to: address,510 token_id: uint256,511 token_uri: string,512 ) -> Result<bool> {513 let key = key::url();514 let permission = get_token_permission::<T>(self.id, &key)?;515 if !permission.collection_admin {516 return Err("Operation is not allowed".into());517 }518519 let caller = T::CrossAccountId::from_eth(caller);520 let to = T::CrossAccountId::from_eth(to);521 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;522 let budget = self523 .recorder524 .weight_calls_budget(<StructureWeight<T>>::find_parent());525526 if <TokensMinted<T>>::get(self.id)527 .checked_add(1)528 .ok_or("item id overflow")?529 != token_id530 {531 return Err("item id should be next".into());532 }533534 let mut properties = CollectionPropertiesVec::default();535 properties536 .try_push(Property {537 key,538 value: token_uri539 .into_bytes()540 .try_into()541 .map_err(|_| "token uri is too long")?,542 })543 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;544545 let users = [(to.clone(), 1)]546 .into_iter()547 .collect::<BTreeMap<_, _>>()548 .try_into()549 .unwrap();550 <Pallet<T>>::create_item(551 self,552 &caller,553 CreateItemData::<T::CrossAccountId> { users, properties },554 &budget,555 )556 .map_err(dispatch_to_evm::<T>)?;557 Ok(true)558 }559560 /// @dev Not implemented561 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {562 Err("not implementable".into())563 }564}565566fn get_token_property<T: Config>(567 collection: &CollectionHandle<T>,568 token_id: u32,569 key: &up_data_structs::PropertyKey,570) -> Result<string> {571 collection.consume_store_reads(1)?;572 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))573 .map_err(|_| Error::Revert("Token properties not found".into()))?;574 if let Some(property) = properties.get(key) {575 return Ok(string::from_utf8_lossy(property).into());576 }577578 Err("Property tokenURI not found".into())579}580581fn is_erc721_metadata_compatible<T: Config>(collection_id: CollectionId) -> bool {582 if let Some(shema_name) =583 pallet_common::Pallet::<T>::get_collection_property(collection_id, &key::schema_name())584 {585 let shema_name = shema_name.into_inner();586 shema_name == property_value::ERC721_METADATA587 } else {588 false589 }590}591592fn get_token_permission<T: Config>(593 collection_id: CollectionId,594 key: &PropertyKey,595) -> Result<PropertyPermission> {596 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)597 .map_err(|_| Error::Revert("No permissions for collection".into()))?;598 let a = token_property_permissions599 .get(key)600 .map(Clone::clone)601 .ok_or_else(|| {602 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();603 Error::Revert(alloc::format!("No permission for key {}", key))604 })?;605 Ok(a)606}607608/// @title Unique extensions for ERC721.609#[solidity_interface(name = ERC721UniqueExtensions)]610impl<T: Config> RefungibleHandle<T> {611 /// @notice Transfer ownership of an RFT612 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`613 /// is the zero address. Throws if `tokenId` is not a valid RFT.614 /// Throws if RFT pieces have multiple owners.615 /// @param to The new owner616 /// @param tokenId The RFT to transfer617 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]618 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {619 let caller = T::CrossAccountId::from_eth(caller);620 let to = T::CrossAccountId::from_eth(to);621 let token = token_id.try_into()?;622 let budget = self623 .recorder624 .weight_calls_budget(<StructureWeight<T>>::find_parent());625626 let balance = balance(&self, token, &caller)?;627 ensure_single_owner(&self, token, balance)?;628629 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)630 .map_err(dispatch_to_evm::<T>)?;631 Ok(())632 }633634 /// @notice Burns a specific ERC721 token.635 /// @dev Throws unless `msg.sender` is the current owner or an authorized636 /// operator for this RFT. Throws if `from` is not the current owner. Throws637 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.638 /// Throws if RFT pieces have multiple owners.639 /// @param from The current owner of the RFT640 /// @param tokenId The RFT to transfer641 #[weight(<SelfWeightOf<T>>::burn_from())]642 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {643 let caller = T::CrossAccountId::from_eth(caller);644 let from = T::CrossAccountId::from_eth(from);645 let token = token_id.try_into()?;646 let budget = self647 .recorder648 .weight_calls_budget(<StructureWeight<T>>::find_parent());649650 let balance = balance(&self, token, &caller)?;651 ensure_single_owner(&self, token, balance)?;652653 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)654 .map_err(dispatch_to_evm::<T>)?;655 Ok(())656 }657658 /// @notice Returns next free RFT ID.659 fn next_token_id(&self) -> Result<uint256> {660 self.consume_store_reads(1)?;661 Ok(<TokensMinted<T>>::get(self.id)662 .checked_add(1)663 .ok_or("item id overflow")?664 .into())665 }666667 /// @notice Function to mint multiple tokens.668 /// @dev `tokenIds` should be an array of consecutive numbers and first number669 /// should be obtained with `nextTokenId` method670 /// @param to The new owner671 /// @param tokenIds IDs of the minted RFTs672 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]673 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {674 let caller = T::CrossAccountId::from_eth(caller);675 let to = T::CrossAccountId::from_eth(to);676 let mut expected_index = <TokensMinted<T>>::get(self.id)677 .checked_add(1)678 .ok_or("item id overflow")?;679 let budget = self680 .recorder681 .weight_calls_budget(<StructureWeight<T>>::find_parent());682683 let total_tokens = token_ids.len();684 for id in token_ids.into_iter() {685 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;686 if id != expected_index {687 return Err("item id should be next".into());688 }689 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;690 }691 let users = [(to.clone(), 1)]692 .into_iter()693 .collect::<BTreeMap<_, _>>()694 .try_into()695 .unwrap();696 let create_item_data = CreateItemData::<T::CrossAccountId> {697 users,698 properties: CollectionPropertiesVec::default(),699 };700 let data = (0..total_tokens)701 .map(|_| create_item_data.clone())702 .collect();703704 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)705 .map_err(dispatch_to_evm::<T>)?;706 Ok(true)707 }708709 /// @notice Function to mint multiple tokens with the given tokenUris.710 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive711 /// numbers and first number should be obtained with `nextTokenId` method712 /// @param to The new owner713 /// @param tokens array of pairs of token ID and token URI for minted tokens714 #[solidity(rename_selector = "mintBulkWithTokenURI")]715 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]716 fn mint_bulk_with_token_uri(717 &mut self,718 caller: caller,719 to: address,720 tokens: Vec<(uint256, string)>,721 ) -> Result<bool> {722 let key = key::url();723 let caller = T::CrossAccountId::from_eth(caller);724 let to = T::CrossAccountId::from_eth(to);725 let mut expected_index = <TokensMinted<T>>::get(self.id)726 .checked_add(1)727 .ok_or("item id overflow")?;728 let budget = self729 .recorder730 .weight_calls_budget(<StructureWeight<T>>::find_parent());731732 let mut data = Vec::with_capacity(tokens.len());733 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]734 .into_iter()735 .collect::<BTreeMap<_, _>>()736 .try_into()737 .unwrap();738 for (id, token_uri) in tokens {739 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;740 if id != expected_index {741 return Err("item id should be next".into());742 }743 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;744745 let mut properties = CollectionPropertiesVec::default();746 properties747 .try_push(Property {748 key: key.clone(),749 value: token_uri750 .into_bytes()751 .try_into()752 .map_err(|_| "token uri is too long")?,753 })754 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;755756 let create_item_data = CreateItemData::<T::CrossAccountId> {757 users: users.clone(),758 properties,759 };760 data.push(create_item_data);761 }762763 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)764 .map_err(dispatch_to_evm::<T>)?;765 Ok(true)766 }767768 /// Returns EVM address for refungible token769 ///770 /// @param token ID of the token771 fn token_contract_address(&self, token: uint256) -> Result<address> {772 Ok(T::EvmTokenAddressMapping::token_to_address(773 self.id,774 token.try_into().map_err(|_| "token id overflow")?,775 ))776 }777}778779#[solidity_interface(780 name = UniqueRefungible,781 is(782 ERC721,783 ERC721Metadata,784 ERC721Enumerable,785 ERC721UniqueExtensions,786 ERC721Mintable,787 ERC721Burnable,788 Collection(via(common_mut returns CollectionHandle<T>)),789 TokenProperties,790 )791)]792impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}793794// Not a tests, but code generators795generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);796generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);797798impl<T: Config> CommonEvmHandler for RefungibleHandle<T>799where800 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,801{802 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");803 fn call(804 self,805 handle: &mut impl PrecompileHandle,806 ) -> Option<pallet_common::erc::PrecompileResult> {807 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)808 }809}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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};30use frame_support::BoundedBTreeMap;31use pallet_common::{32 CollectionHandle, CollectionPropertyPermissions,33 erc::{34 CommonEvmHandler, CollectionCall,35 static_property::{key, value as property_value},36 },37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::H160;42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,45 PropertyKeyPermission, PropertyPermission, TokenId,46};4748use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5455/// @title A contract that allows to set and delete token properties and change token property permissions.56#[solidity_interface(name = TokenProperties)]57impl<T: Config> RefungibleHandle<T> {58 /// @notice Set permissions for token property.59 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.60 /// @param key Property key.61 /// @param isMutable Permission to mutate property.62 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.63 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.64 fn set_token_property_permission(65 &mut self,66 caller: caller,67 key: string,68 is_mutable: bool,69 collection_admin: bool,70 token_owner: bool,71 ) -> Result<()> {72 let caller = T::CrossAccountId::from_eth(caller);73 <Pallet<T>>::set_token_property_permissions(74 self,75 &caller,76 vec![PropertyKeyPermission {77 key: <Vec<u8>>::from(key)78 .try_into()79 .map_err(|_| "too long key")?,80 permission: PropertyPermission {81 mutable: is_mutable,82 collection_admin,83 token_owner,84 },85 }],86 )87 .map_err(dispatch_to_evm::<T>)88 }8990 /// @notice Set token property value.91 /// @dev Throws error if `msg.sender` has no permission to edit the property.92 /// @param tokenId ID of the token.93 /// @param key Property key.94 /// @param value Property value.95 fn set_property(96 &mut self,97 caller: caller,98 token_id: uint256,99 key: string,100 value: bytes,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;104 let key = <Vec<u8>>::from(key)105 .try_into()106 .map_err(|_| "key too long")?;107 let value = value.try_into().map_err(|_| "value too long")?;108109 let nesting_budget = self110 .recorder111 .weight_calls_budget(<StructureWeight<T>>::find_parent());112113 <Pallet<T>>::set_token_property(114 self,115 &caller,116 TokenId(token_id),117 Property { key, value },118 &nesting_budget,119 )120 .map_err(dispatch_to_evm::<T>)121 }122123 /// @notice Delete token property value.124 /// @dev Throws error if `msg.sender` has no permission to edit the property.125 /// @param tokenId ID of the token.126 /// @param key Property key.127 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too long")?;133134 let nesting_budget = self135 .recorder136 .weight_calls_budget(<StructureWeight<T>>::find_parent());137138 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)139 .map_err(dispatch_to_evm::<T>)140 }141142 /// @notice Get token property value.143 /// @dev Throws error if key not found144 /// @param tokenId ID of the token.145 /// @param key Property key.146 /// @return Property value bytes147 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {148 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too long")?;152153 let props = <TokenProperties<T>>::get((self.id, token_id));154 let prop = props.get(&key).ok_or("key not found")?;155156 Ok(prop.to_vec())157 }158}159160#[derive(ToLog)]161pub enum ERC721Events {162 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed163 /// (`to` == 0). Exception: during contract creation, any number of RFTs164 /// may be created and assigned without emitting Transfer.165 Transfer {166 #[indexed]167 from: address,168 #[indexed]169 to: address,170 #[indexed]171 token_id: uint256,172 },173 /// @dev Not supported174 Approval {175 #[indexed]176 owner: address,177 #[indexed]178 approved: address,179 #[indexed]180 token_id: uint256,181 },182 /// @dev Not supported183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721MintableEvents {195 /// @dev Not supported196 #[allow(dead_code)]197 MintingFinished {},198}199200#[solidity_interface(name = ERC721Metadata)]201impl<T: Config> RefungibleHandle<T> {202 /// @notice A descriptive name for a collection of RFTs in this contract203 fn name(&self) -> Result<string> {204 Ok(decode_utf16(self.name.iter().copied())205 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))206 .collect::<string>())207 }208209 /// @notice An abbreviated name for RFTs in this contract210 fn symbol(&self) -> Result<string> {211 Ok(string::from_utf8_lossy(&self.token_prefix).into())212 }213214 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.215 ///216 /// @dev If the token has a `url` property and it is not empty, it is returned.217 /// 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`.218 /// If the collection property `baseURI` is empty or absent, return "" (empty string)219 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix220 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).221 ///222 /// @return token's const_metadata223 #[solidity(rename_selector = "tokenURI")]224 fn token_uri(&self, token_id: uint256) -> Result<string> {225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 if let Ok(url) = get_token_property(self, token_id_u32, &key::url()) {228 if !url.is_empty() {229 return Ok(url);230 }231 } else if !self.supports_metadata() {232 return Err("tokenURI not set".into());233 }234235 if let Some(base_uri) =236 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())237 {238 if !base_uri.is_empty() {239 let base_uri = string::from_utf8(base_uri.into_inner()).map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245 if let Ok(suffix) = get_token_property(self, token_id_u32, &key::suffix()) {246 if !suffix.is_empty() {247 return Ok(base_uri + suffix.as_str());248 }249 }250251 return Ok(base_uri + token_id.to_string().as_str());252 }253 }254255 Ok("".into())256 }257}258259/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension260/// @dev See https://eips.ethereum.org/EIPS/eip-721261#[solidity_interface(name = ERC721Enumerable)]262impl<T: Config> RefungibleHandle<T> {263 /// @notice Enumerate valid RFTs264 /// @param index A counter less than `totalSupply()`265 /// @return The token identifier for the `index`th NFT,266 /// (sort order not specified)267 fn token_by_index(&self, index: uint256) -> Result<uint256> {268 Ok(index)269 }270271 /// Not implemented272 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {273 // TODO: Not implemetable274 Err("not implemented".into())275 }276277 /// @notice Count RFTs tracked by this contract278 /// @return A count of valid RFTs tracked by this contract, where each one of279 /// them has an assigned and queryable owner not equal to the zero address280 fn total_supply(&self) -> Result<uint256> {281 self.consume_store_reads(1)?;282 Ok(<Pallet<T>>::total_supply(self).into())283 }284}285286/// @title ERC-721 Non-Fungible Token Standard287/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md288#[solidity_interface(name = ERC721, events(ERC721Events))]289impl<T: Config> RefungibleHandle<T> {290 /// @notice Count all RFTs assigned to an owner291 /// @dev RFTs assigned to the zero address are considered invalid, and this292 /// function throws for queries about the zero address.293 /// @param owner An address for whom to query the balance294 /// @return The number of RFTs owned by `owner`, possibly zero295 fn balance_of(&self, owner: address) -> Result<uint256> {296 self.consume_store_reads(1)?;297 let owner = T::CrossAccountId::from_eth(owner);298 let balance = <AccountBalance<T>>::get((self.id, owner));299 Ok(balance.into())300 }301302 /// @notice Find the owner of an RFT303 /// @dev RFTs assigned to zero address are considered invalid, and queries304 /// about them do throw.305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for306 /// the tokens that are partially owned.307 /// @param tokenId The identifier for an RFT308 /// @return The address of the owner of the RFT309 fn owner_of(&self, token_id: uint256) -> Result<address> {310 self.consume_store_reads(2)?;311 let token = token_id.try_into()?;312 let owner = <Pallet<T>>::token_owner(self.id, token);313 Ok(owner314 .map(|address| *address.as_eth())315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))316 }317318 /// @dev Not implemented319 fn safe_transfer_from_with_data(320 &mut self,321 _from: address,322 _to: address,323 _token_id: uint256,324 _data: bytes,325 ) -> Result<void> {326 // TODO: Not implemetable327 Err("not implemented".into())328 }329330 /// @dev Not implemented331 fn safe_transfer_from(332 &mut self,333 _from: address,334 _to: address,335 _token_id: uint256,336 ) -> Result<void> {337 // TODO: Not implemetable338 Err("not implemented".into())339 }340341 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE342 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE343 /// THEY MAY BE PERMANENTLY LOST344 /// @dev Throws unless `msg.sender` is the current owner or an authorized345 /// operator for this RFT. Throws if `from` is not the current owner. Throws346 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.347 /// Throws if RFT pieces have multiple owners.348 /// @param from The current owner of the NFT349 /// @param to The new owner350 /// @param tokenId The NFT to transfer351 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]352 fn transfer_from(353 &mut self,354 caller: caller,355 from: address,356 to: address,357 token_id: uint256,358 ) -> Result<void> {359 let caller = T::CrossAccountId::from_eth(caller);360 let from = T::CrossAccountId::from_eth(from);361 let to = T::CrossAccountId::from_eth(to);362 let token = token_id.try_into()?;363 let budget = self364 .recorder365 .weight_calls_budget(<StructureWeight<T>>::find_parent());366367 let balance = balance(&self, token, &from)?;368 ensure_single_owner(&self, token, balance)?;369370 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)371 .map_err(dispatch_to_evm::<T>)?;372373 Ok(())374 }375376 /// @dev Not implemented377 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {378 Err("not implemented".into())379 }380381 /// @dev Not implemented382 fn set_approval_for_all(383 &mut self,384 _caller: caller,385 _operator: address,386 _approved: bool,387 ) -> Result<void> {388 // TODO: Not implemetable389 Err("not implemented".into())390 }391392 /// @dev Not implemented393 fn get_approved(&self, _token_id: uint256) -> Result<address> {394 // TODO: Not implemetable395 Err("not implemented".into())396 }397398 /// @dev Not implemented399 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403}404405/// Returns amount of pieces of `token` that `owner` have406pub fn balance<T: Config>(407 collection: &RefungibleHandle<T>,408 token: TokenId,409 owner: &T::CrossAccountId,410) -> Result<u128> {411 collection.consume_store_reads(1)?;412 let balance = <Balance<T>>::get((collection.id, token, &owner));413 Ok(balance)414}415416/// Throws if `owner_balance` is lower than total amount of `token` pieces417pub fn ensure_single_owner<T: Config>(418 collection: &RefungibleHandle<T>,419 token: TokenId,420 owner_balance: u128,421) -> Result<()> {422 collection.consume_store_reads(1)?;423 let total_supply = <TotalSupply<T>>::get((collection.id, token));424 if total_supply != owner_balance {425 return Err("token has multiple owners".into());426 }427 Ok(())428}429430/// @title ERC721 Token that can be irreversibly burned (destroyed).431#[solidity_interface(name = ERC721Burnable)]432impl<T: Config> RefungibleHandle<T> {433 /// @notice Burns a specific ERC721 token.434 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized435 /// operator of the current owner.436 /// @param tokenId The RFT to approve437 #[weight(<SelfWeightOf<T>>::burn_item_fully())]438 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let token = token_id.try_into()?;441442 let balance = balance(&self, token, &caller)?;443 ensure_single_owner(&self, token, balance)?;444445 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;446 Ok(())447 }448}449450/// @title ERC721 minting logic.451#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]452impl<T: Config> RefungibleHandle<T> {453 fn minting_finished(&self) -> Result<bool> {454 Ok(false)455 }456457 /// @notice Function to mint token.458 /// @dev `tokenId` should be obtained with `nextTokenId` method,459 /// unlike standard, you can't specify it manually460 /// @param to The new owner461 /// @param tokenId ID of the minted RFT462 #[weight(<SelfWeightOf<T>>::create_item())]463 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {464 let caller = T::CrossAccountId::from_eth(caller);465 let to = T::CrossAccountId::from_eth(to);466 let token_id: u32 = token_id.try_into()?;467 let budget = self468 .recorder469 .weight_calls_budget(<StructureWeight<T>>::find_parent());470471 if <TokensMinted<T>>::get(self.id)472 .checked_add(1)473 .ok_or("item id overflow")?474 != token_id475 {476 return Err("item id should be next".into());477 }478479 let users = [(to.clone(), 1)]480 .into_iter()481 .collect::<BTreeMap<_, _>>()482 .try_into()483 .unwrap();484 <Pallet<T>>::create_item(485 self,486 &caller,487 CreateItemData::<T::CrossAccountId> {488 users,489 properties: CollectionPropertiesVec::default(),490 },491 &budget,492 )493 .map_err(dispatch_to_evm::<T>)?;494495 Ok(true)496 }497498 /// @notice Function to mint token with the given tokenUri.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 RFT503 /// @param tokenUri Token URI that would be stored in the RFT properties504 #[solidity(rename_selector = "mintWithTokenURI")]505 #[weight(<SelfWeightOf<T>>::create_item())]506 fn mint_with_token_uri(507 &mut self,508 caller: caller,509 to: address,510 token_id: uint256,511 token_uri: string,512 ) -> Result<bool> {513 let key = key::url();514 let permission = get_token_permission::<T>(self.id, &key)?;515 if !permission.collection_admin {516 return Err("Operation is not allowed".into());517 }518519 let caller = T::CrossAccountId::from_eth(caller);520 let to = T::CrossAccountId::from_eth(to);521 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;522 let budget = self523 .recorder524 .weight_calls_budget(<StructureWeight<T>>::find_parent());525526 if <TokensMinted<T>>::get(self.id)527 .checked_add(1)528 .ok_or("item id overflow")?529 != token_id530 {531 return Err("item id should be next".into());532 }533534 let mut properties = CollectionPropertiesVec::default();535 properties536 .try_push(Property {537 key,538 value: token_uri539 .into_bytes()540 .try_into()541 .map_err(|_| "token uri is too long")?,542 })543 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;544545 let users = [(to.clone(), 1)]546 .into_iter()547 .collect::<BTreeMap<_, _>>()548 .try_into()549 .unwrap();550 <Pallet<T>>::create_item(551 self,552 &caller,553 CreateItemData::<T::CrossAccountId> { users, properties },554 &budget,555 )556 .map_err(dispatch_to_evm::<T>)?;557 Ok(true)558 }559560 /// @dev Not implemented561 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {562 Err("not implementable".into())563 }564}565566fn get_token_property<T: Config>(567 collection: &CollectionHandle<T>,568 token_id: u32,569 key: &up_data_structs::PropertyKey,570) -> Result<string> {571 collection.consume_store_reads(1)?;572 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))573 .map_err(|_| Error::Revert("Token properties not found".into()))?;574 if let Some(property) = properties.get(key) {575 return Ok(string::from_utf8_lossy(property).into());576 }577578 Err("Property tokenURI not found".into())579}580581fn get_token_permission<T: Config>(582 collection_id: CollectionId,583 key: &PropertyKey,584) -> Result<PropertyPermission> {585 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)586 .map_err(|_| Error::Revert("No permissions for collection".into()))?;587 let a = token_property_permissions588 .get(key)589 .map(Clone::clone)590 .ok_or_else(|| {591 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();592 Error::Revert(alloc::format!("No permission for key {}", key))593 })?;594 Ok(a)595}596597/// @title Unique extensions for ERC721.598#[solidity_interface(name = ERC721UniqueExtensions)]599impl<T: Config> RefungibleHandle<T> {600 /// @notice Transfer ownership of an RFT601 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`602 /// is the zero address. Throws if `tokenId` is not a valid RFT.603 /// Throws if RFT pieces have multiple owners.604 /// @param to The new owner605 /// @param tokenId The RFT to transfer606 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]607 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token = token_id.try_into()?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 let balance = balance(&self, token, &caller)?;616 ensure_single_owner(&self, token, balance)?;617618 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)619 .map_err(dispatch_to_evm::<T>)?;620 Ok(())621 }622623 /// @notice Burns a specific ERC721 token.624 /// @dev Throws unless `msg.sender` is the current owner or an authorized625 /// operator for this RFT. Throws if `from` is not the current owner. Throws626 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.627 /// Throws if RFT pieces have multiple owners.628 /// @param from The current owner of the RFT629 /// @param tokenId The RFT to transfer630 #[weight(<SelfWeightOf<T>>::burn_from())]631 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {632 let caller = T::CrossAccountId::from_eth(caller);633 let from = T::CrossAccountId::from_eth(from);634 let token = token_id.try_into()?;635 let budget = self636 .recorder637 .weight_calls_budget(<StructureWeight<T>>::find_parent());638639 let balance = balance(&self, token, &caller)?;640 ensure_single_owner(&self, token, balance)?;641642 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)643 .map_err(dispatch_to_evm::<T>)?;644 Ok(())645 }646647 /// @notice Returns next free RFT ID.648 fn next_token_id(&self) -> Result<uint256> {649 self.consume_store_reads(1)?;650 Ok(<TokensMinted<T>>::get(self.id)651 .checked_add(1)652 .ok_or("item id overflow")?653 .into())654 }655656 /// @notice Function to mint multiple tokens.657 /// @dev `tokenIds` should be an array of consecutive numbers and first number658 /// should be obtained with `nextTokenId` method659 /// @param to The new owner660 /// @param tokenIds IDs of the minted RFTs661 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]662 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {663 let caller = T::CrossAccountId::from_eth(caller);664 let to = T::CrossAccountId::from_eth(to);665 let mut expected_index = <TokensMinted<T>>::get(self.id)666 .checked_add(1)667 .ok_or("item id overflow")?;668 let budget = self669 .recorder670 .weight_calls_budget(<StructureWeight<T>>::find_parent());671672 let total_tokens = token_ids.len();673 for id in token_ids.into_iter() {674 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;675 if id != expected_index {676 return Err("item id should be next".into());677 }678 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;679 }680 let users = [(to.clone(), 1)]681 .into_iter()682 .collect::<BTreeMap<_, _>>()683 .try_into()684 .unwrap();685 let create_item_data = CreateItemData::<T::CrossAccountId> {686 users,687 properties: CollectionPropertiesVec::default(),688 };689 let data = (0..total_tokens)690 .map(|_| create_item_data.clone())691 .collect();692693 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)694 .map_err(dispatch_to_evm::<T>)?;695 Ok(true)696 }697698 /// @notice Function to mint multiple tokens with the given tokenUris.699 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive700 /// numbers and first number should be obtained with `nextTokenId` method701 /// @param to The new owner702 /// @param tokens array of pairs of token ID and token URI for minted tokens703 #[solidity(rename_selector = "mintBulkWithTokenURI")]704 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]705 fn mint_bulk_with_token_uri(706 &mut self,707 caller: caller,708 to: address,709 tokens: Vec<(uint256, string)>,710 ) -> Result<bool> {711 let key = key::url();712 let caller = T::CrossAccountId::from_eth(caller);713 let to = T::CrossAccountId::from_eth(to);714 let mut expected_index = <TokensMinted<T>>::get(self.id)715 .checked_add(1)716 .ok_or("item id overflow")?;717 let budget = self718 .recorder719 .weight_calls_budget(<StructureWeight<T>>::find_parent());720721 let mut data = Vec::with_capacity(tokens.len());722 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]723 .into_iter()724 .collect::<BTreeMap<_, _>>()725 .try_into()726 .unwrap();727 for (id, token_uri) in tokens {728 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;729 if id != expected_index {730 return Err("item id should be next".into());731 }732 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;733734 let mut properties = CollectionPropertiesVec::default();735 properties736 .try_push(Property {737 key: key.clone(),738 value: token_uri739 .into_bytes()740 .try_into()741 .map_err(|_| "token uri is too long")?,742 })743 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;744745 let create_item_data = CreateItemData::<T::CrossAccountId> {746 users: users.clone(),747 properties,748 };749 data.push(create_item_data);750 }751752 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)753 .map_err(dispatch_to_evm::<T>)?;754 Ok(true)755 }756757 /// Returns EVM address for refungible token758 ///759 /// @param token ID of the token760 fn token_contract_address(&self, token: uint256) -> Result<address> {761 Ok(T::EvmTokenAddressMapping::token_to_address(762 self.id,763 token.try_into().map_err(|_| "token id overflow")?,764 ))765 }766}767768#[solidity_interface(769 name = UniqueRefungible,770 is(771 ERC721,772 ERC721Metadata(if(this.supports_metadata())),773 ERC721Enumerable,774 ERC721UniqueExtensions,775 ERC721Mintable,776 ERC721Burnable,777 Collection(via(common_mut returns CollectionHandle<T>)),778 TokenProperties,779 )780)]781impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}782783// Not a tests, but code generators784generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);785generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);786787impl<T: Config> CommonEvmHandler for RefungibleHandle<T>788where789 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,790{791 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");792 fn call(793 self,794 handle: &mut impl PrecompileHandle,795 ) -> Option<pallet_common::erc::PrecompileResult> {796 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)797 }798}pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -92,14 +92,19 @@
use codec::{Encode, Decode, MaxEncodedLen};
use core::ops::Deref;
+use derivative::Derivative;
use evm_coder::ToLog;
use frame_support::{
- BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,
+ BoundedBTreeMap, BoundedVec, ensure, fail, storage::with_transaction, transactional,
+ pallet_prelude::ConstU32,
};
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_evm_coder_substrate::WithRecorder;
use pallet_common::{
- CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
+ CommonCollectionOperations,
+ erc::static_property::{key, value},
+ Error as CommonError,
+ eth::collection_id_to_address,
Event as CommonEvent, Pallet as PalletCommon,
};
use pallet_structure::Pallet as PalletStructure;
@@ -113,8 +118,6 @@
MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
PropertyScope, PropertyValue, TokenId, TrySetProperty,
};
-use frame_support::BoundedBTreeMap;
-use derivative::Derivative;
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
@@ -301,6 +304,18 @@
}
}
+impl<T: Config> RefungibleHandle<T> {
+ pub fn supports_metadata(&self) -> bool {
+ if let Some(erc721_metadata) =
+ pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
+ {
+ *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
+ } else {
+ false
+ }
+ }
+}
+
impl<T: Config> Deref for RefungibleHandle<T> {
type Target = pallet_common::CollectionHandle<T>;
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -130,6 +130,13 @@
})
.map_err(|e| Error::Revert(format!("{:?}", e)))?;
+ properties
+ .try_push(up_data_structs::Property {
+ key: key::erc721_metadata(),
+ value: property_value::erc721_metadata_supported(),
+ })
+ .map_err(|e| Error::Revert(format!("{:?}", e)))?;
+
if !base_uri_value.is_empty() {
properties
.try_push(up_data_structs::Property {
@@ -212,7 +219,8 @@
/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
/// @return address Address of the newly created collection
#[weight(<SelfWeightOf<T>>::create_collection())]
- fn create_nonfungible_collection(
+ #[solidity(rename_selector = "createNFTCollection")]
+ fn create_nft_collection(
&mut self,
caller: caller,
value: value,
@@ -239,9 +247,26 @@
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
}
+ /// Create an NFT collection
+ /// @param name Name of the collection
+ /// @param description Informative description of the collection
+ /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+ /// @return address Address of the newly created collection
+ #[weight(<SelfWeightOf<T>>::create_collection())]
+ #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]
+ fn create_nonfungible_collection(
+ &mut self,
+ caller: caller,
+ value: value,
+ name: string,
+ description: string,
+ token_prefix: string,
+ ) -> Result<address> {
+ self.create_nft_collection(caller, value, name, description, token_prefix)
+ }
#[weight(<SelfWeightOf<T>>::create_collection())]
- #[solidity(rename_selector = "createERC721MetadataCompatibleCollection")]
+ #[solidity(rename_selector = "createERC721MetadataNFTCollection")]
fn create_nonfungible_collection_with_properties(
&mut self,
caller: caller,
@@ -273,6 +298,27 @@
#[weight(<SelfWeightOf<T>>::create_collection())]
#[solidity(rename_selector = "createRFTCollection")]
+ fn create_rft_collection(
+ &mut self,
+ caller: caller,
+ value: value,
+ name: string,
+ description: string,
+ token_prefix: string,
+ ) -> Result<address> {
+ create_refungible_collection_internal::<T>(
+ caller,
+ value,
+ name,
+ description,
+ token_prefix,
+ Default::default(),
+ false,
+ )
+ }
+
+ #[weight(<SelfWeightOf<T>>::create_collection())]
+ #[deprecated(note = "mathod was renamed to `create_rft_collection`, prefer it instead")]
fn create_refungible_collection(
&mut self,
caller: caller,
@@ -293,7 +339,7 @@
}
#[weight(<SelfWeightOf<T>>::create_collection())]
- #[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]
+ #[solidity(rename_selector = "createERC721MetadataRFTCollection")]
fn create_refungible_collection_with_properties(
&mut self,
caller: caller,
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -23,13 +23,33 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
+/// @dev the ERC-165 identifier for this interface is 0xf62c7aa9
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
/// @param description Informative description of the collection
/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
/// @return address Address of the newly created collection
+ /// @dev EVM selector for this function is: 0x844af658,
+ /// or in textual repr: createNFTCollection(string,string,string)
+ function createNFTCollection(
+ string memory name,
+ string memory description,
+ string memory tokenPrefix
+ ) public payable returns (address) {
+ require(false, stub_error);
+ name;
+ description;
+ tokenPrefix;
+ dummy = 0;
+ return 0x0000000000000000000000000000000000000000;
+ }
+
+ /// Create an NFT collection
+ /// @param name Name of the collection
+ /// @param description Informative description of the collection
+ /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+ /// @return address Address of the newly created collection
/// @dev EVM selector for this function is: 0xe34a6844,
/// or in textual repr: createNonfungibleCollection(string,string,string)
function createNonfungibleCollection(
@@ -45,9 +65,9 @@
return 0x0000000000000000000000000000000000000000;
}
- /// @dev EVM selector for this function is: 0xa634a5f9,
- /// or in textual repr: createERC721MetadataCompatibleCollection(string,string,string,string)
- function createERC721MetadataCompatibleCollection(
+ /// @dev EVM selector for this function is: 0xd1df968c,
+ /// or in textual repr: createERC721MetadataNFTCollection(string,string,string,string)
+ function createERC721MetadataNFTCollection(
string memory name,
string memory description,
string memory tokenPrefix,
@@ -77,9 +97,24 @@
return 0x0000000000000000000000000000000000000000;
}
- /// @dev EVM selector for this function is: 0xa5596388,
- /// or in textual repr: createERC721MetadataCompatibleRFTCollection(string,string,string,string)
- function createERC721MetadataCompatibleRFTCollection(
+ /// @dev EVM selector for this function is: 0x44a68ad5,
+ /// or in textual repr: createRefungibleCollection(string,string,string)
+ function createRefungibleCollection(
+ string memory name,
+ string memory description,
+ string memory tokenPrefix
+ ) public payable returns (address) {
+ require(false, stub_error);
+ name;
+ description;
+ tokenPrefix;
+ dummy = 0;
+ return 0x0000000000000000000000000000000000000000;
+ }
+
+ /// @dev EVM selector for this function is: 0xbea6a299,
+ /// or in textual repr: createERC721MetadataRFTCollection(string,string,string,string)
+ function createERC721MetadataRFTCollection(
string memory name,
string memory description,
string memory tokenPrefix,
tests/src/deprecated-helpers/eth/helpers.tsdiffbeforeafterboth--- a/tests/src/deprecated-helpers/eth/helpers.ts
+++ b/tests/src/deprecated-helpers/eth/helpers.ts
@@ -150,10 +150,10 @@
}
-export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) {
+export async function createNFTCollection(api: ApiPromise, web3: Web3, owner: string) {
const collectionHelper = evmCollectionHelpers(web3, owner);
const result = await collectionHelper.methods
- .createNonfungibleCollection('A', 'B', 'C')
+ .createNFTCollection('A', 'B', 'C')
.send({value: Number(2n * UNIQUE)});
return await getCollectionAddressFromResult(api, result);
}
tests/src/deprecated-helpers/helpers.tsdiffbeforeafterboth--- a/tests/src/deprecated-helpers/helpers.ts
+++ b/tests/src/deprecated-helpers/helpers.ts
@@ -433,6 +433,7 @@
mode: {type: 'NFT'},
name: 'name',
tokenPrefix: 'prefix',
+ properties: [{key: 'ERC721Metadata', value: '1'}],
};
export async function
@@ -441,7 +442,7 @@
sender: IKeyringPair,
params: Partial<CreateCollectionParams> = {},
): Promise<CreateCollectionResult> {
- const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};
+ const {name, description, mode, tokenPrefix, properties} = {...defaultCreateCollectionParams, ...params};
let modeprm = {};
if (mode.type === 'NFT') {
@@ -457,6 +458,7 @@
description: strToUTF16(description),
tokenPrefix: strToUTF16(tokenPrefix),
mode: modeprm as any,
+ properties,
});
const events = await executeTransaction(api, sender, tx);
return getCreateCollectionResult(events);
tests/src/eth/allowlist.test.tsdiffbeforeafterboth--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -78,7 +78,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const user = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
@@ -94,7 +94,7 @@
// const owner = await helper.eth.createAccountWithBalance(donor);
// const user = donor;
- // const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
// const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
// expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
@@ -110,7 +110,7 @@
const notOwner = await helper.eth.createAccountWithBalance(donor);
const user = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
@@ -129,7 +129,7 @@
// const notOwner = await helper.eth.createAccountWithBalance(donor);
// const user = donor;
- // const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
// const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
// expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -18,13 +18,26 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
+/// @dev the ERC-165 identifier for this interface is 0xf62c7aa9
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
/// @param description Informative description of the collection
/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
/// @return address Address of the newly created collection
+ /// @dev EVM selector for this function is: 0x844af658,
+ /// or in textual repr: createNFTCollection(string,string,string)
+ function createNFTCollection(
+ string memory name,
+ string memory description,
+ string memory tokenPrefix
+ ) external payable returns (address);
+
+ /// Create an NFT collection
+ /// @param name Name of the collection
+ /// @param description Informative description of the collection
+ /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+ /// @return address Address of the newly created collection
/// @dev EVM selector for this function is: 0xe34a6844,
/// or in textual repr: createNonfungibleCollection(string,string,string)
function createNonfungibleCollection(
@@ -33,9 +46,9 @@
string memory tokenPrefix
) external payable returns (address);
- /// @dev EVM selector for this function is: 0xa634a5f9,
- /// or in textual repr: createERC721MetadataCompatibleCollection(string,string,string,string)
- function createERC721MetadataCompatibleCollection(
+ /// @dev EVM selector for this function is: 0xd1df968c,
+ /// or in textual repr: createERC721MetadataNFTCollection(string,string,string,string)
+ function createERC721MetadataNFTCollection(
string memory name,
string memory description,
string memory tokenPrefix,
@@ -50,9 +63,17 @@
string memory tokenPrefix
) external payable returns (address);
- /// @dev EVM selector for this function is: 0xa5596388,
- /// or in textual repr: createERC721MetadataCompatibleRFTCollection(string,string,string,string)
- function createERC721MetadataCompatibleRFTCollection(
+ /// @dev EVM selector for this function is: 0x44a68ad5,
+ /// or in textual repr: createRefungibleCollection(string,string,string)
+ function createRefungibleCollection(
+ string memory name,
+ string memory description,
+ string memory tokenPrefix
+ ) external payable returns (address);
+
+ /// @dev EVM selector for this function is: 0xbea6a299,
+ /// or in textual repr: createERC721MetadataRFTCollection(string,string,string,string)
+ function createERC721MetadataRFTCollection(
string memory name,
string memory description,
string memory tokenPrefix,
tests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -38,7 +38,7 @@
itEth('Add admin by owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const newAdmin = helper.eth.createAccount();
@@ -51,7 +51,7 @@
itEth.skip('Add substrate admin by owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
@@ -64,7 +64,7 @@
itEth('Verify owner or admin', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const newAdmin = helper.eth.createAccount();
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -75,7 +75,7 @@
itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const admin = await helper.eth.createAccountWithBalance(donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -93,7 +93,7 @@
itEth('(!negative tests!) Add admin by USER is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const notAdmin = await helper.eth.createAccountWithBalance(donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -108,7 +108,7 @@
itEth.skip('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const admin = await helper.eth.createAccountWithBalance(donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -126,7 +126,7 @@
itEth.skip('(!negative tests!) Add substrate admin by USER is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const notAdmin0 = await helper.eth.createAccountWithBalance(donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -150,7 +150,7 @@
itEth('Remove admin by owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const newAdmin = helper.eth.createAccount();
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -170,7 +170,7 @@
itEth.skip('Remove substrate admin by owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -188,7 +188,7 @@
itEth('(!negative tests!) Remove admin by ADMIN is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -210,7 +210,7 @@
itEth('(!negative tests!) Remove admin by USER is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -230,7 +230,7 @@
itEth.skip('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const [adminSub] = await helper.arrange.createAccounts([10n], donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -250,7 +250,7 @@
itEth.skip('(!negative tests!) Remove substrate admin by USER is not allowed', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const [adminSub] = await helper.arrange.createAccounts([10n], donor);
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -279,7 +279,7 @@
itEth('Change owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const newOwner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await collectionEvm.methods.setOwner(newOwner).send();
@@ -291,7 +291,7 @@
itEth('change owner call fee', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const newOwner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwner(newOwner).send());
expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
@@ -301,7 +301,7 @@
itEth('(!negative tests!) call setOwner by non owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const newOwner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await expect(collectionEvm.methods.setOwner(newOwner).send({from: newOwner})).to.be.rejected;
@@ -321,7 +321,7 @@
itEth.skip('Change owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const [newOwner] = await helper.arrange.createAccounts([10n], donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.true;
@@ -336,7 +336,7 @@
itEth.skip('change owner call fee', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const [newOwner] = await helper.arrange.createAccounts([10n], donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send());
@@ -348,7 +348,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const otherReceiver = await helper.eth.createAccountWithBalance(donor);
const [newOwner] = await helper.arrange.createAccounts([10n], donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await expect(collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send({from: otherReceiver})).to.be.rejected;
tests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -32,7 +32,7 @@
{ "internalType": "string", "name": "tokenPrefix", "type": "string" },
{ "internalType": "string", "name": "baseUri", "type": "string" }
],
- "name": "createERC721MetadataCompatibleCollection",
+ "name": "createERC721MetadataNFTCollection",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "payable",
"type": "function"
@@ -44,7 +44,18 @@
{ "internalType": "string", "name": "tokenPrefix", "type": "string" },
{ "internalType": "string", "name": "baseUri", "type": "string" }
],
- "name": "createERC721MetadataCompatibleRFTCollection",
+ "name": "createERC721MetadataRFTCollection",
+ "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name", "type": "string" },
+ { "internalType": "string", "name": "description", "type": "string" },
+ { "internalType": "string", "name": "tokenPrefix", "type": "string" }
+ ],
+ "name": "createNFTCollection",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "payable",
"type": "function"
@@ -73,6 +84,17 @@
},
{
"inputs": [
+ { "internalType": "string", "name": "name", "type": "string" },
+ { "internalType": "string", "name": "description", "type": "string" },
+ { "internalType": "string", "name": "tokenPrefix", "type": "string" }
+ ],
+ "name": "createRefungibleCollection",
+ "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{
"internalType": "address",
"name": "collectionAddress",
tests/src/eth/collectionProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -24,7 +24,7 @@
const raw = (await collection.getData())?.raw;
- expect(raw.properties[0].value).to.equal('testValue');
+ expect(raw.properties[1].value).to.equal('testValue');
});
itEth('Can be deleted', async({helper}) => {
@@ -54,3 +54,46 @@
expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));
});
});
+
+describe('Supports ERC721Metadata', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (_helper, privateKey) => {
+ donor = privateKey('//Alice');
+ });
+ });
+
+ itEth('ERC721Metadata property can be set for NFT collection', async({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const collection = await helper.nft.mintCollection(donor, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+
+ await collection.addAdmin(donor, {Ethereum: caller});
+ const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
+
+ await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('1')).send({from: caller});
+
+ expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.true;
+
+ await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('0')).send({from: caller});
+
+ expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.false;
+ });
+
+ itEth('ERC721Metadata property can be set for RFT collection', async({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const collection = await helper.rft.mintCollection(donor, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+
+ await collection.addAdmin(donor, {Ethereum: caller});
+
+ const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
+
+ await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('1')).send({from: caller});
+
+ expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.true;
+
+ await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('0')).send({from: caller});
+
+ expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.false;
+ });
+});
tests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -54,7 +54,7 @@
// itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => {
// const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
// const collectionHelpers = evmCollectionHelpers(web3, owner);
- // let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+ // let result = await collectionHelpers.methods.createNFTCollection('Sponsor collection', '1', '1').send();
// const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
// const sponsor = privateKeyWrapper('//Alice');
// const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
@@ -75,7 +75,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
- let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+ let result = await collectionHelpers.methods.createNFTCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const sponsor = await helper.eth.createAccountWithBalance(donor);
const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', owner);
@@ -97,7 +97,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
- let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+ let result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send({value: Number(2n * nominal)});
const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress);
const collection = helper.nft.getCollectionObject(collectionId);
@@ -167,7 +167,7 @@
// itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => {
// const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
// const collectionHelpers = evmCollectionHelpers(web3, owner);
- // const result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+ // const result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send();
// const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
// const sponsor = privateKeyWrapper('//Alice');
// const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
@@ -223,7 +223,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
- let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+ let result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send({value: Number(2n * nominal)});
const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress);
const collection = helper.nft.getCollectionObject(collectionId);
tests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -37,7 +37,7 @@
// todo:playgrounds this might fail when in async environment.
const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
- const {collectionId} = await helper.eth.createNonfungibleCollection(owner, name, description, prefix);
+ const {collectionId} = await helper.eth.createNFTCollection(owner, name, description, prefix);
const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
const collection = helper.nft.getCollectionObject(collectionId);
@@ -64,7 +64,7 @@
.call()).to.be.false;
await collectionHelpers.methods
- .createNonfungibleCollection('A', 'A', 'A')
+ .createNFTCollection('A', 'A', 'A')
.send({value: Number(2n * helper.balance.getOneTokenNominal())});
expect(await collectionHelpers.methods
@@ -76,7 +76,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const sponsor = await helper.eth.createAccountWithBalance(donor);
const ss58Format = helper.chain.getChainProperties().ss58Format;
- const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
+ const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await collection.methods.setCollectionSponsor(sponsor).send();
@@ -95,7 +95,7 @@
itEth('Set limits', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'FLO');
+ const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'FLO');
const limits = {
accountTokenOwnershipLimit: 1000,
sponsoredDataSize: 1024,
@@ -138,7 +138,7 @@
.methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
.to.be.false;
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Exister', 'absolutely anything', 'EVC');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Exister', 'absolutely anything', 'EVC');
expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
.methods.isCollectionExist(collectionAddress).call())
.to.be.true;
@@ -166,7 +166,7 @@
const tokenPrefix = 'A';
await expect(collectionHelper.methods
- .createNonfungibleCollection(collectionName, description, tokenPrefix)
+ .createNFTCollection(collectionName, description, tokenPrefix)
.call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);
}
@@ -176,7 +176,7 @@
const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);
const tokenPrefix = 'A';
await expect(collectionHelper.methods
- .createNonfungibleCollection(collectionName, description, tokenPrefix)
+ .createNFTCollection(collectionName, description, tokenPrefix)
.call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);
}
{
@@ -185,7 +185,7 @@
const description = 'A';
const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);
await expect(collectionHelper.methods
- .createNonfungibleCollection(collectionName, description, tokenPrefix)
+ .createNFTCollection(collectionName, description, tokenPrefix)
.call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);
}
});
@@ -194,14 +194,14 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
await expect(collectionHelper.methods
- .createNonfungibleCollection('Peasantry', 'absolutely anything', 'CVE')
+ .createNFTCollection('Peasantry', 'absolutely anything', 'CVE')
.call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
});
itEth('(!negative test!) Check owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const malfeasant = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
const malfeasantCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', malfeasant);
const EXPECTED_ERROR = 'NoPermission';
{
@@ -224,7 +224,7 @@
itEth('(!negative test!) Set limits', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'OLF');
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await expect(collectionEvm.methods
.setCollectionLimit('badLimit', 'true')
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -39,7 +39,7 @@
// todo:playgrounds this might fail when in async environment.
const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
- const {collectionId} = await helper.eth.createRefungibleCollection(owner, name, description, prefix);
+ const {collectionId} = await helper.eth.createRFTCollection(owner, name, description, prefix);
const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
const data = (await helper.rft.getData(collectionId))!;
@@ -77,7 +77,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const sponsor = await helper.eth.createAccountWithBalance(donor);
const ss58Format = helper.chain.getChainProperties().ss58Format;
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
await collection.methods.setCollectionSponsor(sponsor).send();
@@ -96,7 +96,7 @@
itEth('Set limits', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'INSI');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'INSI');
const limits = {
accountTokenOwnershipLimit: 1000,
sponsoredDataSize: 1024,
@@ -139,7 +139,7 @@
.methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
.to.be.false;
- const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Exister', 'absolutely anything', 'WIWT');
+ const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Exister', 'absolutely anything', 'WIWT');
expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
.methods.isCollectionExist(collectionAddress).call())
.to.be.true;
@@ -202,7 +202,7 @@
itEth('(!negative test!) Check owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const peasant = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
+ const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', peasant);
const EXPECTED_ERROR = 'NoPermission';
{
@@ -225,7 +225,7 @@
itEth('(!negative test!) Set limits', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'ISNI');
+ const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'ISNI');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
await expect(collectionEvm.methods
.setCollectionLimit('badLimit', 'true')
tests/src/eth/evmCoder.test.tsdiffbeforeafterboth--- a/tests/src/eth/evmCoder.test.ts
+++ b/tests/src/eth/evmCoder.test.ts
@@ -65,7 +65,7 @@
itEth('Call non-existing function', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.eth.createNonfungibleCollection(owner, 'EVMCODER', '', 'TEST');
+ const collection = await helper.eth.createNFTCollection(owner, 'EVMCODER', '', 'TEST');
const contract = await helper.ethContract.deployByCode(owner, 'Test', getContractSource(collection.collectionAddress, '0x1bfed5D614b886b9Ab2eA4CBAc22A96B7EC29c9c'));
const testContract = await helper.ethContract.deployByCode(owner, 'Test', getContractSource(collection.collectionAddress, contract.options.address));
{
tests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -62,7 +62,7 @@
const mintRFTToken = async (helper: EthUniqueHelper, owner: string, fractionalizer: Contract, amount: bigint): Promise<{
nftCollectionAddress: string, nftTokenId: number, rftTokenAddress: string
}> => {
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -92,7 +92,7 @@
itEth('Set RFT collection', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 10n);
const fractionalizer = await deployContract(helper, owner);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
const rftContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
await rftContract.methods.addCollectionAdmin(fractionalizer.options.address).send({from: owner});
@@ -121,7 +121,7 @@
itEth('Set Allowlist', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
const {contract: fractionalizer} = await initContract(helper, owner);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const result1 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});
expect(result1.events).to.be.like({
@@ -146,7 +146,7 @@
itEth('NFT to RFT', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -231,7 +231,7 @@
itEth('call setRFTCollection twice', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
const fractionalizer = await deployContract(helper, owner);
@@ -244,7 +244,7 @@
itEth('call setRFTCollection with NFT collection', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const fractionalizer = await deployContract(helper, owner);
@@ -257,7 +257,7 @@
itEth('call setRFTCollection while not collection admin', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
const fractionalizer = await deployContract(helper, owner);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
await expect(fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).call())
.to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);
@@ -278,7 +278,7 @@
itEth('call nft2rft without setting RFT collection for contract', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -293,7 +293,7 @@
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
const nftOwner = await helper.eth.createAccountWithBalance(donor, 10n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -310,7 +310,7 @@
itEth('call nft2rft while not in list of allowed accounts', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -325,7 +325,7 @@
itEth('call nft2rft while fractionalizer doesnt have approval for nft token', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -341,7 +341,7 @@
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
const fractionalizer = await deployContract(helper, owner);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
const rftTokenId = await refungibleContract.methods.nextTokenId().call();
await refungibleContract.methods.mint(owner, rftTokenId).send({from: owner});
@@ -354,7 +354,7 @@
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
const {contract: fractionalizer} = await initContract(helper, owner);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
const rftTokenId = await refungibleContract.methods.nextTokenId().call();
await refungibleContract.methods.mint(owner, rftTokenId).send({from: owner});
@@ -365,7 +365,7 @@
itEth('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor, 20n);
- const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+ const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
const fractionalizer = await deployContract(helper, owner);
@@ -432,7 +432,7 @@
await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send({from: owner});
await helper.executeExtrinsic(donor, 'api.tx.unique.setTransfersEnabledFlag', [rftCollection.collectionId, false], true);
- const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+ const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
const nftTokenId = await nftContract.methods.nextTokenId().call();
await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -7,7 +7,7 @@
helper: EthUniqueHelper,
owner: string,
): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {
- const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+ const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
await contract.methods.setCollectionNesting(true).send({from: owner});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -84,7 +84,7 @@
const receiver = helper.eth.createAccount();
const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
- let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
+ let result = await collectionHelper.methods.createERC721MetadataNFTCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -146,7 +146,7 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Minty', '6', '6');
+ const {collectionAddress} = await helper.eth.createERC721MetadataNFTCollection(owner, 'Mint collection', '6', '6', '');
const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const nextTokenId = await contract.methods.nextTokenId().call();
tests/src/eth/payable.test.tsdiffbeforeafterboth--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -146,7 +146,7 @@
const caller = await helper.eth.createAccountWithBalance(donor);
const contract = await deployProxyContract(helper, deployer);
- const collectionAddress = (await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;
+ const collectionAddress = (await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;
const initialCallerBalance = await helper.balance.getEthereum(caller);
const initialContractBalance = await helper.balance.getEthereum(contract.options.address);
await contract.methods.mintNftToken(collectionAddress).send({from: caller});
@@ -164,7 +164,7 @@
const initialCallerBalance = await helper.balance.getEthereum(caller);
const initialContractBalance = await helper.balance.getEthereum(contract.options.address);
- await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});
+ await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});
const finalCallerBalance = await helper.balance.getEthereum(caller);
const finalContractBalance = await helper.balance.getEthereum(contract.options.address);
expect(finalCallerBalance < initialCallerBalance).to.be.true;
@@ -177,8 +177,8 @@
const caller = await helper.eth.createAccountWithBalance(donor);
const collectionHelper = helper.ethNativeContract.collectionHelpers(caller);
- await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
- await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
+ await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
+ await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
});
itEth('Negative test: call createRFTCollection with wrong fee', async({helper}) => {
@@ -227,9 +227,9 @@
InnerContract(innerContract).flip();
}
- function createNonfungibleCollection() external payable {
+ function createNFTCollection() external payable {
address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
- address nftCollection = CollectionHelpers(collectionHelpers).createNonfungibleCollection{value: msg.value}("A", "B", "C");
+ address nftCollection = CollectionHelpers(collectionHelpers).createNFTCollection{value: msg.value}("A", "B", "C");
emit CollectionCreated(nftCollection);
}
tests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -101,7 +101,7 @@
itEth('Can perform mint()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'A', 'A');
+ const {collectionAddress} = await helper.eth.createERC721MetadataNFTCollection(owner, 'A', 'A', 'A', '');
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -31,7 +31,7 @@
itEth('totalSupply', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TotalSupply', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const nextTokenId = await contract.methods.nextTokenId().call();
await contract.methods.mint(caller, nextTokenId).send();
@@ -41,7 +41,7 @@
itEth('balanceOf', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'BalanceOf', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
{
@@ -63,7 +63,7 @@
itEth('ownerOf', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -76,7 +76,7 @@
itEth('ownerOf after burn', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf-AfterBurn', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -95,7 +95,7 @@
itEth('ownerOf for partial ownership', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Partial-OwnerOf', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -124,7 +124,7 @@
itEth('Can perform mint()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Minty', '6', '6');
+ const {collectionAddress} = await helper.eth.createERC721MetadataRFTCollection(owner, 'Minty', '6', '6', '');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
const nextTokenId = await contract.methods.nextTokenId().call();
@@ -147,7 +147,7 @@
itEth('Can perform mintBulk()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'MintBulky', '6', '6');
+ const {collectionAddress} = await helper.eth.createERC721MetadataRFTCollection(owner, 'MintBulky', '6', '6', '');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
{
@@ -179,7 +179,7 @@
itEth('Can perform burn()', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Burny', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -197,7 +197,7 @@
itEth('Can perform transferFrom()', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TransferFromy', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -241,7 +241,7 @@
itEth('Can perform transfer()', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -271,7 +271,7 @@
itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Partial-to-Full', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -298,7 +298,7 @@
itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Full-to-Partial', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -336,7 +336,7 @@
itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer-From', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -350,7 +350,7 @@
itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer', '6', '6');
+ const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -386,8 +386,8 @@
itEth('Returns symbol name', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
- const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Leviathan', '', '12');
- const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+ const {collectionId} = await helper.rft.mintCollection(alice, {name: 'Leviathan', tokenPrefix: '12'});
+ const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);
const symbol = await contract.methods.symbol().call();
expect(symbol).to.equal('12');
});
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -81,7 +81,7 @@
const receiver = helper.eth.createAccount();
const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
- let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
+ let result = await collectionHelper.methods.createERC721MetadataNFTCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
@@ -294,7 +294,7 @@
itEth('Receiving Transfer event on burning into full ownership', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = await helper.eth.createAccountWithBalance(donor);
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Devastation', '6', '6');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Devastation', '6', '6');
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
const tokenId = await contract.methods.nextTokenId().call();
@@ -479,7 +479,7 @@
itEth('Default parent token address and id', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
- const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sands', '', 'GRAIN');
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sands', '', 'GRAIN');
const collectionContract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
const tokenId = await collectionContract.methods.nextTokenId().call();
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -174,11 +174,23 @@
return await this.helper.callRpc('api.rpc.eth.call', [{from: signer, to: contractAddress, data: abi}]);
}
- async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+ async createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
- const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send({value: Number(collectionCreationPrice)});
+ const result = await collectionHelper.methods.createNFTCollection(name, description, tokenPrefix).send({value: Number(collectionCreationPrice)});
+
+ const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+ const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+ return {collectionId, collectionAddress};
+ }
+
+ async createERC721MetadataNFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{collectionId: number, collectionAddress: string}> {
+ const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
+ const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+
+ const result = await collectionHelper.methods.createERC721MetadataNFTCollection(name, description, tokenPrefix, baseUri).send({value: Number(collectionCreationPrice)});
const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
@@ -186,7 +198,7 @@
return {collectionId, collectionAddress};
}
- async createRefungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+ async createRFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
@@ -198,6 +210,18 @@
return {collectionId, collectionAddress};
}
+ async createERC721MetadataRFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{collectionId: number, collectionAddress: string}> {
+ const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
+ const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+
+ const result = await collectionHelper.methods.createERC721MetadataRFTCollection(name, description, tokenPrefix, baseUri).send({value: Number(collectionCreationPrice)});
+
+ const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+ const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+ return {collectionId, collectionAddress};
+ }
+
async deployCollectorContract(signer: string): Promise<Contract> {
return await this.helper.ethContract.deployByCode(signer, 'Collector', `
// SPDX-License-Identifier: UNLICENSED
tests/src/nesting/properties.test.tsdiffbeforeafterboth--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -18,6 +18,52 @@
import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';
import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique';
+
+describe('Composite Properties Test', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = privateKey('//Alice');
+ [alice] = await helper.arrange.createAccounts([50n], donor);
+ });
+ });
+
+ async function testMakeSureSuppliesRequired(baseCollection: UniqueNFTCollection | UniqueRFTCollection) {
+
+ const collectionOption = await baseCollection.getOptions();
+ expect(collectionOption).is.not.null;
+ let collection = collectionOption;
+ expect(collection.tokenPropertyPermissions).to.be.empty;
+ expect(collection.properties).to.be.deep.equal([{key: 'ERC721Metadata', value: '1'}]);
+
+ const propertyPermissions = [
+ {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},
+ {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},
+ ];
+ await expect(await baseCollection.setTokenPropertyPermissions(alice, propertyPermissions)).to.be.true;
+
+ const collectionProperties = [
+ {key: 'ERC721Metadata', value: '1'},
+ {key: 'black_hole', value: 'LIGO'},
+ {key: 'electron', value: 'come bond'},
+ ];
+
+ await expect(await baseCollection.setProperties(alice, collectionProperties)).to.be.true;
+
+ collection = await baseCollection.getOptions();
+ expect(collection.tokenPropertyPermissions).to.be.deep.equal(propertyPermissions);
+ expect(collection.properties).to.be.deep.equal(collectionProperties);
+ }
+
+ itSub('Makes sure collectionById supplies required fields for NFT', async ({helper}) => {
+ await testMakeSureSuppliesRequired(await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));
+ });
+
+ itSub.ifWithPallets('Makes sure collectionById supplies required fields for ReFungible', [Pallets.ReFungible], async ({helper}) => {
+ await testMakeSureSuppliesRequired(await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));
+ });
+});
// ---------- COLLECTION PROPERTIES
describe('Integration Test: Collection Properties', () => {
@@ -33,7 +79,11 @@
itSub('Properties are initially empty', async ({helper}) => {
const collection = await helper.nft.mintCollection(alice);
- expect(await collection.getProperties()).to.be.empty;
+ const properties = await collection.getProperties();
+ expect(properties).to.be.deep.equal([{
+ 'key': 'ERC721Metadata',
+ 'value': '1',
+ }]);
});
async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {
@@ -150,7 +200,11 @@
await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))
.to.be.rejectedWith(/common\.NoPermission/);
- expect(await collection.getProperties()).to.be.empty;
+ const properties = await collection.getProperties();
+ expect(properties).to.be.deep.equal([{
+ 'key': 'ERC721Metadata',
+ 'value': '1',
+ }]);
}
itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {
@@ -202,7 +256,11 @@
await expect(collection.setProperties(alice, propertiesToBeSet)).
to.be.rejectedWith(/common\.PropertyLimitReached/);
- expect(await collection.getProperties()).to.be.empty;
+ const properties = await collection.getProperties();
+ expect(properties).to.be.deep.equal([{
+ 'key': 'ERC721Metadata',
+ 'value': '1',
+ }]);
}
itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -981,6 +981,10 @@
return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();
}
+ async getCollectionOptions(collectionId: number) {
+ return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();
+ }
+
/**
* Deletes onchain properties from the collection.
*
@@ -1293,6 +1297,7 @@
async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {
collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};
+ collectionOptions.properties = collectionOptions.properties || [{key: 'ERC721Metadata', value: '1'}];
for (const key of ['name', 'description', 'tokenPrefix']) {
if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);
}
@@ -2476,6 +2481,10 @@
return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
}
+ async getOptions() {
+ return await this.helper.collection.getCollectionOptions(this.collectionId);
+ }
+
async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {
return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);
}