git.delta.rocks / unique-network / refs/commits / 3750ef0fdbd1

difftreelog

Merge pull request #422 from UniqueNetwork/feature/CORE-410

Yaroslav Bolyukin2022-07-21parents: #9f5f42d #14bb047.patch.diff
in: master
Feature/core 410 Properties for RFT

21 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6320,7 +6320,7 @@
 
 [[package]]
 name = "pallet-refungible"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "frame-benchmarking",
  "frame-support",
@@ -12733,7 +12733,7 @@
 
 [[package]]
 name = "up-data-structs"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "derivative",
  "frame-support",
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -966,7 +966,7 @@
 				),
 			);
 			<T as Config>::Currency::settle(
-				&owner.as_sub(),
+				owner.as_sub(),
 				imbalance,
 				WithdrawReasons::TRANSFER,
 				ExistenceRequirement::KeepAlive,
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -183,7 +183,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props, false, &Unlimited)?}
+	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?}
 
 	delete_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
@@ -205,7 +205,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-		<Pallet<T>>::set_token_properties(&collection, &owner, item, props, false, &Unlimited)?;
+		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
 		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
-	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete, &Unlimited)?}
+	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
 }
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_nonfungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-06-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -56,7 +56,7 @@
 	// Storage: Nonfungible TokenData (r:0 w:1)
 	// Storage: Nonfungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(24_135_000 as Weight)
+		(20_328_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -65,9 +65,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(21_952_000 as Weight)
-			// Standard Error: 5_000
-			.saturating_add((4_727_000 as Weight).saturating_mul(b as Weight))
+		(10_134_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -77,9 +77,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items_ex(b: u32, ) -> Weight {
-		(10_432_000 as Weight)
-			// Standard Error: 6_000
-			.saturating_add((7_383_000 as Weight).saturating_mul(b as Weight))
+		(5_710_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -93,7 +93,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_item() -> Weight {
-		(29_798_000 as Weight)
+		(28_433_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -105,7 +105,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_recursively_self_raw() -> Weight {
-		(37_955_000 as Weight)
+		(34_435_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -119,8 +119,8 @@
 	// Storage: Common CollectionById (r:1 w:0)
 	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_349_000
-			.saturating_add((275_145_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_539_000
+			.saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(7 as Weight))
 			.saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
@@ -131,14 +131,14 @@
 	// Storage: Nonfungible Allowance (r:1 w:0)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer() -> Weight {
-		(27_867_000 as Weight)
+		(24_376_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible Allowance (r:1 w:1)
 	fn approve() -> Weight {
-		(18_824_000 as Weight)
+		(15_890_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -147,7 +147,7 @@
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer_from() -> Weight {
-		(32_879_000 as Weight)
+		(28_634_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
@@ -159,7 +159,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(37_061_000 as Weight)
+		(32_201_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
@@ -167,28 +167,26 @@
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
 			// Standard Error: 57_000
-			.saturating_add((15_149_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
-	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 2_278_000
-			.saturating_add((409_613_000 as Weight).saturating_mul(b as Weight))
-			.saturating_add(T::DbWeight::get().reads(3 as Weight))
+			// Standard Error: 1_648_000
+			.saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
-	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 2_234_000
-			.saturating_add((408_185_000 as Weight).saturating_mul(b as Weight))
-			.saturating_add(T::DbWeight::get().reads(3 as Weight))
+			// Standard Error: 1_632_000
+			.saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 }
@@ -200,7 +198,7 @@
 	// Storage: Nonfungible TokenData (r:0 w:1)
 	// Storage: Nonfungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(24_135_000 as Weight)
+		(20_328_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -209,9 +207,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(21_952_000 as Weight)
-			// Standard Error: 5_000
-			.saturating_add((4_727_000 as Weight).saturating_mul(b as Weight))
+		(10_134_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -221,9 +219,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items_ex(b: u32, ) -> Weight {
-		(10_432_000 as Weight)
-			// Standard Error: 6_000
-			.saturating_add((7_383_000 as Weight).saturating_mul(b as Weight))
+		(5_710_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -237,7 +235,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_item() -> Weight {
-		(29_798_000 as Weight)
+		(28_433_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -249,7 +247,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_recursively_self_raw() -> Weight {
-		(37_955_000 as Weight)
+		(34_435_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -263,8 +261,8 @@
 	// Storage: Common CollectionById (r:1 w:0)
 	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_349_000
-			.saturating_add((275_145_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_539_000
+			.saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(7 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
@@ -275,14 +273,14 @@
 	// Storage: Nonfungible Allowance (r:1 w:0)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer() -> Weight {
-		(27_867_000 as Weight)
+		(24_376_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible Allowance (r:1 w:1)
 	fn approve() -> Weight {
-		(18_824_000 as Weight)
+		(15_890_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -291,7 +289,7 @@
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer_from() -> Weight {
-		(32_879_000 as Weight)
+		(28_634_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
@@ -303,7 +301,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(37_061_000 as Weight)
+		(32_201_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
@@ -311,28 +309,26 @@
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
 			// Standard Error: 57_000
-			.saturating_add((15_149_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
-	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 2_278_000
-			.saturating_add((409_613_000 as Weight).saturating_mul(b as Weight))
-			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
+			// Standard Error: 1_648_000
+			.saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
-	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 2_234_000
-			.saturating_add((408_185_000 as Weight).saturating_mul(b as Weight))
-			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
+			// Standard Error: 1_632_000
+			.saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 }
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-refungible"
-version = "0.1.0"
+version = "0.1.1"
 license = "GPLv3"
 edition = "2021"
 
addedpallets/refungible/Changelog.mddiffbeforeafterboth
--- /dev/null
+++ b/pallets/refungible/Changelog.md
@@ -0,0 +1,3 @@
+### 0.1.1
+---
+* Added support for properties for RFT collections and tokens.
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -18,9 +18,12 @@
 use crate::{Pallet, Config, RefungibleHandle};
 
 use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data};
+use pallet_common::benchmarking::{create_collection_raw, property_key, property_value, create_data};
 use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, CUSTOM_DATA_LIMIT, budget::Unlimited};
+use up_data_structs::{
+	CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT,
+	budget::Unlimited,
+};
 use pallet_common::bench_init;
 use core::convert::TryInto;
 use core::iter::IntoIterator;
@@ -38,6 +41,7 @@
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
 			.unwrap(),
+		properties: Default::default(),
 	}
 }
 fn create_max_item<T: Config>(
@@ -204,6 +208,68 @@
 		<Pallet<T>>::set_allowance(&collection, &sender, &burner, item, 200)?;
 	}: {<Pallet<T>>::burn_from(&collection, &burner, &sender, item, 200, &Unlimited)?}
 
+	set_token_property_permissions {
+		let b in 0..MAX_PROPERTIES_PER_ITEM;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			owner: cross_from_sub;
+		};
+		let perms = (0..b).map(|k| PropertyKeyPermission {
+			key: property_key(k as usize),
+			permission: PropertyPermission {
+				mutable: false,
+				collection_admin: false,
+				token_owner: false,
+			},
+		}).collect::<Vec<_>>();
+	}: {<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?}
+
+	set_token_properties {
+		let b in 0..MAX_PROPERTIES_PER_ITEM;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			owner: cross_from_sub;
+		};
+		let perms = (0..b).map(|k| PropertyKeyPermission {
+			key: property_key(k as usize),
+			permission: PropertyPermission {
+				mutable: false,
+				collection_admin: true,
+				token_owner: true,
+			},
+		}).collect::<Vec<_>>();
+		<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+		let props = (0..b).map(|k| Property {
+			key: property_key(k as usize),
+			value: property_value(),
+		}).collect::<Vec<_>>();
+		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?}
+
+	delete_token_properties {
+		let b in 0..MAX_PROPERTIES_PER_ITEM;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			owner: cross_from_sub;
+		};
+		let perms = (0..b).map(|k| PropertyKeyPermission {
+			key: property_key(k as usize),
+			permission: PropertyPermission {
+				mutable: true,
+				collection_admin: true,
+				token_owner: true,
+			},
+		}).collect::<Vec<_>>();
+		<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+		let props = (0..b).map(|k| Property {
+			key: property_key(k as usize),
+			value: property_value(),
+		}).collect::<Vec<_>>();
+		let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
+		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
+	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
+
 	repartition_item {
 		bench_init!{
 			owner: sub; collection: collection(owner);
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -20,9 +20,12 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
 use up_data_structs::{
 	CollectionId, TokenId, CreateItemExData, CreateRefungibleExData, budget::Budget, Property,
-	PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData,
+	PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData, CollectionPropertiesVec,
 };
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight};
+use pallet_common::{
+	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
+	weights::WeightInfo as _,
+};
 use pallet_structure::Error as StructureError;
 use sp_runtime::{DispatchError};
 use sp_std::{vec::Vec, vec};
@@ -41,6 +44,14 @@
 	};
 }
 
+fn properties_weight<T: Config>(properties: &CollectionPropertiesVec) -> u64 {
+	if properties.len() > 0 {
+		<CommonWeights<T>>::set_token_properties(properties.len() as u32)
+	} else {
+		0
+	}
+}
+
 pub struct CommonWeights<T: Config>(PhantomData<T>);
 impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_item() -> Weight {
@@ -48,16 +59,31 @@
 	}
 
 	fn create_multiple_items(data: &[CreateItemData]) -> Weight {
-		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32)
+		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
+			data.iter()
+				.map(|data| match data {
+					CreateItemData::ReFungible(rft_data) => {
+						properties_weight::<T>(&rft_data.properties)
+					}
+					_ => 0,
+				})
+				.fold(0, |a, b| a.saturating_add(b)),
+		)
 	}
 
 	fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {
 		match call {
 			CreateItemExData::RefungibleMultipleOwners(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
+					.saturating_add(properties_weight::<T>(&i.properties))
 			}
 			CreateItemExData::RefungibleMultipleItems(i) => {
 				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
+					.saturating_add(
+						i.iter()
+							.map(|d| properties_weight::<T>(&d.properties))
+							.fold(0, |a, b| a.saturating_add(b)),
+					)
 			}
 			_ => 0,
 		}
@@ -67,29 +93,24 @@
 		max_weight_of!(burn_item_partial(), burn_item_fully())
 	}
 
-	fn set_collection_properties(_amount: u32) -> Weight {
-		// Error
-		0
+	fn set_collection_properties(amount: u32) -> Weight {
+		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
 	}
 
-	fn delete_collection_properties(_amount: u32) -> Weight {
-		// Error
-		0
+	fn delete_collection_properties(amount: u32) -> Weight {
+		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
 	}
 
-	fn set_token_properties(_amount: u32) -> Weight {
-		// Error
-		0
+	fn set_token_properties(amount: u32) -> Weight {
+		<SelfWeightOf<T>>::set_token_properties(amount)
 	}
 
-	fn delete_token_properties(_amount: u32) -> Weight {
-		// Error
-		0
+	fn delete_token_properties(amount: u32) -> Weight {
+		<SelfWeightOf<T>>::delete_token_properties(amount)
 	}
 
-	fn set_token_property_permissions(_amount: u32) -> Weight {
-		// Error
-		0
+	fn set_token_property_permissions(amount: u32) -> Weight {
+		<SelfWeightOf<T>>::set_token_property_permissions(amount)
 	}
 
 	fn transfer() -> Weight {
@@ -140,6 +161,7 @@
 				out.insert(to.clone(), data.pieces);
 				out.try_into().expect("limit > 0")
 			},
+			properties: data.properties,
 		}),
 		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),
 	}
@@ -297,46 +319,85 @@
 
 	fn set_collection_properties(
 		&self,
-		_sender: T::CrossAccountId,
-		_property: Vec<Property>,
+		sender: T::CrossAccountId,
+		properties: Vec<Property>,
 	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::SettingPropertiesNotAllowed)
+		let weight = <CommonWeights<T>>::set_collection_properties(properties.len() as u32);
+
+		with_weight(
+			<Pallet<T>>::set_collection_properties(self, &sender, properties),
+			weight,
+		)
 	}
 
 	fn delete_collection_properties(
 		&self,
-		_sender: &T::CrossAccountId,
-		_property_keys: Vec<PropertyKey>,
+		sender: &T::CrossAccountId,
+		property_keys: Vec<PropertyKey>,
 	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::SettingPropertiesNotAllowed)
+		let weight = <CommonWeights<T>>::delete_collection_properties(property_keys.len() as u32);
+
+		with_weight(
+			<Pallet<T>>::delete_collection_properties(self, sender, property_keys),
+			weight,
+		)
 	}
 
 	fn set_token_properties(
 		&self,
-		_sender: T::CrossAccountId,
-		_token_id: TokenId,
-		_property: Vec<Property>,
-		_nesting_budget: &dyn Budget,
+		sender: T::CrossAccountId,
+		token_id: TokenId,
+		properties: Vec<Property>,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::SettingPropertiesNotAllowed)
+		let weight = <CommonWeights<T>>::set_token_properties(properties.len() as u32);
+
+		with_weight(
+			<Pallet<T>>::set_token_properties(
+				self,
+				&sender,
+				token_id,
+				properties.into_iter(),
+				false,
+				nesting_budget,
+			),
+			weight,
+		)
 	}
 
 	fn set_token_property_permissions(
 		&self,
-		_sender: &T::CrossAccountId,
-		_property_permissions: Vec<PropertyKeyPermission>,
+		sender: &T::CrossAccountId,
+		property_permissions: Vec<PropertyKeyPermission>,
 	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::SettingPropertiesNotAllowed)
+		let weight =
+			<CommonWeights<T>>::set_token_property_permissions(property_permissions.len() as u32);
+
+		with_weight(
+			<Pallet<T>>::set_token_property_permissions(self, sender, property_permissions),
+			weight,
+		)
 	}
 
 	fn delete_token_properties(
 		&self,
-		_sender: T::CrossAccountId,
-		_token_id: TokenId,
-		_property_keys: Vec<PropertyKey>,
-		_nesting_budget: &dyn Budget,
+		sender: T::CrossAccountId,
+		token_id: TokenId,
+		property_keys: Vec<PropertyKey>,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
-		fail!(<Error<T>>::SettingPropertiesNotAllowed)
+		let weight = <CommonWeights<T>>::delete_token_properties(property_keys.len() as u32);
+
+		with_weight(
+			<Pallet<T>>::delete_token_properties(
+				self,
+				&sender,
+				token_id,
+				property_keys.into_iter(),
+				nesting_budget,
+			),
+			weight,
+		)
 	}
 
 	fn check_nesting(
@@ -373,8 +434,8 @@
 		TokenId(<TokensMinted<T>>::get(self.id))
 	}
 
-	fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
-		None
+	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
+		<Pallet<T>>::token_owner(self.id, token)
 	}
 
 	fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -87,15 +87,20 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use frame_support::{ensure, BoundedVec};
+use frame_support::{ensure, fail, BoundedVec, transactional, storage::with_transaction};
 use up_data_structs::{
 	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
 	CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,
+	Property, PropertyScope, TrySetProperty, PropertyKey, PropertyValue, PropertyPermission,
+	PropertyKeyPermission,
 };
 use pallet_evm::account::CrossAccountId;
-use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
+use pallet_common::{
+	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
+	CommonCollectionOperations as _,
+};
 use pallet_structure::Pallet as PalletStructure;
-use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
 use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
 use core::ops::Deref;
 use codec::{Encode, Decode, MaxEncodedLen};
@@ -175,6 +180,15 @@
 		QueryKind = ValueQuery,
 	>;
 
+	#[pallet::storage]
+	#[pallet::getter(fn token_properties)]
+	pub type TokenProperties<T: Config> = StorageNMap<
+		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+		Value = up_data_structs::Properties,
+		QueryKind = ValueQuery,
+		OnEmpty = up_data_structs::TokenProperties,
+	>;
+
 	/// Total amount of pieces for token
 	#[pallet::storage]
 	pub type TotalSupply<T: Config> = StorageNMap<
@@ -278,6 +292,34 @@
 	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {
 		<TotalSupply<T>>::contains_key((collection.id, token))
 	}
+
+	pub fn set_scoped_token_property(
+		collection_id: CollectionId,
+		token_id: TokenId,
+		scope: PropertyScope,
+		property: Property,
+	) -> DispatchResult {
+		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {
+			properties.try_scoped_set(scope, property.key, property.value)
+		})
+		.map_err(<CommonError<T>>::from)?;
+
+		Ok(())
+	}
+
+	pub fn set_scoped_token_properties(
+		collection_id: CollectionId,
+		token_id: TokenId,
+		scope: PropertyScope,
+		properties: impl Iterator<Item = Property>,
+	) -> DispatchResult {
+		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {
+			stored_properties.try_scoped_set_from_iter(scope, properties)
+		})
+		.map_err(<CommonError<T>>::from)?;
+
+		Ok(())
+	}
 }
 
 // unchecked calls skips any permission checks
@@ -339,6 +381,7 @@
 
 		<TokensBurnt<T>>::insert(collection.id, burnt);
 		<TokenData<T>>::remove((collection.id, token_id));
+		<TokenProperties<T>>::remove((collection.id, token_id));
 		<TotalSupply<T>>::remove((collection.id, token_id));
 		<Balance<T>>::remove_prefix((collection.id, token_id), None);
 		<Allowance<T>>::remove_prefix((collection.id, token_id), None);
@@ -427,6 +470,170 @@
 		Ok(())
 	}
 
+	#[transactional]
+	fn modify_token_properties(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
+		is_token_create: bool,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		let is_collection_admin = || collection.is_owner_or_admin(sender);
+		let is_token_owner = || -> Result<bool, DispatchError> {
+			let balance = collection.balance(sender.clone(), token_id);
+			let total_pieces: u128 =
+				Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
+			if balance != total_pieces {
+				return Ok(false);
+			}
+
+			let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
+				sender.clone(),
+				collection.id,
+				token_id,
+				None,
+				nesting_budget,
+			)?;
+
+			Ok(is_bundle_owner)
+		};
+
+		for (key, value) in properties {
+			let permission = <PalletCommon<T>>::property_permissions(collection.id)
+				.get(&key)
+				.cloned()
+				.unwrap_or_else(PropertyPermission::none);
+
+			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))
+				.get(&key)
+				.is_some();
+
+			match permission {
+				PropertyPermission { mutable: false, .. } if is_property_exists => {
+					return Err(<CommonError<T>>::NoPermission.into());
+				}
+
+				PropertyPermission {
+					collection_admin,
+					token_owner,
+					..
+				} => {
+					//TODO: investigate threats during public minting.
+					let is_token_create =
+						is_token_create && (collection_admin || token_owner) && value.is_some();
+					if !(is_token_create
+						|| (collection_admin && is_collection_admin())
+						|| (token_owner && is_token_owner()?))
+					{
+						fail!(<CommonError<T>>::NoPermission);
+					}
+				}
+			}
+
+			match value {
+				Some(value) => {
+					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+						properties.try_set(key.clone(), value)
+					})
+					.map_err(<CommonError<T>>::from)?;
+
+					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
+						collection.id,
+						token_id,
+						key,
+					));
+				}
+				None => {
+					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+						properties.remove(&key)
+					})
+					.map_err(<CommonError<T>>::from)?;
+
+					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(
+						collection.id,
+						token_id,
+						key,
+					));
+				}
+			}
+		}
+
+		Ok(())
+	}
+
+	pub fn set_token_properties(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		properties: impl Iterator<Item = Property>,
+		is_token_create: bool,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		Self::modify_token_properties(
+			collection,
+			sender,
+			token_id,
+			properties.map(|p| (p.key, Some(p.value))),
+			is_token_create,
+			nesting_budget,
+		)
+	}
+
+	pub fn set_token_property(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		property: Property,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		let is_token_create = false;
+
+		Self::set_token_properties(
+			collection,
+			sender,
+			token_id,
+			[property].into_iter(),
+			is_token_create,
+			nesting_budget,
+		)
+	}
+
+	pub fn delete_token_properties(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		property_keys: impl Iterator<Item = PropertyKey>,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		let is_token_create = false;
+
+		Self::modify_token_properties(
+			collection,
+			sender,
+			token_id,
+			property_keys.into_iter().map(|key| (key, None)),
+			is_token_create,
+			nesting_budget,
+		)
+	}
+
+	pub fn delete_token_property(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token_id: TokenId,
+		property_key: PropertyKey,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		Self::delete_token_properties(
+			collection,
+			sender,
+			token_id,
+			[property_key].into_iter(),
+			nesting_budget,
+		)
+	}
+
 	/// Transfer RFT token pieces from one account to another.
 	///
 	/// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.
@@ -629,32 +836,58 @@
 
 		// =========
 
+		with_transaction(|| {
+			for (i, data) in data.iter().enumerate() {
+				let token_id = first_token_id + i as u32 + 1;
+				<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
+
+				<TokenData<T>>::insert(
+					(collection.id, token_id),
+					ItemData {
+						const_data: data.const_data.clone(),
+					},
+				);
+
+				for (user, amount) in data.users.iter() {
+					if *amount == 0 {
+						continue;
+					}
+					<Balance<T>>::insert((collection.id, token_id, &user), amount);
+					<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
+					<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
+						user,
+						collection.id,
+						TokenId(token_id),
+					);
+				}
+
+				if let Err(e) = Self::set_token_properties(
+					collection,
+					sender,
+					TokenId(token_id),
+					data.properties.clone().into_iter(),
+					true,
+					nesting_budget,
+				) {
+					return TransactionOutcome::Rollback(Err(e));
+				}
+			}
+			TransactionOutcome::Commit(Ok(()))
+		})?;
+
 		<TokensMinted<T>>::insert(collection.id, tokens_minted);
+
 		for (account, balance) in balances {
 			<AccountBalance<T>>::insert((collection.id, account), balance);
 		}
+
 		for (i, token) in data.into_iter().enumerate() {
 			let token_id = first_token_id + i as u32 + 1;
-			<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
-
-			<TokenData<T>>::insert(
-				(collection.id, token_id),
-				ItemData {
-					const_data: token.const_data,
-				},
-			);
 
 			for (user, amount) in token.users.into_iter() {
 				if amount == 0 {
 					continue;
 				}
-				<Balance<T>>::insert((collection.id, token_id, &user), amount);
-				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
-				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
-					&user,
-					collection.id,
-					TokenId(token_id),
-				);
 
 				// TODO: ERC20 transfer event
 				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
@@ -849,10 +1082,10 @@
 		);
 		ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);
 		// Ensure user owns all pieces
-		let total_supply = <TotalSupply<T>>::get((collection.id, token));
+		let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);
 		let balance = <Balance<T>>::get((collection.id, token, owner));
 		ensure!(
-			total_supply == balance,
+			total_pieces == balance,
 			<Error<T>>::RepartitionWhileNotOwningAllPieces
 		);
 
@@ -861,7 +1094,44 @@
 		Ok(())
 	}
 
+	fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {
+		let mut owner = None;
+		let mut count = 0;
+		for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {
+			count += 1;
+			if count > 1 {
+				return None;
+			}
+			owner = Some(key);
+		}
+		owner
+	}
+
 	fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {
 		<TotalSupply<T>>::try_get((collection_id, token_id)).ok()
 	}
+
+	pub fn set_collection_properties(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		properties: Vec<Property>,
+	) -> DispatchResult {
+		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)
+	}
+
+	pub fn delete_collection_properties(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		property_keys: Vec<PropertyKey>,
+	) -> DispatchResult {
+		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)
+	}
+
+	pub fn set_token_property_permissions(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		property_permissions: Vec<PropertyKeyPermission>,
+	) -> DispatchResult {
+		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
+	}
 }
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_refungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-06-27, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -49,6 +49,9 @@
 	fn transfer_from_removing() -> Weight;
 	fn transfer_from_creating_removing() -> Weight;
 	fn burn_from() -> Weight;
+	fn set_token_property_permissions(b: u32, ) -> Weight;
+	fn set_token_properties(b: u32, ) -> Weight;
+	fn delete_token_properties(b: u32, ) -> Weight;
 	fn repartition_item() -> Weight;
 }
 
@@ -62,7 +65,7 @@
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(17_553_000 as Weight)
+		(21_310_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
@@ -73,9 +76,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(10_654_000 as Weight)
-			// Standard Error: 1_000
-			.saturating_add((5_114_000 as Weight).saturating_mul(b as Weight))
+		(9_552_000 as Weight)
+			// Standard Error: 2_000
+			.saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -87,9 +90,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
-		(3_587_000 as Weight)
+		(4_857_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((7_931_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -102,9 +105,9 @@
 	// Storage: Refungible Balance (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
-		(1_980_000 as Weight)
+		(11_335_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((6_305_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(3 as Weight))
@@ -115,7 +118,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn burn_item_partial() -> Weight {
-		(21_010_000 as Weight)
+		(21_239_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -125,14 +128,15 @@
 	// Storage: Refungible TokensBurnt (r:1 w:1)
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
+	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_item_fully() -> Weight {
-		(28_413_000 as Weight)
+		(29_426_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
-			.saturating_add(T::DbWeight::get().writes(6 as Weight))
+			.saturating_add(T::DbWeight::get().writes(7 as Weight))
 	}
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_normal() -> Weight {
-		(17_513_000 as Weight)
+		(17_743_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
@@ -140,7 +144,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_creating() -> Weight {
-		(20_469_000 as Weight)
+		(20_699_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -148,7 +152,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_removing() -> Weight {
-		(22_472_000 as Weight)
+		(22_833_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -156,21 +160,21 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_creating_removing() -> Weight {
-		(24_866_000 as Weight)
+		(24_936_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Refungible Balance (r:1 w:0)
 	// Storage: Refungible Allowance (r:0 w:1)
 	fn approve() -> Weight {
-		(13_475_000 as Weight)
+		(13_446_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible Allowance (r:1 w:1)
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_from_normal() -> Weight {
-		(24_707_000 as Weight)
+		(24_777_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(3 as Weight))
 	}
@@ -179,7 +183,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_creating() -> Weight {
-		(27_812_000 as Weight)
+		(28_483_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -188,7 +192,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_removing() -> Weight {
-		(29_966_000 as Weight)
+		(29_896_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -197,7 +201,7 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_from_creating_removing() -> Weight {
-		(31_660_000 as Weight)
+		(32_070_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(7 as Weight))
 	}
@@ -208,15 +212,42 @@
 	// Storage: Refungible TokensBurnt (r:1 w:1)
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
+	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(36_248_000 as Weight)
+		(36_789_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
-			.saturating_add(T::DbWeight::get().writes(7 as Weight))
+			.saturating_add(T::DbWeight::get().writes(8 as Weight))
+	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
+	fn set_token_property_permissions(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 62_000
+			.saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn set_token_properties(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 1_668_000
+			.saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn delete_token_properties(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 1_619_000
+			.saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	fn repartition_item() -> Weight {
-		(8_226_000 as Weight)
+		(8_325_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
@@ -231,7 +262,7 @@
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(17_553_000 as Weight)
+		(21_310_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
@@ -242,9 +273,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(10_654_000 as Weight)
-			// Standard Error: 1_000
-			.saturating_add((5_114_000 as Weight).saturating_mul(b as Weight))
+		(9_552_000 as Weight)
+			// Standard Error: 2_000
+			.saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -256,9 +287,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
-		(3_587_000 as Weight)
+		(4_857_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((7_931_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -271,9 +302,9 @@
 	// Storage: Refungible Balance (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
-		(1_980_000 as Weight)
+		(11_335_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((6_305_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
@@ -284,7 +315,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn burn_item_partial() -> Weight {
-		(21_010_000 as Weight)
+		(21_239_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -294,14 +325,15 @@
 	// Storage: Refungible TokensBurnt (r:1 w:1)
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
+	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_item_fully() -> Weight {
-		(28_413_000 as Weight)
+		(29_426_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
 	}
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_normal() -> Weight {
-		(17_513_000 as Weight)
+		(17_743_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
@@ -309,7 +341,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_creating() -> Weight {
-		(20_469_000 as Weight)
+		(20_699_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -317,7 +349,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_removing() -> Weight {
-		(22_472_000 as Weight)
+		(22_833_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -325,21 +357,21 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_creating_removing() -> Weight {
-		(24_866_000 as Weight)
+		(24_936_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Refungible Balance (r:1 w:0)
 	// Storage: Refungible Allowance (r:0 w:1)
 	fn approve() -> Weight {
-		(13_475_000 as Weight)
+		(13_446_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible Allowance (r:1 w:1)
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_from_normal() -> Weight {
-		(24_707_000 as Weight)
+		(24_777_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
 	}
@@ -348,7 +380,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_creating() -> Weight {
-		(27_812_000 as Weight)
+		(28_483_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -357,7 +389,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_removing() -> Weight {
-		(29_966_000 as Weight)
+		(29_896_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -366,7 +398,7 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_from_creating_removing() -> Weight {
-		(31_660_000 as Weight)
+		(32_070_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
 	}
@@ -377,15 +409,42 @@
 	// Storage: Refungible TokensBurnt (r:1 w:1)
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
+	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(36_248_000 as Weight)
+		(36_789_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(8 as Weight))
+	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
+	fn set_token_property_permissions(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 62_000
+			.saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn set_token_properties(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 1_668_000
+			.saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
+	// Storage: Common CollectionPropertyPermissions (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn delete_token_properties(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 1_619_000
+			.saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	fn repartition_item() -> Weight {
-		(8_226_000 as Weight)
+		(8_325_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
modifiedprimitives/data-structs/Cargo.tomldiffbeforeafterboth
--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -6,7 +6,7 @@
 license = 'GPLv3'
 homepage = "https://unique.network"
 repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.1.0'
+version = '0.1.1'
 
 [dependencies]
 scale-info = { version = "2.0.1", default-features = false, features = [
addedprimitives/data-structs/Changelog.mddiffbeforeafterboth
--- /dev/null
+++ b/primitives/data-structs/Changelog.md
@@ -0,0 +1,3 @@
+### 0.1.1
+---
+* Added fields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
\ No newline at end of file
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -534,7 +534,12 @@
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
+
 	pub pieces: u128,
+
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
+	pub properties: CollectionPropertiesVec,
 }
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
@@ -568,6 +573,8 @@
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
 	#[derivative(Debug(format_with = "bounded::map_debug"))]
 	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
+	pub properties: CollectionPropertiesVec,
 }
 
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -376,14 +376,26 @@
       // ReFungible
       const collectionIdReFungible =
         await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const argsReFungible = [
-        {ReFungible: ['1'.repeat(2049), 10]},
-        {ReFungible: ['2'.repeat(2049), 10]},
-        {ReFungible: ['3'.repeat(2049), 10]},
-      ];
-      const createMultipleItemsTxFungible = api.tx.unique
-        .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
-      await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+      {
+        const argsReFungible = [
+          {ReFungible: ['1'.repeat(2049), 10, []]},
+          {ReFungible: ['2'.repeat(2049), 10, []]},
+          {ReFungible: ['3'.repeat(2049), 10, []]},
+        ];
+        const createMultipleItemsTxFungible = api.tx.unique
+          .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
+        await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+      }
+      {
+        const argsReFungible = [
+          {ReFungible: {properties: [{key: 'key', value: 'A'.repeat(32769)}]}},
+          {ReFungible: {properties: [{key: 'key', value: 'B'.repeat(32769)}]}},
+          {ReFungible: {properties: [{key: 'key', value: 'C'.repeat(32769)}]}},
+        ];
+        const createMultipleItemsTxFungible = api.tx.unique
+          .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
+        await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+      }
     });
   });
 
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -408,6 +408,7 @@
        **/
       owned: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: u32 | AnyNumber | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]>;
       tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<PalletRefungibleItemData>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+      tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
       tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       totalSupply: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -2461,12 +2461,14 @@
 export interface UpDataStructsCreateReFungibleData extends Struct {
   readonly constData: Bytes;
   readonly pieces: u128;
+  readonly properties: Vec<UpDataStructsProperty>;
 }
 
 /** @name UpDataStructsCreateRefungibleExData */
 export interface UpDataStructsCreateRefungibleExData extends Struct {
   readonly constData: Bytes;
   readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
+  readonly properties: Vec<UpDataStructsProperty>;
 }
 
 /** @name UpDataStructsNestingPermissions */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1502,7 +1502,8 @@
    **/
   UpDataStructsCreateReFungibleData: {
     constData: 'Bytes',
-    pieces: 'u128'
+    pieces: 'u128',
+    properties: 'Vec<UpDataStructsProperty>'
   },
   /**
    * Lookup193: up_data_structs::CreateItemExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
@@ -1527,7 +1528,8 @@
    **/
   UpDataStructsCreateRefungibleExData: {
     constData: 'Bytes',
-    users: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>'
+    users: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>',
+    properties: 'Vec<UpDataStructsProperty>'
   },
   /**
    * Lookup204: pallet_unique_scheduler::pallet::Call<T>
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1629,6 +1629,7 @@
   export interface UpDataStructsCreateReFungibleData extends Struct {
     readonly constData: Bytes;
     readonly pieces: u128;
+    readonly properties: Vec<UpDataStructsProperty>;
   }
 
   /** @name UpDataStructsCreateItemExData (193) */
@@ -1654,6 +1655,7 @@
   export interface UpDataStructsCreateRefungibleExData extends Struct {
     readonly constData: Bytes;
     readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
+    readonly properties: Vec<UpDataStructsProperty>;
   }
 
   /** @name PalletUniqueSchedulerCall (204) */
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
before · tests/src/nesting/properties.test.ts
1import {expect} from 'chai';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {4  addCollectionAdminExpectSuccess,5  createCollectionExpectSuccess,6  setCollectionPermissionsExpectSuccess,7  createItemExpectSuccess,8  getCreateCollectionResult,9  transferExpectSuccess,10} from '../util/helpers';11import {IKeyringPair} from '@polkadot/types/types';12import {tokenIdToAddress} from '../eth/util/helpers';1314let alice: IKeyringPair;15let bob: IKeyringPair;16let charlie: IKeyringPair;1718describe('Composite Properties Test', () => {19  before(async () => {20    await usingApi(async (api, privateKeyWrapper) => {21      alice = privateKeyWrapper('//Alice');22      bob = privateKeyWrapper('//Bob');23    });24  });2526  it('Makes sure collectionById supplies required fields', async () => {27    await usingApi(async api => {28      const collectionId = await createCollectionExpectSuccess();2930      const collectionOption = await api.rpc.unique.collectionById(collectionId);31      expect(collectionOption.isSome).to.be.true;32      let collection = collectionOption.unwrap();33      expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;34      expect(collection.properties.toHuman()).to.be.empty;3536      const propertyPermissions = [37        {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},38        {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},39      ];40      await expect(executeTransaction(41        api, 42        alice, 43        api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 44      )).to.not.be.rejected;4546      const collectionProperties = [47        {key: 'black_hole', value: 'LIGO'},48        {key: 'electron', value: 'come bond'}, 49      ];50      await expect(executeTransaction(51        api, 52        alice, 53        api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 54      )).to.not.be.rejected;5556      collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();57      expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);58      expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);59    });60  });61});6263// ---------- COLLECTION PROPERTIES6465describe('Integration Test: Collection Properties', () => {66  before(async () => {67    await usingApi(async (api, privateKeyWrapper) => {68      alice = privateKeyWrapper('//Alice');69      bob = privateKeyWrapper('//Bob');70    });71  });7273  it('Reads properties from a collection', async () => {74    await usingApi(async api => {75      const collection = await createCollectionExpectSuccess();76      const properties = (await api.query.common.collectionProperties(collection)).toJSON();77      expect(properties.map).to.be.empty;78      expect(properties.consumedSpace).to.equal(0);79    });80  });8182  it('Sets properties for a collection', async () => {83    await usingApi(async api => {84      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));85      const {collectionId} = getCreateCollectionResult(events);8687      // As owner88      await expect(executeTransaction(89        api, 90        bob, 91        api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 92      )).to.not.be.rejected;9394      await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);9596      // As administrator97      await expect(executeTransaction(98        api, 99        alice, 100        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 101      )).to.not.be.rejected;102103      const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();104      expect(properties).to.be.deep.equal([105        {key: 'electron', value: 'come bond'},106        {key: 'black_hole', value: ''},107      ]);108    });109  });110111  it('Check valid names for collection properties keys', async () => {112    await usingApi(async api => {113      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));114      const {collectionId} = getCreateCollectionResult(events);115116      // alpha symbols117      await expect(executeTransaction(118        api, 119        bob, 120        api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 121      )).to.not.be.rejected;122123      // numeric symbols124      await expect(executeTransaction(125        api, 126        bob, 127        api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 128      )).to.not.be.rejected;129130      // underscore symbol131      await expect(executeTransaction(132        api, 133        bob, 134        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 135      )).to.not.be.rejected;136137      // dash symbol138      await expect(executeTransaction(139        api, 140        bob, 141        api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 142      )).to.not.be.rejected;143144      // underscore symbol145      await expect(executeTransaction(146        api, 147        bob, 148        api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 149      )).to.not.be.rejected;150151      const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];152      const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();153      expect(properties).to.be.deep.equal([154        {key: 'alpha', value: ''},155        {key: '123', value: ''},156        {key: 'black_hole', value: ''},157        {key: 'semi-automatic', value: ''},158        {key: 'build.rs', value: ''},159      ]);160    });161  });162163  it('Changes properties of a collection', async () => {164    await usingApi(async api => {165      const collection = await createCollectionExpectSuccess();166167      await expect(executeTransaction(168        api, 169        alice, 170        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 171      )).to.not.be.rejected;172173      // Mutate the properties174      await expect(executeTransaction(175        api, 176        alice, 177        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 178      )).to.not.be.rejected;179180      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();181      expect(properties).to.be.deep.equal([182        {key: 'electron', value: 'bonded'},183        {key: 'black_hole', value: 'LIGO'},184      ]);185    });186  });187188  it('Deletes properties of a collection', async () => {189    await usingApi(async api => {190      const collection = await createCollectionExpectSuccess();191192      await expect(executeTransaction(193        api, 194        alice, 195        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 196      )).to.not.be.rejected;197198      await expect(executeTransaction(199        api, 200        alice, 201        api.tx.unique.deleteCollectionProperties(collection, ['electron']), 202      )).to.not.be.rejected;203204      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();205      expect(properties).to.be.deep.equal([206        {key: 'black_hole', value: 'LIGO'},207      ]);208    });209  });210});211212describe('Negative Integration Test: Collection Properties', () => {213  before(async () => {214    await usingApi(async (api, privateKeyWrapper) => {215      alice = privateKeyWrapper('//Alice');216      bob = privateKeyWrapper('//Bob');217    });218  });219  220  it('Fails to set properties in a collection if not its onwer/administrator', async () => {221    await usingApi(async api => {222      const collection = await createCollectionExpectSuccess();223224      await expect(executeTransaction(225        api, 226        bob, 227        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 228      )).to.be.rejectedWith(/common\.NoPermission/);229  230      const properties = (await api.query.common.collectionProperties(collection)).toJSON();231      expect(properties.map).to.be.empty;232      expect(properties.consumedSpace).to.equal(0);233    });234  });235  236  it('Fails to set properties that exceed the limits', async () => {237    await usingApi(async api => {238      const collection = await createCollectionExpectSuccess();239      const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 240241      // Mute the general tx parsing error, too many bytes to process242      {243        console.error = () => {};244        await expect(executeTransaction(245          api, 246          alice, 247          api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 248        )).to.be.rejected;249      }250251      let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();252      expect(properties).to.be.empty;253254      await expect(executeTransaction(255        api, 256        alice, 257        api.tx.unique.setCollectionProperties(collection, [258          {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 259          {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 260        ]), 261      )).to.be.rejectedWith(/common\.NoSpaceForProperty/);262263      properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();264      expect(properties).to.be.empty;265    });266  });267  268  it('Fails to set more properties than it is allowed', async () => {269    await usingApi(async api => {270      const collection = await createCollectionExpectSuccess();271272      const propertiesToBeSet = [];273      for (let i = 0; i < 65; i++) {274        propertiesToBeSet.push({275          key: 'electron_' + i,276          value: Math.random() > 0.5 ? 'high' : 'low',277        });278      }279280      await expect(executeTransaction(281        api, 282        alice, 283        api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 284      )).to.be.rejectedWith(/common\.PropertyLimitReached/);285286      const properties = (await api.query.common.collectionProperties(collection)).toJSON();287      expect(properties.map).to.be.empty;288      expect(properties.consumedSpace).to.equal(0);289    });290  });291292  it('Fails to set properties with invalid names', async () => {293    await usingApi(async api => {294      const collection = await createCollectionExpectSuccess();295296      const invalidProperties = [297        [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],298        [{key: 'Mr/Sandman', value: 'Bring me a gene'}],299        [{key: 'déjà vu', value: 'hmm...'}],300      ];301302      for (let i = 0; i < invalidProperties.length; i++) {303        await expect(executeTransaction(304          api, 305          alice, 306          api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 307        ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);308      }309310      await expect(executeTransaction(311        api, 312        alice, 313        api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 314      ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);315316      await expect(executeTransaction(317        api, 318        alice, 319        api.tx.unique.setCollectionProperties(collection, [320          {key: 'CRISPR-Cas9', value: 'rewriting nature!'},321        ]), 322      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;323324      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');325326      const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();327      expect(properties).to.be.deep.equal([328        {key: 'CRISPR-Cas9', value: 'rewriting nature!'},329      ]);330331      for (let i = 0; i < invalidProperties.length; i++) {332        await expect(executeTransaction(333          api, 334          alice, 335          api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 336        ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);337      }338    });339  });340});341342// ---------- ACCESS RIGHTS343344describe('Integration Test: Access Rights to Token Properties', () => {345  before(async () => {346    await usingApi(async (api, privateKeyWrapper) => {347      alice = privateKeyWrapper('//Alice');348      bob = privateKeyWrapper('//Bob');349    });350  });351  352  it('Reads access rights to properties of a collection', async () => {353    await usingApi(async api => {354      const collection = await createCollectionExpectSuccess();355      const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();356      expect(propertyRights).to.be.empty;357    });358  });359  360  it('Sets access rights to properties of a collection', async () => {361    await usingApi(async api => {362      const collection = await createCollectionExpectSuccess();363364      await expect(executeTransaction(365        api, 366        alice, 367        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 368      )).to.not.be.rejected;369370      await addCollectionAdminExpectSuccess(alice, collection, bob.address);371372      await expect(executeTransaction(373        api, 374        alice, 375        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 376      )).to.not.be.rejected;377378      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();379      expect(propertyRights).to.be.deep.equal([380        {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},381        {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},382      ]);383    });384  });385  386  it('Changes access rights to properties of a collection', async () => {387    await usingApi(async api => {388      const collection = await createCollectionExpectSuccess();389390      await expect(executeTransaction(391        api, 392        alice, 393        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 394      )).to.not.be.rejected;395396      await expect(executeTransaction(397        api, 398        alice, 399        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 400      )).to.not.be.rejected;401402      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();403      expect(propertyRights).to.be.deep.equal([404        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},405      ]);406    });407  });408});409410describe('Negative Integration Test: Access Rights to Token Properties', () => {411  before(async () => {412    await usingApi(async (api, privateKeyWrapper) => {413      alice = privateKeyWrapper('//Alice');414      bob = privateKeyWrapper('//Bob');415    });416  });417418  it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {419    await usingApi(async api => {420      const collection = await createCollectionExpectSuccess();421422      await expect(executeTransaction(423        api, 424        bob, 425        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 426      )).to.be.rejectedWith(/common\.NoPermission/);427428      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();429      expect(propertyRights).to.be.empty;430    });431  });432433  it('Prevents from adding too many possible properties', async () => {434    await usingApi(async api => {435      const collection = await createCollectionExpectSuccess();436437      const constitution = [];438      for (let i = 0; i < 65; i++) {439        constitution.push({440          key: 'property_' + i,441          permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},442        });443      }444445      await expect(executeTransaction(446        api, 447        alice, 448        api.tx.unique.setTokenPropertyPermissions(collection, constitution), 449      )).to.be.rejectedWith(/common\.PropertyLimitReached/);450451      const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();452      expect(propertyRights).to.be.empty;453    });454  });455456  it('Prevents access rights to be modified if constant', async () => {457    await usingApi(async api => {458      const collection = await createCollectionExpectSuccess();459460      await expect(executeTransaction(461        api, 462        alice, 463        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 464      )).to.not.be.rejected;465466      await expect(executeTransaction(467        api, 468        alice, 469        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 470      )).to.be.rejectedWith(/common\.NoPermission/);471472      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();473      expect(propertyRights).to.deep.equal([474        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},475      ]);476    });477  });478479  it('Prevents adding properties with invalid names', async () => {480    await usingApi(async api => {481      const collection = await createCollectionExpectSuccess();482483      const invalidProperties = [484        [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],485        [{key: 'G#4', permission: {tokenOwner: true}}],486        [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],487      ];488489      for (let i = 0; i < invalidProperties.length; i++) {490        await expect(executeTransaction(491          api, 492          alice, 493          api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 494        ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);495      }496497      await expect(executeTransaction(498        api, 499        alice, 500        api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 501      ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);502503      const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string504      await expect(executeTransaction(505        api, 506        alice, 507        api.tx.unique.setTokenPropertyPermissions(collection, [508          {key: correctKey, permission: {collectionAdmin: true}},509        ]), 510      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;511512      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');513514      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();515      expect(propertyRights).to.be.deep.equal([516        {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},517      ]);518    });519  });520});521522// ---------- TOKEN PROPERTIES523524describe('Integration Test: Token Properties', () => {525  let collection: number;526  let token: number;527  let nestedToken: number;528  let permissions: {permission: any, signers: IKeyringPair[]}[];529530  before(async () => {531    await usingApi(async (api, privateKeyWrapper) => {532      alice = privateKeyWrapper('//Alice');533      bob = privateKeyWrapper('//Bob');534      charlie = privateKeyWrapper('//Charlie');535536      permissions = [537        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},538        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},539        {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},540        {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},541        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},542        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},543      ];544    });545  });546547  beforeEach(async () => {548    await usingApi(async () => {549      collection = await createCollectionExpectSuccess();550      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});551552      token = await createItemExpectSuccess(alice, collection, 'NFT');553      nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});554555      await addCollectionAdminExpectSuccess(alice, collection, bob.address);556      await transferExpectSuccess(collection, token, alice, charlie);557    });558  });559  560  it('Reads yet empty properties of a token', async () => {561    await usingApi(async api => {562      const collection = await createCollectionExpectSuccess();563      const token = await createItemExpectSuccess(alice, collection, 'NFT');564  565      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();566      expect(properties.map).to.be.empty;567      expect(properties.consumedSpace).to.be.equal(0);568569      const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;570      expect(tokenData).to.be.empty;571    });572  });573574  it('Assigns properties to a token according to permissions', async () => {575    await usingApi(async api => {576      const propertyKeys: string[] = [];577      let i = 0;578      for (const permission of permissions) {579        for (const signer of permission.signers) {580          const key = i + '_' + signer.address;581          propertyKeys.push(key);582583          await expect(executeTransaction(584            api, 585            alice, 586            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 587          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;588589          await expect(executeTransaction(590            api, 591            signer, 592            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 593          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;594        }595596        i++;597      }598599      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];600      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];601      for (let i = 0; i < properties.length; i++) {602        expect(properties[i].value).to.be.equal('Serotonin increase');603        expect(tokensData[i].value).to.be.equal('Serotonin increase');604      }605    });606  });607608  it('Changes properties of a token according to permissions', async () => {609    await usingApi(async api => {610      const propertyKeys: string[] = [];611      let i = 0;612      for (const permission of permissions) {613        if (!permission.permission.mutable) continue;614        615        for (const signer of permission.signers) {616          const key = i + '_' + signer.address;617          propertyKeys.push(key);618619          await expect(executeTransaction(620            api, 621            alice, 622            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 623          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;624625          await expect(executeTransaction(626            api, 627            signer, 628            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 629          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;630631          await expect(executeTransaction(632            api, 633            signer, 634            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 635          ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;636        }637638        i++;639      }640641      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];642      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];643      for (let i = 0; i < properties.length; i++) {644        expect(properties[i].value).to.be.equal('Serotonin stable');645        expect(tokensData[i].value).to.be.equal('Serotonin stable');646      }647    });648  });649650  it('Deletes properties of a token according to permissions', async () => {651    await usingApi(async api => {652      const propertyKeys: string[] = [];653      let i = 0;654655      for (const permission of permissions) {656        if (!permission.permission.mutable) continue;657        658        for (const signer of permission.signers) {659          const key = i + '_' + signer.address;660          propertyKeys.push(key);661662          await expect(executeTransaction(663            api, 664            alice, 665            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 666          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;667668          await expect(executeTransaction(669            api, 670            signer, 671            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 672          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;673674          await expect(executeTransaction(675            api, 676            signer, 677            api.tx.unique.deleteTokenProperties(collection, token, [key]), 678          ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;679        }680        681        i++;682      }683684      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];685      expect(properties).to.be.empty;686      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];687      expect(tokensData).to.be.empty;688      expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);689    });690  });691692  it('Assigns properties to a nested token according to permissions', async () => {693    await usingApi(async api => {694      const propertyKeys: string[] = [];695      let i = 0;696      for (const permission of permissions) {697        for (const signer of permission.signers) {698          const key = i + '_' + signer.address;699          propertyKeys.push(key);700701          await expect(executeTransaction(702            api, 703            alice, 704            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 705          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;706707          await expect(executeTransaction(708            api, 709            signer, 710            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 711          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;712        }713714        i++;715      }716717      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];718      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];719      for (let i = 0; i < properties.length; i++) {720        expect(properties[i].value).to.be.equal('Serotonin increase');721        expect(tokensData[i].value).to.be.equal('Serotonin increase');722      }723    });724  });725726  it('Changes properties of a nested token according to permissions', async () => {727    await usingApi(async api => {728      const propertyKeys: string[] = [];729      let i = 0;730      for (const permission of permissions) {731        if (!permission.permission.mutable) continue;732        733        for (const signer of permission.signers) {734          const key = i + '_' + signer.address;735          propertyKeys.push(key);736737          await expect(executeTransaction(738            api, 739            alice, 740            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 741          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;742743          await expect(executeTransaction(744            api, 745            signer, 746            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 747          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;748749          await expect(executeTransaction(750            api, 751            signer, 752            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 753          ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;754        }755756        i++;757      }758759      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];760      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];761      for (let i = 0; i < properties.length; i++) {762        expect(properties[i].value).to.be.equal('Serotonin stable');763        expect(tokensData[i].value).to.be.equal('Serotonin stable');764      }765    });766  });767768  it('Deletes properties of a nested token according to permissions', async () => {769    await usingApi(async api => {770      const propertyKeys: string[] = [];771      let i = 0;772773      for (const permission of permissions) {774        if (!permission.permission.mutable) continue;775        776        for (const signer of permission.signers) {777          const key = i + '_' + signer.address;778          propertyKeys.push(key);779780          await expect(executeTransaction(781            api, 782            alice, 783            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 784          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;785786          await expect(executeTransaction(787            api, 788            signer, 789            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 790          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;791792          await expect(executeTransaction(793            api, 794            signer, 795            api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 796          ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;797        }798        799        i++;800      }801802      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];803      expect(properties).to.be.empty;804      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];805      expect(tokensData).to.be.empty;806      expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);807    });808  });809});810811describe('Negative Integration Test: Token Properties', () => {812  let collection: number;813  let token: number;814  let originalSpace: number;815  let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];816817  before(async () => {818    await usingApi(async (api, privateKeyWrapper) => {819      alice = privateKeyWrapper('//Alice');820      bob = privateKeyWrapper('//Bob');821      charlie = privateKeyWrapper('//Charlie');822      const dave = privateKeyWrapper('//Dave');823824      constitution = [825        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},826        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},827        {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},828        {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},829        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},830        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},831      ];832    });833  });834835  beforeEach(async () => {836    collection = await createCollectionExpectSuccess();837    token = await createItemExpectSuccess(alice, collection, 'NFT');838    await addCollectionAdminExpectSuccess(alice, collection, bob.address);839    await transferExpectSuccess(collection, token, alice, charlie);840        841    await usingApi(async api => {842      let i = 0;843      for (const passage of constitution) {844        const signer = passage.signers[0];845        846        await expect(executeTransaction(847          api, 848          alice, 849          api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 850        ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;851852        await expect(executeTransaction(853          api, 854          signer, 855          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 856        ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;857858        i++;859      }860861      originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;862    });863  });864865  it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {866    await usingApi(async api => {867      let i = -1;868      for (const forbiddance of constitution) {869        i++;870        if (!forbiddance.permission.mutable) continue;871872        await expect(executeTransaction(873          api, 874          forbiddance.sinner, 875          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 876        ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);877878        await expect(executeTransaction(879          api, 880          forbiddance.sinner, 881          api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 882        ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);883      }884885      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();886      expect(properties.consumedSpace).to.be.equal(originalSpace);887    });888  });889890  it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {891    await usingApi(async api => {892      let i = -1;893      for (const permission of constitution) {894        i++;895        if (permission.permission.mutable) continue;896897        await expect(executeTransaction(898          api, 899          permission.signers[0], 900          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 901        ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);902903        await expect(executeTransaction(904          api, 905          permission.signers[0], 906          api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 907        ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);908      }909910      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();911      expect(properties.consumedSpace).to.be.equal(originalSpace);912    });913  });914915  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {916    await usingApi(async api => {917      await expect(executeTransaction(918        api, 919        alice, 920        api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 921      ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);922        923      await expect(executeTransaction(924        api, 925        alice, 926        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 927      ), 'on setting a new non-permitted property').to.not.be.rejected;928929      await expect(executeTransaction(930        api, 931        alice, 932        api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 933      ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);934935      expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;936      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();937      expect(properties.consumedSpace).to.be.equal(originalSpace);938    });939  });940941  it('Forbids adding too many properties to a token', async () => {942    await usingApi(async api => {943      await expect(executeTransaction(944        api, 945        alice, 946        api.tx.unique.setTokenPropertyPermissions(collection, [947          {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 948          {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},949        ]), 950      ), 'on setting a new non-permitted property').to.not.be.rejected;951952      // Mute the general tx parsing error953      {954        console.error = () => {};955        await expect(executeTransaction(956          api, 957          alice, 958          api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 959        )).to.be.rejected;960      }961962      await expect(executeTransaction(963        api, 964        alice, 965        api.tx.unique.setTokenProperties(collection, token, [966          {key: 'a_holy_book', value: 'word '.repeat(3277)}, 967          {key: 'young_years', value: 'neverending'.repeat(1490)},968        ]), 969      )).to.be.rejectedWith(/common\.NoSpaceForProperty/);970971      expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;972      const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();973      expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);974    });975  });976});
after · tests/src/nesting/properties.test.ts
1import {expect} from 'chai';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {4  addCollectionAdminExpectSuccess,5  CollectionMode,6  createCollectionExpectSuccess,7  setCollectionPermissionsExpectSuccess,8  createItemExpectSuccess,9  getCreateCollectionResult,10  transferExpectSuccess,11} from '../util/helpers';12import {IKeyringPair} from '@polkadot/types/types';13import {tokenIdToAddress} from '../eth/util/helpers';1415let alice: IKeyringPair;16let bob: IKeyringPair;17let charlie: IKeyringPair;1819describe('Composite Properties Test', () => {20  before(async () => {21    await usingApi(async (api, privateKeyWrapper) => {22      alice = privateKeyWrapper('//Alice');23      bob = privateKeyWrapper('//Bob');24    });25  });2627  async function testMakeSureSuppliesRequired(mode: CollectionMode) {28    await usingApi(async api => {29      const collectionId = await createCollectionExpectSuccess({mode: mode});3031      const collectionOption = await api.rpc.unique.collectionById(collectionId);32      expect(collectionOption.isSome).to.be.true;33      let collection = collectionOption.unwrap();34      expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;35      expect(collection.properties.toHuman()).to.be.empty;3637      const propertyPermissions = [38        {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},39        {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},40      ];41      await expect(executeTransaction(42        api, 43        alice, 44        api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 45      )).to.not.be.rejected;4647      const collectionProperties = [48        {key: 'black_hole', value: 'LIGO'},49        {key: 'electron', value: 'come bond'}, 50      ];51      await expect(executeTransaction(52        api, 53        alice, 54        api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 55      )).to.not.be.rejected;5657      collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();58      expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);59      expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);60    });61  }6263  it('Makes sure collectionById supplies required fields for NFT', async () => {64    await testMakeSureSuppliesRequired({type: 'NFT'});65  });6667  it('Makes sure collectionById supplies required fields for ReFungible', async () => {68    await testMakeSureSuppliesRequired({type: 'ReFungible'});69  });70});7172// ---------- COLLECTION PROPERTIES7374describe('Integration Test: Collection Properties', () => {75  before(async () => {76    await usingApi(async (api, privateKeyWrapper) => {77      alice = privateKeyWrapper('//Alice');78      bob = privateKeyWrapper('//Bob');79    });80  });8182  it('Reads properties from a collection', async () => {83    await usingApi(async api => {84      const collection = await createCollectionExpectSuccess();85      const properties = (await api.query.common.collectionProperties(collection)).toJSON();86      expect(properties.map).to.be.empty;87      expect(properties.consumedSpace).to.equal(0);88    });89  });909192  async function testSetsPropertiesForCollection(mode: string) {93    await usingApi(async api => {94      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));95      const {collectionId} = getCreateCollectionResult(events);9697      // As owner98      await expect(executeTransaction(99        api, 100        bob, 101        api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 102      )).to.not.be.rejected;103104      await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);105106      // As administrator107      await expect(executeTransaction(108        api, 109        alice, 110        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 111      )).to.not.be.rejected;112113      const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();114      expect(properties).to.be.deep.equal([115        {key: 'electron', value: 'come bond'},116        {key: 'black_hole', value: ''},117      ]);118    });119  }120  it('Sets properties for a NFT collection', async () => {121    await testSetsPropertiesForCollection('NFT');122  });123  it('Sets properties for a ReFungible collection', async () => {124    await testSetsPropertiesForCollection('ReFungible');125  });126127  async function testCheckValidNames(mode: string) {128    await usingApi(async api => {129      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));130      const {collectionId} = getCreateCollectionResult(events);131  132      // alpha symbols133      await expect(executeTransaction(134        api, 135        bob, 136        api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 137      )).to.not.be.rejected;138  139      // numeric symbols140      await expect(executeTransaction(141        api, 142        bob, 143        api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 144      )).to.not.be.rejected;145  146      // underscore symbol147      await expect(executeTransaction(148        api, 149        bob, 150        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 151      )).to.not.be.rejected;152  153      // dash symbol154      await expect(executeTransaction(155        api, 156        bob, 157        api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 158      )).to.not.be.rejected;159  160      // underscore symbol161      await expect(executeTransaction(162        api, 163        bob, 164        api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 165      )).to.not.be.rejected;166  167      const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];168      const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();169      expect(properties).to.be.deep.equal([170        {key: 'alpha', value: ''},171        {key: '123', value: ''},172        {key: 'black_hole', value: ''},173        {key: 'semi-automatic', value: ''},174        {key: 'build.rs', value: ''},175      ]);176    });177  }178  it('Check valid names for NFT collection properties keys', async () => {179    await testCheckValidNames('NFT');180  });181  it('Check valid names for ReFungible collection properties keys', async () => {182    await testCheckValidNames('ReFungible');183  });184185  async function testChangesProperties(mode: CollectionMode) {186    await usingApi(async api => {187      const collection = await createCollectionExpectSuccess({mode: mode});188  189      await expect(executeTransaction(190        api, 191        alice, 192        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 193      )).to.not.be.rejected;194  195      // Mutate the properties196      await expect(executeTransaction(197        api, 198        alice, 199        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 200      )).to.not.be.rejected;201  202      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();203      expect(properties).to.be.deep.equal([204        {key: 'electron', value: 'bonded'},205        {key: 'black_hole', value: 'LIGO'},206      ]);207    });208  }209  it('Changes properties of a NFT collection', async () => {210    await testChangesProperties({type: 'NFT'});211  });212  it('Changes properties of a ReFungible collection', async () => {213    await testChangesProperties({type: 'ReFungible'});214  });215216  async function testDeleteProperties(mode: CollectionMode) {217    await usingApi(async api => {218      const collection = await createCollectionExpectSuccess({mode: mode});219  220      await expect(executeTransaction(221        api, 222        alice, 223        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 224      )).to.not.be.rejected;225  226      await expect(executeTransaction(227        api, 228        alice, 229        api.tx.unique.deleteCollectionProperties(collection, ['electron']), 230      )).to.not.be.rejected;231  232      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();233      expect(properties).to.be.deep.equal([234        {key: 'black_hole', value: 'LIGO'},235      ]);236    });  237  }238  it('Deletes properties of a NFT collection', async () => {239    await testDeleteProperties({type: 'NFT'});240  });241  it('Deletes properties of a ReFungible collection', async () => {242    await testDeleteProperties({type: 'ReFungible'});243  });244});245246describe('Negative Integration Test: Collection Properties', () => {247  before(async () => {248    await usingApi(async (api, privateKeyWrapper) => {249      alice = privateKeyWrapper('//Alice');250      bob = privateKeyWrapper('//Bob');251    });252  });253  254  async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {255    await usingApi(async api => {256      const collection = await createCollectionExpectSuccess({mode: mode});257  258      await expect(executeTransaction(259        api, 260        bob, 261        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 262      )).to.be.rejectedWith(/common\.NoPermission/);263  264      const properties = (await api.query.common.collectionProperties(collection)).toJSON();265      expect(properties.map).to.be.empty;266      expect(properties.consumedSpace).to.equal(0);267    });  268  }269  it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {270    await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});271  });272  it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {273    await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});274  });275  276  async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {277    await usingApi(async api => {278      const collection = await createCollectionExpectSuccess({mode: mode});279      const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 280  281      // Mute the general tx parsing error, too many bytes to process282      {283        console.error = () => {};284        await expect(executeTransaction(285          api, 286          alice, 287          api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 288        )).to.be.rejected;289      }290  291      let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();292      expect(properties).to.be.empty;293  294      await expect(executeTransaction(295        api, 296        alice, 297        api.tx.unique.setCollectionProperties(collection, [298          {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 299          {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 300        ]), 301      )).to.be.rejectedWith(/common\.NoSpaceForProperty/);302  303      properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();304      expect(properties).to.be.empty;305    });  306  }307  it('Fails to set properties that exceed the limits (NFT)', async () => {308    await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});309  });310  it('Fails to set properties that exceed the limits (ReFungible)', async () => {311    await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});312  });313  314  async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {315    await usingApi(async api => {316      const collection = await createCollectionExpectSuccess({mode: mode});317  318      const propertiesToBeSet = [];319      for (let i = 0; i < 65; i++) {320        propertiesToBeSet.push({321          key: 'electron_' + i,322          value: Math.random() > 0.5 ? 'high' : 'low',323        });324      }325  326      await expect(executeTransaction(327        api, 328        alice, 329        api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 330      )).to.be.rejectedWith(/common\.PropertyLimitReached/);331  332      const properties = (await api.query.common.collectionProperties(collection)).toJSON();333      expect(properties.map).to.be.empty;334      expect(properties.consumedSpace).to.equal(0);335    });  336  }337  it('Fails to set more properties than it is allowed (NFT)', async () => {338    await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});339  });340  it('Fails to set more properties than it is allowed (ReFungible)', async () => {341    await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});342  });343  344  async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {345    await usingApi(async api => {346      const collection = await createCollectionExpectSuccess({mode: mode});347  348      const invalidProperties = [349        [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],350        [{key: 'Mr/Sandman', value: 'Bring me a gene'}],351        [{key: 'déjà vu', value: 'hmm...'}],352      ];353  354      for (let i = 0; i < invalidProperties.length; i++) {355        await expect(executeTransaction(356          api, 357          alice, 358          api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 359        ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);360      }361  362      await expect(executeTransaction(363        api, 364        alice, 365        api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 366      ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);367  368      await expect(executeTransaction(369        api, 370        alice, 371        api.tx.unique.setCollectionProperties(collection, [372          {key: 'CRISPR-Cas9', value: 'rewriting nature!'},373        ]), 374      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;375  376      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');377  378      const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();379      expect(properties).to.be.deep.equal([380        {key: 'CRISPR-Cas9', value: 'rewriting nature!'},381      ]);382  383      for (let i = 0; i < invalidProperties.length; i++) {384        await expect(executeTransaction(385          api, 386          alice, 387          api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 388        ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);389      }390    });391  }392  it('Fails to set properties with invalid names (NFT)', async () => {393    await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});394  });395  it('Fails to set properties with invalid names (ReFungible)', async () => {396    await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});397  });398});399400// ---------- ACCESS RIGHTS401402describe('Integration Test: Access Rights to Token Properties', () => {403  before(async () => {404    await usingApi(async (api, privateKeyWrapper) => {405      alice = privateKeyWrapper('//Alice');406      bob = privateKeyWrapper('//Bob');407    });408  });409  410  it('Reads access rights to properties of a collection', async () => {411    await usingApi(async api => {412      const collection = await createCollectionExpectSuccess();413      const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();414      expect(propertyRights).to.be.empty;415    });416  });417  418  async function testSetsAccessRightsToProperties(mode: CollectionMode) {419    await usingApi(async api => {420      const collection = await createCollectionExpectSuccess({mode: mode});421  422      await expect(executeTransaction(423        api, 424        alice, 425        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 426      )).to.not.be.rejected;427  428      await addCollectionAdminExpectSuccess(alice, collection, bob.address);429  430      await expect(executeTransaction(431        api, 432        alice, 433        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 434      )).to.not.be.rejected;435  436      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();437      expect(propertyRights).to.be.deep.equal([438        {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},439        {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},440      ]);441    });  442  }443  it('Sets access rights to properties of a collection (NFT)', async () => {444    await testSetsAccessRightsToProperties({type: 'NFT'});445  });446  it('Sets access rights to properties of a collection (ReFungible)', async () => {447    await testSetsAccessRightsToProperties({type: 'ReFungible'});448  });449  450  async function testChangesAccessRightsToProperty(mode: CollectionMode) {451    await usingApi(async api => {452      const collection = await createCollectionExpectSuccess({mode: mode});453  454      await expect(executeTransaction(455        api, 456        alice, 457        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 458      )).to.not.be.rejected;459  460      await expect(executeTransaction(461        api, 462        alice, 463        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 464      )).to.not.be.rejected;465  466      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();467      expect(propertyRights).to.be.deep.equal([468        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},469      ]);470    });471  }472  it('Changes access rights to properties of a NFT collection', async () => {473    await testChangesAccessRightsToProperty({type: 'NFT'});474  });475  it('Changes access rights to properties of a ReFungible collection', async () => {476    await testChangesAccessRightsToProperty({type: 'ReFungible'});477  });478});479480describe('Negative Integration Test: Access Rights to Token Properties', () => {481  before(async () => {482    await usingApi(async (api, privateKeyWrapper) => {483      alice = privateKeyWrapper('//Alice');484      bob = privateKeyWrapper('//Bob');485    });486  });487488  async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {489    await usingApi(async api => {490      const collection = await createCollectionExpectSuccess({mode: mode});491  492      await expect(executeTransaction(493        api, 494        bob, 495        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 496      )).to.be.rejectedWith(/common\.NoPermission/);497  498      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();499      expect(propertyRights).to.be.empty;500    });501  }502  it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {503    await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});504  });505  it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {506    await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});507  });508509  async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {510    await usingApi(async api => {511      const collection = await createCollectionExpectSuccess({mode: mode});512  513      const constitution = [];514      for (let i = 0; i < 65; i++) {515        constitution.push({516          key: 'property_' + i,517          permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},518        });519      }520  521      await expect(executeTransaction(522        api, 523        alice, 524        api.tx.unique.setTokenPropertyPermissions(collection, constitution), 525      )).to.be.rejectedWith(/common\.PropertyLimitReached/);526  527      const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();528      expect(propertyRights).to.be.empty;529    });  530  }531  it('Prevents from adding too many possible properties (NFT)', async () => {532    await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});533  });534  it('Prevents from adding too many possible properties (ReFungible)', async () => {535    await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});536  });537538  async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {539    await usingApi(async api => {540      const collection = await createCollectionExpectSuccess({mode: mode});541  542      await expect(executeTransaction(543        api, 544        alice, 545        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 546      )).to.not.be.rejected;547  548      await expect(executeTransaction(549        api, 550        alice, 551        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 552      )).to.be.rejectedWith(/common\.NoPermission/);553  554      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();555      expect(propertyRights).to.deep.equal([556        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},557      ]);558    });  559  }560  it('Prevents access rights to be modified if constant (NFT)', async () => {561    await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});562  });563  it('Prevents access rights to be modified if constant (ReFungible)', async () => {564    await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});565  });566567  async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {568    await usingApi(async api => {569      const collection = await createCollectionExpectSuccess({mode: mode});570  571      const invalidProperties = [572        [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],573        [{key: 'G#4', permission: {tokenOwner: true}}],574        [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],575      ];576  577      for (let i = 0; i < invalidProperties.length; i++) {578        await expect(executeTransaction(579          api, 580          alice, 581          api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 582        ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);583      }584  585      await expect(executeTransaction(586        api, 587        alice, 588        api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 589      ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);590  591      const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string592      await expect(executeTransaction(593        api, 594        alice, 595        api.tx.unique.setTokenPropertyPermissions(collection, [596          {key: correctKey, permission: {collectionAdmin: true}},597        ]), 598      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;599  600      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');601  602      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();603      expect(propertyRights).to.be.deep.equal([604        {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},605      ]);606    });607  }608  it('Prevents adding properties with invalid names (NFT)', async () => {609    await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});610  });611  it('Prevents adding properties with invalid names (ReFungible)', async () => {612    await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});613  });614});615616// ---------- TOKEN PROPERTIES617618describe('Integration Test: Token Properties', () => {619  let permissions: {permission: any, signers: IKeyringPair[]}[];620621  before(async () => {622    await usingApi(async (api, privateKeyWrapper) => {623      alice = privateKeyWrapper('//Alice'); // collection owner624      bob = privateKeyWrapper('//Bob'); // collection admin625      charlie = privateKeyWrapper('//Charlie'); // token owner626627      permissions = [628        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},629        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},630        {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},631        {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},632        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},633        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},634      ];635    });636  });637  638  async function testReadsYetEmptyProperties(mode: CollectionMode) {639    await usingApi(async api => {640      const collection = await createCollectionExpectSuccess({mode: mode});641      const token = await createItemExpectSuccess(alice, collection, mode.type);642  643      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();644      expect(properties.map).to.be.empty;645      expect(properties.consumedSpace).to.be.equal(0);646  647      const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;648      expect(tokenData).to.be.empty;649    });650  }651  it('Reads yet empty properties of a token (NFT)', async () => {652    await testReadsYetEmptyProperties({type: 'NFT'});653  });654  it('Reads yet empty properties of a token (ReFungible)', async () => {655    await testReadsYetEmptyProperties({type: 'ReFungible'});656  });657658  async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {659    await usingApi(async api => {660      const collection = await createCollectionExpectSuccess({mode: mode});661      const token = await createItemExpectSuccess(alice, collection, mode.type);662      await addCollectionAdminExpectSuccess(alice, collection, bob.address);663      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);664665      const propertyKeys: string[] = [];666      let i = 0;667      for (const permission of permissions) {668        for (const signer of permission.signers) {669          const key = i + '_' + signer.address;670          propertyKeys.push(key);671672          await expect(executeTransaction(673            api, 674            alice, 675            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 676          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;677678          await expect(executeTransaction(679            api, 680            signer, 681            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 682          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;683        }684685        i++;686      }687688      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];689      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];690      for (let i = 0; i < properties.length; i++) {691        expect(properties[i].value).to.be.equal('Serotonin increase');692        expect(tokensData[i].value).to.be.equal('Serotonin increase');693      }694    });695  }696  it('Assigns properties to a token according to permissions (NFT)', async () => {697    await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);698  });699  it('Assigns properties to a token according to permissions (ReFungible)', async () => {700    await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);701  });702703  async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {704    await usingApi(async api => {705      const collection = await createCollectionExpectSuccess({mode: mode});706      const token = await createItemExpectSuccess(alice, collection, mode.type);707      await addCollectionAdminExpectSuccess(alice, collection, bob.address);708      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);709710      const propertyKeys: string[] = [];711      let i = 0;712      for (const permission of permissions) {713        if (!permission.permission.mutable) continue;714        715        for (const signer of permission.signers) {716          const key = i + '_' + signer.address;717          propertyKeys.push(key);718  719          await expect(executeTransaction(720            api, 721            alice, 722            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 723          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;724  725          await expect(executeTransaction(726            api, 727            signer, 728            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 729          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;730  731          await expect(executeTransaction(732            api, 733            signer, 734            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 735          ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;736        }737  738        i++;739      }740  741      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];742      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];743      for (let i = 0; i < properties.length; i++) {744        expect(properties[i].value).to.be.equal('Serotonin stable');745        expect(tokensData[i].value).to.be.equal('Serotonin stable');746      }747    });748  }749  it('Changes properties of a token according to permissions (NFT)', async () => {750    await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);751  });752  it('Changes properties of a token according to permissions (ReFungible)', async () => {753    await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);754  });755756  async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {757    await usingApi(async api => {758      const collection = await createCollectionExpectSuccess({mode: mode});759      const token = await createItemExpectSuccess(alice, collection, mode.type);760      await addCollectionAdminExpectSuccess(alice, collection, bob.address);761      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);762763      const propertyKeys: string[] = [];764      let i = 0;765  766      for (const permission of permissions) {767        if (!permission.permission.mutable) continue;768        769        for (const signer of permission.signers) {770          const key = i + '_' + signer.address;771          propertyKeys.push(key);772  773          await expect(executeTransaction(774            api, 775            alice, 776            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 777          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;778  779          await expect(executeTransaction(780            api, 781            signer, 782            api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 783          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;784  785          await expect(executeTransaction(786            api, 787            signer, 788            api.tx.unique.deleteTokenProperties(collection, token, [key]), 789          ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;790        }791        792        i++;793      }794  795      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];796      expect(properties).to.be.empty;797      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];798      expect(tokensData).to.be.empty;799      expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);800    });801  }802  it('Deletes properties of a token according to permissions (NFT)', async () => {803    await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);804  });805  it('Deletes properties of a token according to permissions (ReFungible)', async () => {806    await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);807  });808809  it('Assigns properties to a nested token according to permissions', async () => {810    await usingApi(async api => {811      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});812      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});813      const token = await createItemExpectSuccess(alice, collection, 'NFT');814      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});815      await addCollectionAdminExpectSuccess(alice, collection, bob.address);816      await transferExpectSuccess(collection, token, alice, charlie);817818      const propertyKeys: string[] = [];819      let i = 0;820      for (const permission of permissions) {821        for (const signer of permission.signers) {822          const key = i + '_' + signer.address;823          propertyKeys.push(key);824825          await expect(executeTransaction(826            api, 827            alice, 828            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 829          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;830831          await expect(executeTransaction(832            api, 833            signer, 834            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 835          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;836        }837838        i++;839      }840841      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];842      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];843      for (let i = 0; i < properties.length; i++) {844        expect(properties[i].value).to.be.equal('Serotonin increase');845        expect(tokensData[i].value).to.be.equal('Serotonin increase');846      }847    });848  });849850  it('Changes properties of a nested token according to permissions', async () => {851    await usingApi(async api => {852      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});853      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});854      const token = await createItemExpectSuccess(alice, collection, 'NFT');855      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});856      await addCollectionAdminExpectSuccess(alice, collection, bob.address);857      await transferExpectSuccess(collection, token, alice, charlie);858859      const propertyKeys: string[] = [];860      let i = 0;861      for (const permission of permissions) {862        if (!permission.permission.mutable) continue;863        864        for (const signer of permission.signers) {865          const key = i + '_' + signer.address;866          propertyKeys.push(key);867868          await expect(executeTransaction(869            api, 870            alice, 871            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 872          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;873874          await expect(executeTransaction(875            api, 876            signer, 877            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 878          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;879880          await expect(executeTransaction(881            api, 882            signer, 883            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 884          ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;885        }886887        i++;888      }889890      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];891      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];892      for (let i = 0; i < properties.length; i++) {893        expect(properties[i].value).to.be.equal('Serotonin stable');894        expect(tokensData[i].value).to.be.equal('Serotonin stable');895      }896    });897  });898899  it('Deletes properties of a nested token according to permissions', async () => {900    await usingApi(async api => {901      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});902      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});903      const token = await createItemExpectSuccess(alice, collection, 'NFT');904      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});905      await addCollectionAdminExpectSuccess(alice, collection, bob.address);906      await transferExpectSuccess(collection, token, alice, charlie);907908      const propertyKeys: string[] = [];909      let i = 0;910911      for (const permission of permissions) {912        if (!permission.permission.mutable) continue;913        914        for (const signer of permission.signers) {915          const key = i + '_' + signer.address;916          propertyKeys.push(key);917918          await expect(executeTransaction(919            api, 920            alice, 921            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 922          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;923924          await expect(executeTransaction(925            api, 926            signer, 927            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 928          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;929930          await expect(executeTransaction(931            api, 932            signer, 933            api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 934          ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;935        }936        937        i++;938      }939940      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];941      expect(properties).to.be.empty;942      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];943      expect(tokensData).to.be.empty;944      expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);945    });946  });947});948949describe('Negative Integration Test: Token Properties', () => {950  let collection: number;951  let token: number;952  let originalSpace: number;953  let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];954955  before(async () => {956    await usingApi(async (api, privateKeyWrapper) => {957      alice = privateKeyWrapper('//Alice');958      bob = privateKeyWrapper('//Bob');959      charlie = privateKeyWrapper('//Charlie');960      const dave = privateKeyWrapper('//Dave');961962      constitution = [963        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},964        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},965        {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},966        {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},967        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},968        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},969      ];970    });971  });972973  async function prepare(mode: CollectionMode, pieces: number) {974    collection = await createCollectionExpectSuccess({mode: mode});975    token = await createItemExpectSuccess(alice, collection, mode.type);976    await addCollectionAdminExpectSuccess(alice, collection, bob.address);977    await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);978        979    await usingApi(async api => {980      let i = 0;981      for (const passage of constitution) {982        const signer = passage.signers[0];983        984        await expect(executeTransaction(985          api, 986          alice, 987          api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 988        ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;989  990        await expect(executeTransaction(991          api, 992          signer, 993          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 994        ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;995  996        i++;997      }998  999      originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;1000    }); 1001  }10021003  async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {1004    await prepare(mode, pieces);1005  1006    await usingApi(async api => {1007      let i = -1;1008      for (const forbiddance of constitution) {1009        i++;1010        if (!forbiddance.permission.mutable) continue;1011  1012        await expect(executeTransaction(1013          api, 1014          forbiddance.sinner, 1015          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1016        ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1017  1018        await expect(executeTransaction(1019          api, 1020          forbiddance.sinner, 1021          api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 1022        ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1023      }1024  1025      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1026      expect(properties.consumedSpace).to.be.equal(originalSpace);1027    });1028  }1029  it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {1030    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);1031  });1032  it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {1033    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);1034  });10351036  async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {1037    await prepare(mode, pieces);1038    1039    await usingApi(async api => {1040      let i = -1;1041      for (const permission of constitution) {1042        i++;1043        if (permission.permission.mutable) continue;1044  1045        await expect(executeTransaction(1046          api, 1047          permission.signers[0], 1048          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1049        ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1050  1051        await expect(executeTransaction(1052          api, 1053          permission.signers[0], 1054          api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 1055        ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1056      }1057  1058      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1059      expect(properties.consumedSpace).to.be.equal(originalSpace);1060    });  1061  }1062  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {1063    await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);1064  });1065  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {1066    await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);1067  });10681069  async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {1070    await prepare(mode, pieces);10711072    await usingApi(async api => {1073      await expect(executeTransaction(1074        api, 1075        alice, 1076        api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 1077      ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);1078        1079      await expect(executeTransaction(1080        api, 1081        alice, 1082        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 1083      ), 'on setting a new non-permitted property').to.not.be.rejected;1084  1085      await expect(executeTransaction(1086        api, 1087        alice, 1088        api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 1089      ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);1090  1091      expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;1092      const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1093      expect(properties.consumedSpace).to.be.equal(originalSpace);1094    });1095  }1096  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {1097    await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);1098  });1099  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {1100    await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);1101  });11021103  async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {1104    await prepare(mode, pieces);11051106    await usingApi(async api => {1107      await expect(executeTransaction(1108        api, 1109        alice, 1110        api.tx.unique.setTokenPropertyPermissions(collection, [1111          {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 1112          {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1113        ]), 1114      ), 'on setting a new non-permitted property').to.not.be.rejected;1115  1116      // Mute the general tx parsing error1117      {1118        console.error = () => {};1119        await expect(executeTransaction(1120          api, 1121          alice, 1122          api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 1123        )).to.be.rejected;1124      }1125  1126      await expect(executeTransaction(1127        api, 1128        alice, 1129        api.tx.unique.setTokenProperties(collection, token, [1130          {key: 'a_holy_book', value: 'word '.repeat(3277)}, 1131          {key: 'young_years', value: 'neverending'.repeat(1490)},1132        ]), 1133      )).to.be.rejectedWith(/common\.NoSpaceForProperty/);1134  1135      expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;1136      const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1137      expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1138    });1139  }1140  it('Forbids adding too many properties to a token (NFT)', async () => {1141    await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);1142  });1143  it('Forbids adding too many properties to a token (ReFungible)', async () => {1144    await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);1145  });1146});11471148describe('ReFungible token properties permissions tests', () => {1149  let collection: number;1150  let token: number;11511152  before(async () => {1153    await usingApi(async (api, privateKeyWrapper) => {1154      alice = privateKeyWrapper('//Alice');1155      bob = privateKeyWrapper('//Bob');1156      charlie = privateKeyWrapper('//Charlie');1157    });1158  });11591160  beforeEach(async () => {1161    await usingApi(async api => {1162      collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});1163      token = await createItemExpectSuccess(alice, collection, 'ReFungible');1164      await addCollectionAdminExpectSuccess(alice, collection, bob.address);11651166      await expect(executeTransaction(1167        api, 1168        alice, 1169        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1170      )).to.not.be.rejected;1171    });1172  });11731174  it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {1175    await usingApi(async api => {1176      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1177  1178      await expect(executeTransaction(1179        api, 1180        alice, 1181        api.tx.unique.setTokenProperties(collection, token, [1182          {key: 'key', value: 'word'}, 1183        ]), 1184      )).to.be.rejectedWith(/common\.NoPermission/);1185    });1186  });11871188  it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {1189    await usingApi(async api => {1190      await expect(executeTransaction(1191        api, 1192        alice, 1193        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1194      )).to.not.be.rejected;1195  1196      await expect(executeTransaction(1197        api, 1198        alice, 1199        api.tx.unique.setTokenProperties(collection, token, [1200          {key: 'key', value: 'word'}, 1201        ]), 1202      )).to.be.not.rejected;12031204      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1205  1206      await expect(executeTransaction(1207        api, 1208        alice, 1209        api.tx.unique.setTokenProperties(collection, token, [1210          {key: 'key', value: 'bad word'}, 1211        ]), 1212      )).to.be.rejectedWith(/common\.NoPermission/);1213    });1214  });12151216  it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {1217    await usingApi(async api => {1218      await expect(executeTransaction(1219        api, 1220        alice, 1221        api.tx.unique.setTokenProperties(collection, token, [1222          {key: 'key', value: 'word'}, 1223        ]), 1224      )).to.be.not.rejected;12251226      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1227  1228      await expect(executeTransaction(1229        api, 1230        alice, 1231        api.tx.unique.deleteTokenProperties(collection, token, [1232          'key',1233        ]), 1234      )).to.be.rejectedWith(/common\.NoPermission/);1235    });1236  });1237});
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {default as usingApi} from './substrate/substrate-api';
+import {default as usingApi, executeTransaction} from './substrate/substrate-api';
 import {IKeyringPair} from '@polkadot/types/types';
 import {
   createCollectionExpectSuccess,
@@ -30,6 +30,8 @@
   transfer,
   burnItem,
   repartitionRFT,
+  createCollectionWithPropsExpectSuccess,
+  getDetailedCollectionInfo,
 } from './util/helpers';
 
 import chai from 'chai';
@@ -187,3 +189,26 @@
     });
   });
 });
+
+describe('Test Refungible properties:', () => {
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
+    });
+  });
+  
+  it('Сreate new collection with properties', async () => {
+    await usingApi(async api => {
+      const properties = [{key: 'key1', value: 'val1'}];
+      const propertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
+      const collectionId = await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'ReFungible'},
+        properties: properties,
+        propPerm: propertyPermissions, 
+      });
+      const collection = (await getDetailedCollectionInfo(api, collectionId))!;
+      expect(collection.properties.toHuman()).to.be.deep.equal(properties);
+      expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
+    });
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -284,7 +284,7 @@
   type: 'ReFungible';
 }
 
-type CollectionMode = Nft | Fungible | ReFungible;
+export type CollectionMode = Nft | Fungible | ReFungible;
 
 export type Property = {
   key: any,
@@ -1414,7 +1414,7 @@
       tx = api.tx.unique.createItem(collectionId, to, createData as any);
     }
 
-    const events = await submitTransactionAsync(sender, tx);
+    const events = await executeTransaction(api, sender, tx);
     const result = getCreateItemResult(events);
 
     const itemCountAfter = await getLastTokenId(api, collectionId);