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
before · primitives/data-structs/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::{20	convert::{TryFrom, TryInto},21	fmt,22};23use frame_support::{24	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},25	traits::Get,26	parameter_types,27};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, Permill};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use frame_support::{BoundedVec, traits::ConstU32};36use derivative::Derivative;37use scale_info::TypeInfo;3839// RMRK40use rmrk_traits::{41	CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,42	ResourceTypes, BasicResource, ComposableResource, SlotResource, EquippableList,43};44pub use rmrk_traits::{45	primitives::{46		CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,47		SlotId as RmrkSlotId, PartId as RmrkPartId, ResourceId as RmrkResourceId,48	},49	NftChild as RmrkNftChild, AccountIdOrCollectionNftTuple as RmrkAccountIdOrCollectionNftTuple,50	FixedPart as RmrkFixedPart, SlotPart as RmrkSlotPart,51};5253mod bounded;54pub mod budget;55pub mod mapping;56mod migration;5758pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;59pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;60pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;6162pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {63	100_00064} else {65	1066};67pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {68	100_00069} else {70	1071};72pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {73	204874} else {75	1076};77pub const COLLECTION_ADMINS_LIMIT: u32 = 5;78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;79pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {80	1_000_00081} else {82	1083};8485// Timeouts for item types in passed blocks86pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;87pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;88pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;8990pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9192// Schema limits93pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;94pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;95pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;9697pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;9899pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;100pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;101pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;102103pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;104pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;105pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;106107pub const MAX_AUX_PROPERTY_VALUE_LENGTH: u32 = 2048;108109pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;110pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;111112/// How much items can be created per single113/// create_many call114pub const MAX_ITEMS_PER_BATCH: u32 = 200;115116pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;117118#[derive(119	Encode,120	Decode,121	PartialEq,122	Eq,123	PartialOrd,124	Ord,125	Clone,126	Copy,127	Debug,128	Default,129	TypeInfo,130	MaxEncodedLen,131)]132#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]133pub struct CollectionId(pub u32);134impl EncodeLike<u32> for CollectionId {}135impl EncodeLike<CollectionId> for u32 {}136137#[derive(138	Encode,139	Decode,140	PartialEq,141	Eq,142	PartialOrd,143	Ord,144	Clone,145	Copy,146	Debug,147	Default,148	TypeInfo,149	MaxEncodedLen,150)]151#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]152pub struct TokenId(pub u32);153impl EncodeLike<u32> for TokenId {}154impl EncodeLike<TokenId> for u32 {}155156impl TokenId {157	pub fn try_next(self) -> Result<TokenId, ArithmeticError> {158		self.0159			.checked_add(1)160			.ok_or(ArithmeticError::Overflow)161			.map(Self)162	}163}164165impl From<TokenId> for U256 {166	fn from(t: TokenId) -> Self {167		t.0.into()168	}169}170171impl TryFrom<U256> for TokenId {172	type Error = &'static str;173174	fn try_from(value: U256) -> Result<Self, Self::Error> {175		Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))176	}177}178179#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]180#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]181pub struct TokenData<CrossAccountId> {182	pub properties: Vec<Property>,183	pub owner: Option<CrossAccountId>,184	pub pieces: u128,185}186187pub struct OverflowError;188impl From<OverflowError> for &'static str {189	fn from(_: OverflowError) -> Self {190		"overflow occured"191	}192}193194pub type DecimalPoints = u8;195196#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]197#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]198pub enum CollectionMode {199	NFT,200	// decimal points201	Fungible(DecimalPoints),202	ReFungible,203}204205impl CollectionMode {206	pub fn id(&self) -> u8 {207		match self {208			CollectionMode::NFT => 1,209			CollectionMode::Fungible(_) => 2,210			CollectionMode::ReFungible => 3,211		}212	}213}214215pub trait SponsoringResolve<AccountId, Call> {216	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;217}218219#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]220#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]221pub enum AccessMode {222	Normal,223	AllowList,224}225impl Default for AccessMode {226	fn default() -> Self {227		Self::Normal228	}229}230231#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]232#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]233pub enum SchemaVersion {234	ImageURL,235	Unique,236}237impl Default for SchemaVersion {238	fn default() -> Self {239		Self::ImageURL240	}241}242243#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]244#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]245pub struct Ownership<AccountId> {246	pub owner: AccountId,247	pub fraction: u128,248}249250#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]251#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]252pub enum SponsorshipState<AccountId> {253	/// The fees are applied to the transaction sender254	Disabled,255	Unconfirmed(AccountId),256	/// Transactions are sponsored by specified account257	Confirmed(AccountId),258}259260impl<AccountId> SponsorshipState<AccountId> {261	pub fn sponsor(&self) -> Option<&AccountId> {262		match self {263			Self::Confirmed(sponsor) => Some(sponsor),264			_ => None,265		}266	}267268	pub fn pending_sponsor(&self) -> Option<&AccountId> {269		match self {270			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),271			_ => None,272		}273	}274275	pub fn confirmed(&self) -> bool {276		matches!(self, Self::Confirmed(_))277	}278}279280impl<T> Default for SponsorshipState<T> {281	fn default() -> Self {282		Self::Disabled283	}284}285286/// Used in storage287#[struct_versioning::versioned(version = 2, upper)]288#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]289pub struct Collection<AccountId> {290	pub owner: AccountId,291	pub mode: CollectionMode,292	#[version(..2)]293	pub access: AccessMode,294	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,295	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,296	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,297298	#[version(..2)]299	pub mint_mode: bool,300301	#[version(..2)]302	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,303304	#[version(..2)]305	pub schema_version: SchemaVersion,306	pub sponsorship: SponsorshipState<AccountId>,307308	pub limits: CollectionLimits,309310	#[version(2.., upper(Default::default()))]311	pub permissions: CollectionPermissions,312313	/// Marks that this collection is not "unique", and managed from external.314	#[version(2.., upper(false))]315	pub external_collection: bool,316317	#[version(..2)]318	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,319320	#[version(..2)]321	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,322323	#[version(..2)]324	pub meta_update_permission: MetaUpdatePermission,325}326327/// Used in RPC calls328#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]329#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]330pub struct RpcCollection<AccountId> {331	pub owner: AccountId,332	pub mode: CollectionMode,333	pub name: Vec<u16>,334	pub description: Vec<u16>,335	pub token_prefix: Vec<u8>,336	pub sponsorship: SponsorshipState<AccountId>,337	pub limits: CollectionLimits,338	pub permissions: CollectionPermissions,339	pub token_property_permissions: Vec<PropertyKeyPermission>,340	pub properties: Vec<Property>,341	pub read_only: bool,342}343344#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]345#[derivative(Debug, Default(bound = ""))]346pub struct CreateCollectionData<AccountId> {347	#[derivative(Default(value = "CollectionMode::NFT"))]348	pub mode: CollectionMode,349	pub access: Option<AccessMode>,350	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,351	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,352	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,353	pub pending_sponsor: Option<AccountId>,354	pub limits: Option<CollectionLimits>,355	pub permissions: Option<CollectionPermissions>,356	pub token_property_permissions: CollectionPropertiesPermissionsVec,357	pub properties: CollectionPropertiesVec,358}359360pub type CollectionPropertiesPermissionsVec =361	BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;362363pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;364365/// All fields are wrapped in `Option`s, where None means chain default366// When adding/removing fields from this struct - don't forget to also update clamp_limits367#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]368#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]369pub struct CollectionLimits {370	pub account_token_ownership_limit: Option<u32>,371	pub sponsored_data_size: Option<u32>,372373	/// FIXME should we delete this or repurpose it?374	/// None - setVariableMetadata is not sponsored375	/// Some(v) - setVariableMetadata is sponsored376	///           if there is v block between txs377	pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,378	pub token_limit: Option<u32>,379380	// Timeouts for item types in passed blocks381	pub sponsor_transfer_timeout: Option<u32>,382	pub sponsor_approve_timeout: Option<u32>,383	pub owner_can_transfer: Option<bool>,384	pub owner_can_destroy: Option<bool>,385	pub transfers_enabled: Option<bool>,386}387388impl CollectionLimits {389	pub fn account_token_ownership_limit(&self) -> u32 {390		self.account_token_ownership_limit391			.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)392			.min(MAX_TOKEN_OWNERSHIP)393	}394	pub fn sponsored_data_size(&self) -> u32 {395		self.sponsored_data_size396			.unwrap_or(CUSTOM_DATA_LIMIT)397			.min(CUSTOM_DATA_LIMIT)398	}399	pub fn token_limit(&self) -> u32 {400		self.token_limit401			.unwrap_or(COLLECTION_TOKEN_LIMIT)402			.min(COLLECTION_TOKEN_LIMIT)403	}404	pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {405		self.sponsor_transfer_timeout406			.unwrap_or(default)407			.min(MAX_SPONSOR_TIMEOUT)408	}409	pub fn sponsor_approve_timeout(&self) -> u32 {410		self.sponsor_approve_timeout411			.unwrap_or(SPONSOR_APPROVE_TIMEOUT)412			.min(MAX_SPONSOR_TIMEOUT)413	}414	pub fn owner_can_transfer(&self) -> bool {415		self.owner_can_transfer.unwrap_or(false)416	}417	pub fn owner_can_transfer_instaled(&self) -> bool {418		self.owner_can_transfer.is_some()419	}420	pub fn owner_can_destroy(&self) -> bool {421		self.owner_can_destroy.unwrap_or(true)422	}423	pub fn transfers_enabled(&self) -> bool {424		self.transfers_enabled.unwrap_or(true)425	}426	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {427		match self428			.sponsored_data_rate_limit429			.unwrap_or(SponsoringRateLimit::SponsoringDisabled)430		{431			SponsoringRateLimit::SponsoringDisabled => None,432			SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),433		}434	}435}436437// When adding/removing fields from this struct - don't forget to also update clamp_limits438#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]439#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]440pub struct CollectionPermissions {441	pub access: Option<AccessMode>,442	pub mint_mode: Option<bool>,443	pub nesting: Option<NestingPermissions>,444}445446impl CollectionPermissions {447	pub fn access(&self) -> AccessMode {448		self.access.unwrap_or(AccessMode::Normal)449	}450	pub fn mint_mode(&self) -> bool {451		self.mint_mode.unwrap_or(false)452	}453	pub fn nesting(&self) -> &NestingPermissions {454		static DEFAULT: NestingPermissions = NestingPermissions {455			token_owner: false,456			collection_admin: false,457			restricted: None,458			#[cfg(feature = "runtime-benchmarks")]459			permissive: false,460		};461		self.nesting.as_ref().unwrap_or(&DEFAULT)462	}463}464465type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;466467#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]468#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]469#[derivative(Debug)]470pub struct OwnerRestrictedSet(471	#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]472	#[derivative(Debug(format_with = "bounded::set_debug"))]473	pub OwnerRestrictedSetInner,474);475impl OwnerRestrictedSet {476	pub fn new() -> Self {477		Self(Default::default())478	}479}480impl core::ops::Deref for OwnerRestrictedSet {481	type Target = OwnerRestrictedSetInner;482	fn deref(&self) -> &Self::Target {483		&self.0484	}485}486impl core::ops::DerefMut for OwnerRestrictedSet {487	fn deref_mut(&mut self) -> &mut Self::Target {488		&mut self.0489	}490}491492#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]493#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]494#[derivative(Debug)]495pub struct NestingPermissions {496	/// Owner of token can nest tokens under it497	pub token_owner: bool,498	/// Admin of token collection can nest tokens under token499	pub collection_admin: bool,500	/// If set - only tokens from specified collections can be nested501	pub restricted: Option<OwnerRestrictedSet>,502503	#[cfg(feature = "runtime-benchmarks")]504	/// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`505	pub permissive: bool,506}507508#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]509#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]510pub enum SponsoringRateLimit {511	SponsoringDisabled,512	Blocks(u32),513}514515#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]516#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]517#[derivative(Debug)]518pub struct CreateNftData {519	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]520	#[derivative(Debug(format_with = "bounded::vec_debug"))]521	pub properties: CollectionPropertiesVec,522}523524#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]525#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]526pub struct CreateFungibleData {527	pub value: u128,528}529530#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]531#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]532#[derivative(Debug)]533pub struct CreateReFungibleData {534	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]535	#[derivative(Debug(format_with = "bounded::vec_debug"))]536	pub const_data: BoundedVec<u8, CustomDataLimit>,537	pub pieces: u128,538}539540#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]541#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]542pub enum MetaUpdatePermission {543	ItemOwner,544	Admin,545	None,546}547548#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]549#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]550pub enum CreateItemData {551	NFT(CreateNftData),552	Fungible(CreateFungibleData),553	ReFungible(CreateReFungibleData),554}555556#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]557#[derivative(Debug)]558pub struct CreateNftExData<CrossAccountId> {559	#[derivative(Debug(format_with = "bounded::vec_debug"))]560	pub properties: CollectionPropertiesVec,561	pub owner: CrossAccountId,562}563564#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]565#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]566pub struct CreateRefungibleExData<CrossAccountId> {567	#[derivative(Debug(format_with = "bounded::vec_debug"))]568	pub const_data: BoundedVec<u8, CustomDataLimit>,569	#[derivative(Debug(format_with = "bounded::map_debug"))]570	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,571}572573#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]574#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]575pub enum CreateItemExData<CrossAccountId> {576	NFT(577		#[derivative(Debug(format_with = "bounded::vec_debug"))]578		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,579	),580	Fungible(581		#[derivative(Debug(format_with = "bounded::map_debug"))]582		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,583	),584	/// Many tokens, each may have only one owner585	RefungibleMultipleItems(586		#[derivative(Debug(format_with = "bounded::vec_debug"))]587		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,588	),589	/// Single token, which may have many owners590	RefungibleMultipleOwners(CreateRefungibleExData<CrossAccountId>),591}592593impl CreateItemData {594	pub fn data_size(&self) -> usize {595		match self {596			CreateItemData::ReFungible(data) => data.const_data.len(),597			_ => 0,598		}599	}600}601602impl From<CreateNftData> for CreateItemData {603	fn from(item: CreateNftData) -> Self {604		CreateItemData::NFT(item)605	}606}607608impl From<CreateReFungibleData> for CreateItemData {609	fn from(item: CreateReFungibleData) -> Self {610		CreateItemData::ReFungible(item)611	}612}613614impl From<CreateFungibleData> for CreateItemData {615	fn from(item: CreateFungibleData) -> Self {616		CreateItemData::Fungible(item)617	}618}619620#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]621#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]622// todo possibly rename to be used generally as an address pair623pub struct TokenChild {624	pub token: TokenId,625	pub collection: CollectionId,626}627628#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]629#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]630pub struct CollectionStats {631	pub created: u32,632	pub destroyed: u32,633	pub alive: u32,634}635636#[derive(Encode, Decode, Clone, Debug)]637#[cfg_attr(feature = "std", derive(PartialEq))]638pub struct PhantomType<T>(core::marker::PhantomData<T>);639640impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {641	type Identity = PhantomType<T>;642643	fn type_info() -> scale_info::Type {644		use scale_info::{645			Type, Path,646			build::{FieldsBuilder, UnnamedFields},647			type_params,648		};649		Type::builder()650			.path(Path::new("up_data_structs", "PhantomType"))651			.type_params(type_params!(T))652			.composite(<FieldsBuilder<UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()))653	}654}655impl<T> MaxEncodedLen for PhantomType<T> {656	fn max_encoded_len() -> usize {657		0658	}659}660661pub type BoundedBytes<S> = BoundedVec<u8, S>;662663pub type AuxPropertyValue = BoundedBytes<ConstU32<MAX_AUX_PROPERTY_VALUE_LENGTH>>;664665pub type PropertyKey = BoundedBytes<ConstU32<MAX_PROPERTY_KEY_LENGTH>>;666pub type PropertyValue = BoundedBytes<ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;667668#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]669#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]670pub struct PropertyPermission {671	pub mutable: bool,672	pub collection_admin: bool,673	pub token_owner: bool,674}675676impl PropertyPermission {677	pub fn none() -> Self {678		Self {679			mutable: true,680			collection_admin: false,681			token_owner: false,682		}683	}684}685686#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]687#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]688pub struct Property {689	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]690	pub key: PropertyKey,691692	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]693	pub value: PropertyValue,694}695696impl Into<(PropertyKey, PropertyValue)> for Property {697	fn into(self) -> (PropertyKey, PropertyValue) {698		(self.key, self.value)699	}700}701702#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]703#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]704pub struct PropertyKeyPermission {705	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]706	pub key: PropertyKey,707708	pub permission: PropertyPermission,709}710711impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {712	fn into(self) -> (PropertyKey, PropertyPermission) {713		(self.key, self.permission)714	}715}716717#[derive(Debug)]718pub enum PropertiesError {719	NoSpaceForProperty,720	PropertyLimitReached,721	InvalidCharacterInPropertyKey,722	PropertyKeyIsTooLong,723	EmptyPropertyKey,724}725726#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Clone, Copy)]727pub enum PropertyScope {728	None,729	Rmrk,730}731732impl PropertyScope {733	pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {734		let scope_str: &[u8] = match self {735			Self::None => return Ok(key),736			Self::Rmrk => b"rmrk",737		};738739		[scope_str, b":", key.as_slice()]740			.concat()741			.try_into()742			.map_err(|_| PropertiesError::PropertyKeyIsTooLong)743	}744}745746pub trait TrySetProperty: Sized {747	type Value;748749	fn try_scoped_set(750		&mut self,751		scope: PropertyScope,752		key: PropertyKey,753		value: Self::Value,754	) -> Result<(), PropertiesError>;755756	fn try_scoped_set_from_iter<I, KV>(757		&mut self,758		scope: PropertyScope,759		iter: I,760	) -> Result<(), PropertiesError>761	where762		I: Iterator<Item = KV>,763		KV: Into<(PropertyKey, Self::Value)>,764	{765		for kv in iter {766			let (key, value) = kv.into();767			self.try_scoped_set(scope, key, value)?;768		}769770		Ok(())771	}772773	fn try_set(&mut self, key: PropertyKey, value: Self::Value) -> Result<(), PropertiesError> {774		self.try_scoped_set(PropertyScope::None, key, value)775	}776777	fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>778	where779		I: Iterator<Item = KV>,780		KV: Into<(PropertyKey, Self::Value)>,781	{782		self.try_scoped_set_from_iter(PropertyScope::None, iter)783	}784}785786#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]787#[derivative(Default(bound = ""))]788pub struct PropertiesMap<Value>(789	BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,790);791792impl<Value> PropertiesMap<Value> {793	pub fn new() -> Self {794		Self(BoundedBTreeMap::new())795	}796797	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {798		Self::check_property_key(key)?;799800		Ok(self.0.remove(key))801	}802803	pub fn get(&self, key: &PropertyKey) -> Option<&Value> {804		self.0.get(key)805	}806807	pub fn contains_key(&self, key: &PropertyKey) -> bool {808		self.0.contains_key(key)809	}810811	fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {812		if key.is_empty() {813			return Err(PropertiesError::EmptyPropertyKey);814		}815816		for byte in key.as_slice().iter() {817			let byte = *byte;818819			if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' && byte != b'.' {820				return Err(PropertiesError::InvalidCharacterInPropertyKey);821			}822		}823824		Ok(())825	}826}827828impl<Value> IntoIterator for PropertiesMap<Value> {829	type Item = (PropertyKey, Value);830	type IntoIter = <831		BoundedBTreeMap<832			PropertyKey,833			Value,834			ConstU32<MAX_PROPERTIES_PER_ITEM>835		> as IntoIterator836	>::IntoIter;837838	fn into_iter(self) -> Self::IntoIter {839		self.0.into_iter()840	}841}842843impl<Value> TrySetProperty for PropertiesMap<Value> {844	type Value = Value;845846	fn try_scoped_set(847		&mut self,848		scope: PropertyScope,849		key: PropertyKey,850		value: Self::Value,851	) -> Result<(), PropertiesError> {852		Self::check_property_key(&key)?;853854		let key = scope.apply(key)?;855		self.0856			.try_insert(key, value)857			.map_err(|_| PropertiesError::PropertyLimitReached)?;858859		Ok(())860	}861}862863pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;864865#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, MaxEncodedLen)]866pub struct Properties {867	map: PropertiesMap<PropertyValue>,868	consumed_space: u32,869	space_limit: u32,870}871872impl Properties {873	pub fn new(space_limit: u32) -> Self {874		Self {875			map: PropertiesMap::new(),876			consumed_space: 0,877			space_limit,878		}879	}880881	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {882		let value = self.map.remove(key)?;883884		if let Some(ref value) = value {885			let value_len = value.len() as u32;886			self.consumed_space -= value_len;887		}888889		Ok(value)890	}891892	pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {893		self.map.get(key)894	}895}896897impl IntoIterator for Properties {898	type Item = (PropertyKey, PropertyValue);899	type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;900901	fn into_iter(self) -> Self::IntoIter {902		self.map.into_iter()903	}904}905906impl TrySetProperty for Properties {907	type Value = PropertyValue;908909	fn try_scoped_set(910		&mut self,911		scope: PropertyScope,912		key: PropertyKey,913		value: Self::Value,914	) -> Result<(), PropertiesError> {915		let value_len = value.len();916917		if self.consumed_space as usize + value_len > self.space_limit as usize918			&& !cfg!(feature = "runtime-benchmarks")919		{920			return Err(PropertiesError::NoSpaceForProperty);921		}922923		self.map.try_scoped_set(scope, key, value)?;924925		self.consumed_space += value_len as u32;926927		Ok(())928	}929}930931pub struct CollectionProperties;932933impl Get<Properties> for CollectionProperties {934	fn get() -> Properties {935		Properties::new(MAX_COLLECTION_PROPERTIES_SIZE)936	}937}938939pub struct TokenProperties;940941impl Get<Properties> for TokenProperties {942	fn get() -> Properties {943		Properties::new(MAX_TOKEN_PROPERTIES_SIZE)944	}945}946947// RMRK948// todo document?949parameter_types! {950	#[derive(PartialEq, TypeInfo)]951	pub const RmrkStringLimit: u32 = 128;952	#[derive(PartialEq)]953	pub const RmrkCollectionSymbolLimit: u32 = MAX_TOKEN_PREFIX_LENGTH;954	#[derive(PartialEq)]955	pub const RmrkResourceSymbolLimit: u32 = 10;956	#[derive(PartialEq)]957	pub const RmrkBaseSymbolLimit: u32 = MAX_TOKEN_PREFIX_LENGTH;958	#[derive(PartialEq)]959	pub const RmrkKeyLimit: u32 = 32;960	#[derive(PartialEq)]961	pub const RmrkValueLimit: u32 = 256;962	#[derive(PartialEq)]963	pub const RmrkMaxCollectionsEquippablePerPart: u32 = 100;964	#[derive(PartialEq)]965	pub const MaxPropertiesPerTheme: u32 = 5;966	#[derive(PartialEq)]967	pub const RmrkPartsLimit: u32 = 25;968	#[derive(PartialEq)]969	pub const RmrkMaxPriorities: u32 = 25;970	#[derive(PartialEq)]971	pub const MaxResourcesOnMint: u32 = 100;972}973974impl From<RmrkCollectionId> for CollectionId {975	fn from(id: RmrkCollectionId) -> Self {976		Self(id)977	}978}979980impl From<RmrkNftId> for TokenId {981	fn from(id: RmrkNftId) -> Self {982		Self(id)983	}984}985986pub type RmrkCollectionInfo<AccountId> =987	CollectionInfo<RmrkString, RmrkCollectionSymbol, AccountId>;988pub type RmrkInstanceInfo<AccountId> = NftInfo<AccountId, Permill, RmrkString>;989pub type RmrkResourceInfo = ResourceInfo<RmrkString, RmrkBoundedParts>;990pub type RmrkPropertyInfo = PropertyInfo<RmrkKeyString, RmrkValueString>;991pub type RmrkBaseInfo<AccountId> = BaseInfo<AccountId, RmrkString>;992pub type BoundedEquippableCollectionIds =993	BoundedVec<RmrkCollectionId, RmrkMaxCollectionsEquippablePerPart>;994pub type RmrkPartType = PartType<RmrkString, BoundedEquippableCollectionIds>;995pub type RmrkEquippableList = EquippableList<BoundedEquippableCollectionIds>;996pub type RmrkThemeProperty = ThemeProperty<RmrkString>;997pub type RmrkTheme = Theme<RmrkString, Vec<RmrkThemeProperty>>;998pub type RmrkBoundedTheme = Theme<RmrkString, BoundedVec<RmrkThemeProperty, MaxPropertiesPerTheme>>;999pub type RmrkResourceTypes = ResourceTypes<RmrkString, RmrkBoundedParts>;10001001pub type RmrkBasicResource = BasicResource<RmrkString>;1002pub type RmrkComposableResource = ComposableResource<RmrkString, RmrkBoundedParts>;1003pub type RmrkSlotResource = SlotResource<RmrkString>;10041005pub type RmrkString = BoundedVec<u8, RmrkStringLimit>;1006pub type RmrkCollectionSymbol = BoundedVec<u8, RmrkCollectionSymbolLimit>;1007pub type RmrkBaseSymbol = BoundedVec<u8, RmrkBaseSymbolLimit>;1008pub type RmrkKeyString = BoundedVec<u8, RmrkKeyLimit>;1009pub type RmrkValueString = BoundedVec<u8, RmrkValueLimit>;1010pub type RmrkBoundedResource = BoundedVec<u8, RmrkResourceSymbolLimit>;1011pub type RmrkBoundedParts = BoundedVec<RmrkPartId, RmrkPartsLimit>; // todo make sure it is needed10121013pub type RmrkRpcString = Vec<u8>;1014pub type RmrkThemeName = RmrkRpcString;1015pub type RmrkPropertyKey = RmrkRpcString;
after · primitives/data-structs/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::{20	convert::{TryFrom, TryInto},21	fmt,22};23use frame_support::{24	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},25	traits::Get,26	parameter_types,27};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, Permill};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use frame_support::{BoundedVec, traits::ConstU32};36use derivative::Derivative;37use scale_info::TypeInfo;3839// RMRK40use rmrk_traits::{41	CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,42	ResourceTypes, BasicResource, ComposableResource, SlotResource, EquippableList,43};44pub use rmrk_traits::{45	primitives::{46		CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,47		SlotId as RmrkSlotId, PartId as RmrkPartId, ResourceId as RmrkResourceId,48	},49	NftChild as RmrkNftChild, AccountIdOrCollectionNftTuple as RmrkAccountIdOrCollectionNftTuple,50	FixedPart as RmrkFixedPart, SlotPart as RmrkSlotPart,51};5253mod bounded;54pub mod budget;55pub mod mapping;56mod migration;5758pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;59pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;60pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;6162pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {63	100_00064} else {65	1066};67pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {68	100_00069} else {70	1071};72pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {73	204874} else {75	1076};77pub const COLLECTION_ADMINS_LIMIT: u32 = 5;78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;79pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {80	1_000_00081} else {82	1083};8485// Timeouts for item types in passed blocks86pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;87pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;88pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;8990pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9192// Schema limits93pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;94pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;95pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;9697pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;9899pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;100pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;101pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;102103pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;104pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;105pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;106107pub const MAX_AUX_PROPERTY_VALUE_LENGTH: u32 = 2048;108109pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;110pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;111112/// How much items can be created per single113/// create_many call114pub const MAX_ITEMS_PER_BATCH: u32 = 200;115116pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;117118#[derive(119	Encode,120	Decode,121	PartialEq,122	Eq,123	PartialOrd,124	Ord,125	Clone,126	Copy,127	Debug,128	Default,129	TypeInfo,130	MaxEncodedLen,131)]132#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]133pub struct CollectionId(pub u32);134impl EncodeLike<u32> for CollectionId {}135impl EncodeLike<CollectionId> for u32 {}136137#[derive(138	Encode,139	Decode,140	PartialEq,141	Eq,142	PartialOrd,143	Ord,144	Clone,145	Copy,146	Debug,147	Default,148	TypeInfo,149	MaxEncodedLen,150)]151#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]152pub struct TokenId(pub u32);153impl EncodeLike<u32> for TokenId {}154impl EncodeLike<TokenId> for u32 {}155156impl TokenId {157	pub fn try_next(self) -> Result<TokenId, ArithmeticError> {158		self.0159			.checked_add(1)160			.ok_or(ArithmeticError::Overflow)161			.map(Self)162	}163}164165impl From<TokenId> for U256 {166	fn from(t: TokenId) -> Self {167		t.0.into()168	}169}170171impl TryFrom<U256> for TokenId {172	type Error = &'static str;173174	fn try_from(value: U256) -> Result<Self, Self::Error> {175		Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))176	}177}178179#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]180#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]181pub struct TokenData<CrossAccountId> {182	pub properties: Vec<Property>,183	pub owner: Option<CrossAccountId>,184	pub pieces: u128,185}186187pub struct OverflowError;188impl From<OverflowError> for &'static str {189	fn from(_: OverflowError) -> Self {190		"overflow occured"191	}192}193194pub type DecimalPoints = u8;195196#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]197#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]198pub enum CollectionMode {199	NFT,200	// decimal points201	Fungible(DecimalPoints),202	ReFungible,203}204205impl CollectionMode {206	pub fn id(&self) -> u8 {207		match self {208			CollectionMode::NFT => 1,209			CollectionMode::Fungible(_) => 2,210			CollectionMode::ReFungible => 3,211		}212	}213}214215pub trait SponsoringResolve<AccountId, Call> {216	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;217}218219#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]220#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]221pub enum AccessMode {222	Normal,223	AllowList,224}225impl Default for AccessMode {226	fn default() -> Self {227		Self::Normal228	}229}230231#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]232#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]233pub enum SchemaVersion {234	ImageURL,235	Unique,236}237impl Default for SchemaVersion {238	fn default() -> Self {239		Self::ImageURL240	}241}242243#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]244#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]245pub struct Ownership<AccountId> {246	pub owner: AccountId,247	pub fraction: u128,248}249250#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]251#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]252pub enum SponsorshipState<AccountId> {253	/// The fees are applied to the transaction sender254	Disabled,255	Unconfirmed(AccountId),256	/// Transactions are sponsored by specified account257	Confirmed(AccountId),258}259260impl<AccountId> SponsorshipState<AccountId> {261	pub fn sponsor(&self) -> Option<&AccountId> {262		match self {263			Self::Confirmed(sponsor) => Some(sponsor),264			_ => None,265		}266	}267268	pub fn pending_sponsor(&self) -> Option<&AccountId> {269		match self {270			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),271			_ => None,272		}273	}274275	pub fn confirmed(&self) -> bool {276		matches!(self, Self::Confirmed(_))277	}278}279280impl<T> Default for SponsorshipState<T> {281	fn default() -> Self {282		Self::Disabled283	}284}285286/// Used in storage287#[struct_versioning::versioned(version = 2, upper)]288#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]289pub struct Collection<AccountId> {290	pub owner: AccountId,291	pub mode: CollectionMode,292	#[version(..2)]293	pub access: AccessMode,294	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,295	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,296	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,297298	#[version(..2)]299	pub mint_mode: bool,300301	#[version(..2)]302	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,303304	#[version(..2)]305	pub schema_version: SchemaVersion,306	pub sponsorship: SponsorshipState<AccountId>,307308	pub limits: CollectionLimits,309310	#[version(2.., upper(Default::default()))]311	pub permissions: CollectionPermissions,312313	/// Marks that this collection is not "unique", and managed from external.314	#[version(2.., upper(false))]315	pub external_collection: bool,316317	#[version(..2)]318	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,319320	#[version(..2)]321	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,322323	#[version(..2)]324	pub meta_update_permission: MetaUpdatePermission,325}326327/// Used in RPC calls328#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]329#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]330pub struct RpcCollection<AccountId> {331	pub owner: AccountId,332	pub mode: CollectionMode,333	pub name: Vec<u16>,334	pub description: Vec<u16>,335	pub token_prefix: Vec<u8>,336	pub sponsorship: SponsorshipState<AccountId>,337	pub limits: CollectionLimits,338	pub permissions: CollectionPermissions,339	pub token_property_permissions: Vec<PropertyKeyPermission>,340	pub properties: Vec<Property>,341	pub read_only: bool,342}343344#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]345#[derivative(Debug, Default(bound = ""))]346pub struct CreateCollectionData<AccountId> {347	#[derivative(Default(value = "CollectionMode::NFT"))]348	pub mode: CollectionMode,349	pub access: Option<AccessMode>,350	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,351	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,352	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,353	pub pending_sponsor: Option<AccountId>,354	pub limits: Option<CollectionLimits>,355	pub permissions: Option<CollectionPermissions>,356	pub token_property_permissions: CollectionPropertiesPermissionsVec,357	pub properties: CollectionPropertiesVec,358}359360pub type CollectionPropertiesPermissionsVec =361	BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;362363pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;364365/// All fields are wrapped in `Option`s, where None means chain default366// When adding/removing fields from this struct - don't forget to also update clamp_limits367#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]368#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]369pub struct CollectionLimits {370	pub account_token_ownership_limit: Option<u32>,371	pub sponsored_data_size: Option<u32>,372373	/// FIXME should we delete this or repurpose it?374	/// None - setVariableMetadata is not sponsored375	/// Some(v) - setVariableMetadata is sponsored376	///           if there is v block between txs377	pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,378	pub token_limit: Option<u32>,379380	// Timeouts for item types in passed blocks381	pub sponsor_transfer_timeout: Option<u32>,382	pub sponsor_approve_timeout: Option<u32>,383	pub owner_can_transfer: Option<bool>,384	pub owner_can_destroy: Option<bool>,385	pub transfers_enabled: Option<bool>,386}387388impl CollectionLimits {389	pub fn account_token_ownership_limit(&self) -> u32 {390		self.account_token_ownership_limit391			.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)392			.min(MAX_TOKEN_OWNERSHIP)393	}394	pub fn sponsored_data_size(&self) -> u32 {395		self.sponsored_data_size396			.unwrap_or(CUSTOM_DATA_LIMIT)397			.min(CUSTOM_DATA_LIMIT)398	}399	pub fn token_limit(&self) -> u32 {400		self.token_limit401			.unwrap_or(COLLECTION_TOKEN_LIMIT)402			.min(COLLECTION_TOKEN_LIMIT)403	}404	pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {405		self.sponsor_transfer_timeout406			.unwrap_or(default)407			.min(MAX_SPONSOR_TIMEOUT)408	}409	pub fn sponsor_approve_timeout(&self) -> u32 {410		self.sponsor_approve_timeout411			.unwrap_or(SPONSOR_APPROVE_TIMEOUT)412			.min(MAX_SPONSOR_TIMEOUT)413	}414	pub fn owner_can_transfer(&self) -> bool {415		self.owner_can_transfer.unwrap_or(false)416	}417	pub fn owner_can_transfer_instaled(&self) -> bool {418		self.owner_can_transfer.is_some()419	}420	pub fn owner_can_destroy(&self) -> bool {421		self.owner_can_destroy.unwrap_or(true)422	}423	pub fn transfers_enabled(&self) -> bool {424		self.transfers_enabled.unwrap_or(true)425	}426	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {427		match self428			.sponsored_data_rate_limit429			.unwrap_or(SponsoringRateLimit::SponsoringDisabled)430		{431			SponsoringRateLimit::SponsoringDisabled => None,432			SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),433		}434	}435}436437// When adding/removing fields from this struct - don't forget to also update clamp_limits438#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]439#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]440pub struct CollectionPermissions {441	pub access: Option<AccessMode>,442	pub mint_mode: Option<bool>,443	pub nesting: Option<NestingPermissions>,444}445446impl CollectionPermissions {447	pub fn access(&self) -> AccessMode {448		self.access.unwrap_or(AccessMode::Normal)449	}450	pub fn mint_mode(&self) -> bool {451		self.mint_mode.unwrap_or(false)452	}453	pub fn nesting(&self) -> &NestingPermissions {454		static DEFAULT: NestingPermissions = NestingPermissions {455			token_owner: false,456			collection_admin: false,457			restricted: None,458			#[cfg(feature = "runtime-benchmarks")]459			permissive: false,460		};461		self.nesting.as_ref().unwrap_or(&DEFAULT)462	}463}464465type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;466467#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]468#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]469#[derivative(Debug)]470pub struct OwnerRestrictedSet(471	#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]472	#[derivative(Debug(format_with = "bounded::set_debug"))]473	pub OwnerRestrictedSetInner,474);475impl OwnerRestrictedSet {476	pub fn new() -> Self {477		Self(Default::default())478	}479}480impl core::ops::Deref for OwnerRestrictedSet {481	type Target = OwnerRestrictedSetInner;482	fn deref(&self) -> &Self::Target {483		&self.0484	}485}486impl core::ops::DerefMut for OwnerRestrictedSet {487	fn deref_mut(&mut self) -> &mut Self::Target {488		&mut self.0489	}490}491492#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]493#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]494#[derivative(Debug)]495pub struct NestingPermissions {496	/// Owner of token can nest tokens under it497	pub token_owner: bool,498	/// Admin of token collection can nest tokens under token499	pub collection_admin: bool,500	/// If set - only tokens from specified collections can be nested501	pub restricted: Option<OwnerRestrictedSet>,502503	#[cfg(feature = "runtime-benchmarks")]504	/// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`505	pub permissive: bool,506}507508#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]509#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]510pub enum SponsoringRateLimit {511	SponsoringDisabled,512	Blocks(u32),513}514515#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]516#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]517#[derivative(Debug)]518pub struct CreateNftData {519	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]520	#[derivative(Debug(format_with = "bounded::vec_debug"))]521	pub properties: CollectionPropertiesVec,522}523524#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]525#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]526pub struct CreateFungibleData {527	pub value: u128,528}529530#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]531#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]532#[derivative(Debug)]533pub struct CreateReFungibleData {534	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]535	#[derivative(Debug(format_with = "bounded::vec_debug"))]536	pub const_data: BoundedVec<u8, CustomDataLimit>,537538	pub pieces: u128,539540	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]541	#[derivative(Debug(format_with = "bounded::vec_debug"))]542	pub properties: CollectionPropertiesVec,543}544545#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]546#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]547pub enum MetaUpdatePermission {548	ItemOwner,549	Admin,550	None,551}552553#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]554#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]555pub enum CreateItemData {556	NFT(CreateNftData),557	Fungible(CreateFungibleData),558	ReFungible(CreateReFungibleData),559}560561#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]562#[derivative(Debug)]563pub struct CreateNftExData<CrossAccountId> {564	#[derivative(Debug(format_with = "bounded::vec_debug"))]565	pub properties: CollectionPropertiesVec,566	pub owner: CrossAccountId,567}568569#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]570#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]571pub struct CreateRefungibleExData<CrossAccountId> {572	#[derivative(Debug(format_with = "bounded::vec_debug"))]573	pub const_data: BoundedVec<u8, CustomDataLimit>,574	#[derivative(Debug(format_with = "bounded::map_debug"))]575	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,576	#[derivative(Debug(format_with = "bounded::vec_debug"))]577	pub properties: CollectionPropertiesVec,578}579580#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]581#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]582pub enum CreateItemExData<CrossAccountId> {583	NFT(584		#[derivative(Debug(format_with = "bounded::vec_debug"))]585		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,586	),587	Fungible(588		#[derivative(Debug(format_with = "bounded::map_debug"))]589		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,590	),591	/// Many tokens, each may have only one owner592	RefungibleMultipleItems(593		#[derivative(Debug(format_with = "bounded::vec_debug"))]594		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,595	),596	/// Single token, which may have many owners597	RefungibleMultipleOwners(CreateRefungibleExData<CrossAccountId>),598}599600impl CreateItemData {601	pub fn data_size(&self) -> usize {602		match self {603			CreateItemData::ReFungible(data) => data.const_data.len(),604			_ => 0,605		}606	}607}608609impl From<CreateNftData> for CreateItemData {610	fn from(item: CreateNftData) -> Self {611		CreateItemData::NFT(item)612	}613}614615impl From<CreateReFungibleData> for CreateItemData {616	fn from(item: CreateReFungibleData) -> Self {617		CreateItemData::ReFungible(item)618	}619}620621impl From<CreateFungibleData> for CreateItemData {622	fn from(item: CreateFungibleData) -> Self {623		CreateItemData::Fungible(item)624	}625}626627#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]628#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]629// todo possibly rename to be used generally as an address pair630pub struct TokenChild {631	pub token: TokenId,632	pub collection: CollectionId,633}634635#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]636#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]637pub struct CollectionStats {638	pub created: u32,639	pub destroyed: u32,640	pub alive: u32,641}642643#[derive(Encode, Decode, Clone, Debug)]644#[cfg_attr(feature = "std", derive(PartialEq))]645pub struct PhantomType<T>(core::marker::PhantomData<T>);646647impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {648	type Identity = PhantomType<T>;649650	fn type_info() -> scale_info::Type {651		use scale_info::{652			Type, Path,653			build::{FieldsBuilder, UnnamedFields},654			type_params,655		};656		Type::builder()657			.path(Path::new("up_data_structs", "PhantomType"))658			.type_params(type_params!(T))659			.composite(<FieldsBuilder<UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()))660	}661}662impl<T> MaxEncodedLen for PhantomType<T> {663	fn max_encoded_len() -> usize {664		0665	}666}667668pub type BoundedBytes<S> = BoundedVec<u8, S>;669670pub type AuxPropertyValue = BoundedBytes<ConstU32<MAX_AUX_PROPERTY_VALUE_LENGTH>>;671672pub type PropertyKey = BoundedBytes<ConstU32<MAX_PROPERTY_KEY_LENGTH>>;673pub type PropertyValue = BoundedBytes<ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;674675#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]676#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]677pub struct PropertyPermission {678	pub mutable: bool,679	pub collection_admin: bool,680	pub token_owner: bool,681}682683impl PropertyPermission {684	pub fn none() -> Self {685		Self {686			mutable: true,687			collection_admin: false,688			token_owner: false,689		}690	}691}692693#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]694#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]695pub struct Property {696	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]697	pub key: PropertyKey,698699	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]700	pub value: PropertyValue,701}702703impl Into<(PropertyKey, PropertyValue)> for Property {704	fn into(self) -> (PropertyKey, PropertyValue) {705		(self.key, self.value)706	}707}708709#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]710#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]711pub struct PropertyKeyPermission {712	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]713	pub key: PropertyKey,714715	pub permission: PropertyPermission,716}717718impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {719	fn into(self) -> (PropertyKey, PropertyPermission) {720		(self.key, self.permission)721	}722}723724#[derive(Debug)]725pub enum PropertiesError {726	NoSpaceForProperty,727	PropertyLimitReached,728	InvalidCharacterInPropertyKey,729	PropertyKeyIsTooLong,730	EmptyPropertyKey,731}732733#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Clone, Copy)]734pub enum PropertyScope {735	None,736	Rmrk,737}738739impl PropertyScope {740	pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {741		let scope_str: &[u8] = match self {742			Self::None => return Ok(key),743			Self::Rmrk => b"rmrk",744		};745746		[scope_str, b":", key.as_slice()]747			.concat()748			.try_into()749			.map_err(|_| PropertiesError::PropertyKeyIsTooLong)750	}751}752753pub trait TrySetProperty: Sized {754	type Value;755756	fn try_scoped_set(757		&mut self,758		scope: PropertyScope,759		key: PropertyKey,760		value: Self::Value,761	) -> Result<(), PropertiesError>;762763	fn try_scoped_set_from_iter<I, KV>(764		&mut self,765		scope: PropertyScope,766		iter: I,767	) -> Result<(), PropertiesError>768	where769		I: Iterator<Item = KV>,770		KV: Into<(PropertyKey, Self::Value)>,771	{772		for kv in iter {773			let (key, value) = kv.into();774			self.try_scoped_set(scope, key, value)?;775		}776777		Ok(())778	}779780	fn try_set(&mut self, key: PropertyKey, value: Self::Value) -> Result<(), PropertiesError> {781		self.try_scoped_set(PropertyScope::None, key, value)782	}783784	fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>785	where786		I: Iterator<Item = KV>,787		KV: Into<(PropertyKey, Self::Value)>,788	{789		self.try_scoped_set_from_iter(PropertyScope::None, iter)790	}791}792793#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]794#[derivative(Default(bound = ""))]795pub struct PropertiesMap<Value>(796	BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,797);798799impl<Value> PropertiesMap<Value> {800	pub fn new() -> Self {801		Self(BoundedBTreeMap::new())802	}803804	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {805		Self::check_property_key(key)?;806807		Ok(self.0.remove(key))808	}809810	pub fn get(&self, key: &PropertyKey) -> Option<&Value> {811		self.0.get(key)812	}813814	pub fn contains_key(&self, key: &PropertyKey) -> bool {815		self.0.contains_key(key)816	}817818	fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {819		if key.is_empty() {820			return Err(PropertiesError::EmptyPropertyKey);821		}822823		for byte in key.as_slice().iter() {824			let byte = *byte;825826			if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' && byte != b'.' {827				return Err(PropertiesError::InvalidCharacterInPropertyKey);828			}829		}830831		Ok(())832	}833}834835impl<Value> IntoIterator for PropertiesMap<Value> {836	type Item = (PropertyKey, Value);837	type IntoIter = <838		BoundedBTreeMap<839			PropertyKey,840			Value,841			ConstU32<MAX_PROPERTIES_PER_ITEM>842		> as IntoIterator843	>::IntoIter;844845	fn into_iter(self) -> Self::IntoIter {846		self.0.into_iter()847	}848}849850impl<Value> TrySetProperty for PropertiesMap<Value> {851	type Value = Value;852853	fn try_scoped_set(854		&mut self,855		scope: PropertyScope,856		key: PropertyKey,857		value: Self::Value,858	) -> Result<(), PropertiesError> {859		Self::check_property_key(&key)?;860861		let key = scope.apply(key)?;862		self.0863			.try_insert(key, value)864			.map_err(|_| PropertiesError::PropertyLimitReached)?;865866		Ok(())867	}868}869870pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;871872#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, MaxEncodedLen)]873pub struct Properties {874	map: PropertiesMap<PropertyValue>,875	consumed_space: u32,876	space_limit: u32,877}878879impl Properties {880	pub fn new(space_limit: u32) -> Self {881		Self {882			map: PropertiesMap::new(),883			consumed_space: 0,884			space_limit,885		}886	}887888	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {889		let value = self.map.remove(key)?;890891		if let Some(ref value) = value {892			let value_len = value.len() as u32;893			self.consumed_space -= value_len;894		}895896		Ok(value)897	}898899	pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {900		self.map.get(key)901	}902}903904impl IntoIterator for Properties {905	type Item = (PropertyKey, PropertyValue);906	type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;907908	fn into_iter(self) -> Self::IntoIter {909		self.map.into_iter()910	}911}912913impl TrySetProperty for Properties {914	type Value = PropertyValue;915916	fn try_scoped_set(917		&mut self,918		scope: PropertyScope,919		key: PropertyKey,920		value: Self::Value,921	) -> Result<(), PropertiesError> {922		let value_len = value.len();923924		if self.consumed_space as usize + value_len > self.space_limit as usize925			&& !cfg!(feature = "runtime-benchmarks")926		{927			return Err(PropertiesError::NoSpaceForProperty);928		}929930		self.map.try_scoped_set(scope, key, value)?;931932		self.consumed_space += value_len as u32;933934		Ok(())935	}936}937938pub struct CollectionProperties;939940impl Get<Properties> for CollectionProperties {941	fn get() -> Properties {942		Properties::new(MAX_COLLECTION_PROPERTIES_SIZE)943	}944}945946pub struct TokenProperties;947948impl Get<Properties> for TokenProperties {949	fn get() -> Properties {950		Properties::new(MAX_TOKEN_PROPERTIES_SIZE)951	}952}953954// RMRK955// todo document?956parameter_types! {957	#[derive(PartialEq, TypeInfo)]958	pub const RmrkStringLimit: u32 = 128;959	#[derive(PartialEq)]960	pub const RmrkCollectionSymbolLimit: u32 = MAX_TOKEN_PREFIX_LENGTH;961	#[derive(PartialEq)]962	pub const RmrkResourceSymbolLimit: u32 = 10;963	#[derive(PartialEq)]964	pub const RmrkBaseSymbolLimit: u32 = MAX_TOKEN_PREFIX_LENGTH;965	#[derive(PartialEq)]966	pub const RmrkKeyLimit: u32 = 32;967	#[derive(PartialEq)]968	pub const RmrkValueLimit: u32 = 256;969	#[derive(PartialEq)]970	pub const RmrkMaxCollectionsEquippablePerPart: u32 = 100;971	#[derive(PartialEq)]972	pub const MaxPropertiesPerTheme: u32 = 5;973	#[derive(PartialEq)]974	pub const RmrkPartsLimit: u32 = 25;975	#[derive(PartialEq)]976	pub const RmrkMaxPriorities: u32 = 25;977	#[derive(PartialEq)]978	pub const MaxResourcesOnMint: u32 = 100;979}980981impl From<RmrkCollectionId> for CollectionId {982	fn from(id: RmrkCollectionId) -> Self {983		Self(id)984	}985}986987impl From<RmrkNftId> for TokenId {988	fn from(id: RmrkNftId) -> Self {989		Self(id)990	}991}992993pub type RmrkCollectionInfo<AccountId> =994	CollectionInfo<RmrkString, RmrkCollectionSymbol, AccountId>;995pub type RmrkInstanceInfo<AccountId> = NftInfo<AccountId, Permill, RmrkString>;996pub type RmrkResourceInfo = ResourceInfo<RmrkString, RmrkBoundedParts>;997pub type RmrkPropertyInfo = PropertyInfo<RmrkKeyString, RmrkValueString>;998pub type RmrkBaseInfo<AccountId> = BaseInfo<AccountId, RmrkString>;999pub type BoundedEquippableCollectionIds =1000	BoundedVec<RmrkCollectionId, RmrkMaxCollectionsEquippablePerPart>;1001pub type RmrkPartType = PartType<RmrkString, BoundedEquippableCollectionIds>;1002pub type RmrkEquippableList = EquippableList<BoundedEquippableCollectionIds>;1003pub type RmrkThemeProperty = ThemeProperty<RmrkString>;1004pub type RmrkTheme = Theme<RmrkString, Vec<RmrkThemeProperty>>;1005pub type RmrkBoundedTheme = Theme<RmrkString, BoundedVec<RmrkThemeProperty, MaxPropertiesPerTheme>>;1006pub type RmrkResourceTypes = ResourceTypes<RmrkString, RmrkBoundedParts>;10071008pub type RmrkBasicResource = BasicResource<RmrkString>;1009pub type RmrkComposableResource = ComposableResource<RmrkString, RmrkBoundedParts>;1010pub type RmrkSlotResource = SlotResource<RmrkString>;10111012pub type RmrkString = BoundedVec<u8, RmrkStringLimit>;1013pub type RmrkCollectionSymbol = BoundedVec<u8, RmrkCollectionSymbolLimit>;1014pub type RmrkBaseSymbol = BoundedVec<u8, RmrkBaseSymbolLimit>;1015pub type RmrkKeyString = BoundedVec<u8, RmrkKeyLimit>;1016pub type RmrkValueString = BoundedVec<u8, RmrkValueLimit>;1017pub type RmrkBoundedResource = BoundedVec<u8, RmrkResourceSymbolLimit>;1018pub type RmrkBoundedParts = BoundedVec<RmrkPartId, RmrkPartsLimit>; // todo make sure it is needed10191020pub type RmrkRpcString = Vec<u8>;1021pub type RmrkThemeName = RmrkRpcString;1022pub type RmrkPropertyKey = RmrkRpcString;
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
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -2,6 +2,7 @@
 import usingApi, {executeTransaction} from '../substrate/substrate-api';
 import {
   addCollectionAdminExpectSuccess,
+  CollectionMode,
   createCollectionExpectSuccess,
   setCollectionPermissionsExpectSuccess,
   createItemExpectSuccess,
@@ -23,9 +24,9 @@
     });
   });
 
-  it('Makes sure collectionById supplies required fields', async () => {
+  async function testMakeSureSuppliesRequired(mode: CollectionMode) {
     await usingApi(async api => {
-      const collectionId = await createCollectionExpectSuccess();
+      const collectionId = await createCollectionExpectSuccess({mode: mode});
 
       const collectionOption = await api.rpc.unique.collectionById(collectionId);
       expect(collectionOption.isSome).to.be.true;
@@ -57,7 +58,15 @@
       expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
       expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);
     });
+  }
+
+  it('Makes sure collectionById supplies required fields for NFT', async () => {
+    await testMakeSureSuppliesRequired({type: 'NFT'});
   });
+
+  it('Makes sure collectionById supplies required fields for ReFungible', async () => {
+    await testMakeSureSuppliesRequired({type: 'ReFungible'});
+  });
 });
 
 // ---------- COLLECTION PROPERTIES
@@ -79,9 +88,10 @@
     });
   });
 
-  it('Sets properties for a collection', async () => {
+
+  async function testSetsPropertiesForCollection(mode: string) {
     await usingApi(async api => {
-      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));
       const {collectionId} = getCreateCollectionResult(events);
 
       // As owner
@@ -106,48 +116,54 @@
         {key: 'black_hole', value: ''},
       ]);
     });
+  }
+  it('Sets properties for a NFT collection', async () => {
+    await testSetsPropertiesForCollection('NFT');
   });
+  it('Sets properties for a ReFungible collection', async () => {
+    await testSetsPropertiesForCollection('ReFungible');
+  });
 
-  it('Check valid names for collection properties keys', async () => {
+  async function testCheckValidNames(mode: string) {
     await usingApi(async api => {
-      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));
       const {collectionId} = getCreateCollectionResult(events);
-
+  
       // alpha symbols
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 
       )).to.not.be.rejected;
-
+  
       // numeric symbols
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 
       )).to.not.be.rejected;
-
+  
       // underscore symbol
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 
       )).to.not.be.rejected;
-
+  
       // dash symbol
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 
       )).to.not.be.rejected;
-
+  
       // underscore symbol
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 
       )).to.not.be.rejected;
-
+  
       const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];
       const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();
       expect(properties).to.be.deep.equal([
@@ -158,54 +174,72 @@
         {key: 'build.rs', value: ''},
       ]);
     });
+  }
+  it('Check valid names for NFT collection properties keys', async () => {
+    await testCheckValidNames('NFT');
+  });
+  it('Check valid names for ReFungible collection properties keys', async () => {
+    await testCheckValidNames('ReFungible');
   });
 
-  it('Changes properties of a collection', async () => {
+  async function testChangesProperties(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 
       )).to.not.be.rejected;
-
+  
       // Mutate the properties
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 
       )).to.not.be.rejected;
-
+  
       const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
       expect(properties).to.be.deep.equal([
         {key: 'electron', value: 'bonded'},
         {key: 'black_hole', value: 'LIGO'},
       ]);
     });
+  }
+  it('Changes properties of a NFT collection', async () => {
+    await testChangesProperties({type: 'NFT'});
+  });
+  it('Changes properties of a ReFungible collection', async () => {
+    await testChangesProperties({type: 'ReFungible'});
   });
 
-  it('Deletes properties of a collection', async () => {
+  async function testDeleteProperties(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 
       )).to.not.be.rejected;
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.deleteCollectionProperties(collection, ['electron']), 
       )).to.not.be.rejected;
-
+  
       const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
       expect(properties).to.be.deep.equal([
         {key: 'black_hole', value: 'LIGO'},
       ]);
-    });
+    });  
+  }
+  it('Deletes properties of a NFT collection', async () => {
+    await testDeleteProperties({type: 'NFT'});
+  });
+  it('Deletes properties of a ReFungible collection', async () => {
+    await testDeleteProperties({type: 'ReFungible'});
   });
 });
 
@@ -217,10 +251,10 @@
     });
   });
   
-  it('Fails to set properties in a collection if not its onwer/administrator', async () => {
+  async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         bob, 
@@ -230,14 +264,20 @@
       const properties = (await api.query.common.collectionProperties(collection)).toJSON();
       expect(properties.map).to.be.empty;
       expect(properties.consumedSpace).to.equal(0);
-    });
+    });  
+  }
+  it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {
+    await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});
   });
+  it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {
+    await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});
+  });
   
-  it('Fails to set properties that exceed the limits', async () => {
+  async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
+      const collection = await createCollectionExpectSuccess({mode: mode});
       const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 
-
+  
       // Mute the general tx parsing error, too many bytes to process
       {
         console.error = () => {};
@@ -247,10 +287,10 @@
           api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 
         )).to.be.rejected;
       }
-
+  
       let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();
       expect(properties).to.be.empty;
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
@@ -259,16 +299,22 @@
           {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 
         ]), 
       )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
-
+  
       properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();
       expect(properties).to.be.empty;
-    });
+    });  
+  }
+  it('Fails to set properties that exceed the limits (NFT)', async () => {
+    await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});
+  });
+  it('Fails to set properties that exceed the limits (ReFungible)', async () => {
+    await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});
   });
   
-  it('Fails to set more properties than it is allowed', async () => {
+  async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       const propertiesToBeSet = [];
       for (let i = 0; i < 65; i++) {
         propertiesToBeSet.push({
@@ -276,29 +322,35 @@
           value: Math.random() > 0.5 ? 'high' : 'low',
         });
       }
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 
       )).to.be.rejectedWith(/common\.PropertyLimitReached/);
-
+  
       const properties = (await api.query.common.collectionProperties(collection)).toJSON();
       expect(properties.map).to.be.empty;
       expect(properties.consumedSpace).to.equal(0);
-    });
+    });  
+  }
+  it('Fails to set more properties than it is allowed (NFT)', async () => {
+    await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});
   });
-
-  it('Fails to set properties with invalid names', async () => {
+  it('Fails to set more properties than it is allowed (ReFungible)', async () => {
+    await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});
+  });
+  
+  async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       const invalidProperties = [
         [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],
         [{key: 'Mr/Sandman', value: 'Bring me a gene'}],
         [{key: 'déjà vu', value: 'hmm...'}],
       ];
-
+  
       for (let i = 0; i < invalidProperties.length; i++) {
         await expect(executeTransaction(
           api, 
@@ -306,13 +358,13 @@
           api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 
         ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
       }
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 
       ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
@@ -320,14 +372,14 @@
           {key: 'CRISPR-Cas9', value: 'rewriting nature!'},
         ]), 
       ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
-
+  
       const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');
-
+  
       const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();
       expect(properties).to.be.deep.equal([
         {key: 'CRISPR-Cas9', value: 'rewriting nature!'},
       ]);
-
+  
       for (let i = 0; i < invalidProperties.length; i++) {
         await expect(executeTransaction(
           api, 
@@ -336,7 +388,13 @@
         ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
       }
     });
+  }
+  it('Fails to set properties with invalid names (NFT)', async () => {
+    await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});
   });
+  it('Fails to set properties with invalid names (ReFungible)', async () => {
+    await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});
+  });
 });
 
 // ---------- ACCESS RIGHTS
@@ -357,54 +415,66 @@
     });
   });
   
-  it('Sets access rights to properties of a collection', async () => {
+  async function testSetsAccessRightsToProperties(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 
       )).to.not.be.rejected;
-
+  
       await addCollectionAdminExpectSuccess(alice, collection, bob.address);
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 
       )).to.not.be.rejected;
-
+  
       const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();
       expect(propertyRights).to.be.deep.equal([
         {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},
         {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},
       ]);
-    });
+    });  
+  }
+  it('Sets access rights to properties of a collection (NFT)', async () => {
+    await testSetsAccessRightsToProperties({type: 'NFT'});
   });
+  it('Sets access rights to properties of a collection (ReFungible)', async () => {
+    await testSetsAccessRightsToProperties({type: 'ReFungible'});
+  });
   
-  it('Changes access rights to properties of a collection', async () => {
+  async function testChangesAccessRightsToProperty(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 
       )).to.not.be.rejected;
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 
       )).to.not.be.rejected;
-
+  
       const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
       expect(propertyRights).to.be.deep.equal([
         {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
       ]);
     });
+  }
+  it('Changes access rights to properties of a NFT collection', async () => {
+    await testChangesAccessRightsToProperty({type: 'NFT'});
   });
+  it('Changes access rights to properties of a ReFungible collection', async () => {
+    await testChangesAccessRightsToProperty({type: 'ReFungible'});
+  });
 });
 
 describe('Negative Integration Test: Access Rights to Token Properties', () => {
@@ -415,25 +485,31 @@
     });
   });
 
-  it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {
+  async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         bob, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 
       )).to.be.rejectedWith(/common\.NoPermission/);
-
+  
       const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();
       expect(propertyRights).to.be.empty;
     });
+  }
+  it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {
+    await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});
   });
+  it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {
+    await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});
+  });
 
-  it('Prevents from adding too many possible properties', async () => {
+  async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       const constitution = [];
       for (let i = 0; i < 65; i++) {
         constitution.push({
@@ -441,51 +517,63 @@
           permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},
         });
       }
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, constitution), 
       )).to.be.rejectedWith(/common\.PropertyLimitReached/);
-
+  
       const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();
       expect(propertyRights).to.be.empty;
-    });
+    });  
+  }
+  it('Prevents from adding too many possible properties (NFT)', async () => {
+    await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});
   });
+  it('Prevents from adding too many possible properties (ReFungible)', async () => {
+    await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});
+  });
 
-  it('Prevents access rights to be modified if constant', async () => {
+  async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 
       )).to.not.be.rejected;
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 
       )).to.be.rejectedWith(/common\.NoPermission/);
-
+  
       const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
       expect(propertyRights).to.deep.equal([
         {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
       ]);
-    });
+    });  
+  }
+  it('Prevents access rights to be modified if constant (NFT)', async () => {
+    await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});
+  });
+  it('Prevents access rights to be modified if constant (ReFungible)', async () => {
+    await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});
   });
 
-  it('Prevents adding properties with invalid names', async () => {
+  async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-
+      const collection = await createCollectionExpectSuccess({mode: mode});
+  
       const invalidProperties = [
         [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],
         [{key: 'G#4', permission: {tokenOwner: true}}],
         [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],
       ];
-
+  
       for (let i = 0; i < invalidProperties.length; i++) {
         await expect(executeTransaction(
           api, 
@@ -493,13 +581,13 @@
           api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 
         ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
       }
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 
       ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);
-
+  
       const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string
       await expect(executeTransaction(
         api, 
@@ -508,30 +596,33 @@
           {key: correctKey, permission: {collectionAdmin: true}},
         ]), 
       ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
-
+  
       const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');
-
+  
       const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();
       expect(propertyRights).to.be.deep.equal([
         {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},
       ]);
     });
+  }
+  it('Prevents adding properties with invalid names (NFT)', async () => {
+    await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});
+  });
+  it('Prevents adding properties with invalid names (ReFungible)', async () => {
+    await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});
   });
 });
 
 // ---------- TOKEN PROPERTIES
 
 describe('Integration Test: Token Properties', () => {
-  let collection: number;
-  let token: number;
-  let nestedToken: number;
   let permissions: {permission: any, signers: IKeyringPair[]}[];
 
   before(async () => {
     await usingApi(async (api, privateKeyWrapper) => {
-      alice = privateKeyWrapper('//Alice');
-      bob = privateKeyWrapper('//Bob');
-      charlie = privateKeyWrapper('//Charlie');
+      alice = privateKeyWrapper('//Alice'); // collection owner
+      bob = privateKeyWrapper('//Bob'); // collection admin
+      charlie = privateKeyWrapper('//Charlie'); // token owner
 
       permissions = [
         {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},
@@ -543,36 +634,34 @@
       ];
     });
   });
-
-  beforeEach(async () => {
-    await usingApi(async () => {
-      collection = await createCollectionExpectSuccess();
-      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
-
-      token = await createItemExpectSuccess(alice, collection, 'NFT');
-      nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
-
-      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
-      await transferExpectSuccess(collection, token, alice, charlie);
-    });
-  });
   
-  it('Reads yet empty properties of a token', async () => {
+  async function testReadsYetEmptyProperties(mode: CollectionMode) {
     await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess();
-      const token = await createItemExpectSuccess(alice, collection, 'NFT');
+      const collection = await createCollectionExpectSuccess({mode: mode});
+      const token = await createItemExpectSuccess(alice, collection, mode.type);
   
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.map).to.be.empty;
       expect(properties.consumedSpace).to.be.equal(0);
-
+  
       const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;
       expect(tokenData).to.be.empty;
     });
+  }
+  it('Reads yet empty properties of a token (NFT)', async () => {
+    await testReadsYetEmptyProperties({type: 'NFT'});
   });
+  it('Reads yet empty properties of a token (ReFungible)', async () => {
+    await testReadsYetEmptyProperties({type: 'ReFungible'});
+  });
 
-  it('Assigns properties to a token according to permissions', async () => {
+  async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: mode});
+      const token = await createItemExpectSuccess(alice, collection, mode.type);
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
       const propertyKeys: string[] = [];
       let i = 0;
       for (const permission of permissions) {
@@ -603,10 +692,21 @@
         expect(tokensData[i].value).to.be.equal('Serotonin increase');
       }
     });
+  }
+  it('Assigns properties to a token according to permissions (NFT)', async () => {
+    await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);
+  });
+  it('Assigns properties to a token according to permissions (ReFungible)', async () => {
+    await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);
   });
 
-  it('Changes properties of a token according to permissions', async () => {
+  async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: mode});
+      const token = await createItemExpectSuccess(alice, collection, mode.type);
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
       const propertyKeys: string[] = [];
       let i = 0;
       for (const permission of permissions) {
@@ -615,29 +715,29 @@
         for (const signer of permission.signers) {
           const key = i + '_' + signer.address;
           propertyKeys.push(key);
-
+  
           await expect(executeTransaction(
             api, 
             alice, 
             api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 
           ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
           await expect(executeTransaction(
             api, 
             signer, 
             api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 
           ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
           await expect(executeTransaction(
             api, 
             signer, 
             api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 
           ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;
         }
-
+  
         i++;
       }
-
+  
       const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
       const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
       for (let i = 0; i < properties.length; i++) {
@@ -645,32 +745,43 @@
         expect(tokensData[i].value).to.be.equal('Serotonin stable');
       }
     });
+  }
+  it('Changes properties of a token according to permissions (NFT)', async () => {
+    await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);
   });
+  it('Changes properties of a token according to permissions (ReFungible)', async () => {
+    await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);
+  });
 
-  it('Deletes properties of a token according to permissions', async () => {
+  async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: mode});
+      const token = await createItemExpectSuccess(alice, collection, mode.type);
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
       const propertyKeys: string[] = [];
       let i = 0;
-
+  
       for (const permission of permissions) {
         if (!permission.permission.mutable) continue;
         
         for (const signer of permission.signers) {
           const key = i + '_' + signer.address;
           propertyKeys.push(key);
-
+  
           await expect(executeTransaction(
             api, 
             alice, 
             api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 
           ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
           await expect(executeTransaction(
             api, 
             signer, 
             api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 
           ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
           await expect(executeTransaction(
             api, 
             signer, 
@@ -680,17 +791,30 @@
         
         i++;
       }
-
+  
       const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];
       expect(properties).to.be.empty;
       const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];
       expect(tokensData).to.be.empty;
       expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);
     });
+  }
+  it('Deletes properties of a token according to permissions (NFT)', async () => {
+    await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);
   });
+  it('Deletes properties of a token according to permissions (ReFungible)', async () => {
+    await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);
+  });
 
   it('Assigns properties to a nested token according to permissions', async () => {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+      const token = await createItemExpectSuccess(alice, collection, 'NFT');
+      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie);
+
       const propertyKeys: string[] = [];
       let i = 0;
       for (const permission of permissions) {
@@ -725,6 +849,13 @@
 
   it('Changes properties of a nested token according to permissions', async () => {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+      const token = await createItemExpectSuccess(alice, collection, 'NFT');
+      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie);
+
       const propertyKeys: string[] = [];
       let i = 0;
       for (const permission of permissions) {
@@ -767,6 +898,13 @@
 
   it('Deletes properties of a nested token according to permissions', async () => {
     await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+      const token = await createItemExpectSuccess(alice, collection, 'NFT');
+      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+      await transferExpectSuccess(collection, token, alice, charlie);
+
       const propertyKeys: string[] = [];
       let i = 0;
 
@@ -832,11 +970,11 @@
     });
   });
 
-  beforeEach(async () => {
-    collection = await createCollectionExpectSuccess();
-    token = await createItemExpectSuccess(alice, collection, 'NFT');
+  async function prepare(mode: CollectionMode, pieces: number) {
+    collection = await createCollectionExpectSuccess({mode: mode});
+    token = await createItemExpectSuccess(alice, collection, mode.type);
     await addCollectionAdminExpectSuccess(alice, collection, bob.address);
-    await transferExpectSuccess(collection, token, alice, charlie);
+    await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
         
     await usingApi(async api => {
       let i = 0;
@@ -848,71 +986,89 @@
           alice, 
           api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 
         ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
         await expect(executeTransaction(
           api, 
           signer, 
           api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 
         ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+  
         i++;
       }
-
+  
       originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;
-    });
-  });
+    }); 
+  }
 
-  it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {
+  async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {
+    await prepare(mode, pieces);
+  
     await usingApi(async api => {
       let i = -1;
       for (const forbiddance of constitution) {
         i++;
         if (!forbiddance.permission.mutable) continue;
-
+  
         await expect(executeTransaction(
           api, 
           forbiddance.sinner, 
           api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 
         ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
-
+  
         await expect(executeTransaction(
           api, 
           forbiddance.sinner, 
           api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 
         ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
       }
-
+  
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.consumedSpace).to.be.equal(originalSpace);
     });
+  }
+  it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {
+    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);
   });
+  it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {
+    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);
+  });
 
-  it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {
+  async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {
+    await prepare(mode, pieces);
+    
     await usingApi(async api => {
       let i = -1;
       for (const permission of constitution) {
         i++;
         if (permission.permission.mutable) continue;
-
+  
         await expect(executeTransaction(
           api, 
           permission.signers[0], 
           api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 
         ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
-
+  
         await expect(executeTransaction(
           api, 
           permission.signers[0], 
           api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 
         ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
       }
-
+  
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.consumedSpace).to.be.equal(originalSpace);
-    });
+    });  
+  }
+  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {
+    await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);
   });
+  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {
+    await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);
+  });
+
+  async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {
+    await prepare(mode, pieces);
 
-  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {
     await usingApi(async api => {
       await expect(executeTransaction(
         api, 
@@ -925,20 +1081,28 @@
         alice, 
         api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 
       ), 'on setting a new non-permitted property').to.not.be.rejected;
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
         api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 
       ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);
-
+  
       expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.consumedSpace).to.be.equal(originalSpace);
     });
+  }
+  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {
+    await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);
+  });
+  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {
+    await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);
   });
 
-  it('Forbids adding too many properties to a token', async () => {
+  async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {
+    await prepare(mode, pieces);
+
     await usingApi(async api => {
       await expect(executeTransaction(
         api, 
@@ -948,7 +1112,7 @@
           {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},
         ]), 
       ), 'on setting a new non-permitted property').to.not.be.rejected;
-
+  
       // Mute the general tx parsing error
       {
         console.error = () => {};
@@ -958,7 +1122,7 @@
           api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 
         )).to.be.rejected;
       }
-
+  
       await expect(executeTransaction(
         api, 
         alice, 
@@ -967,10 +1131,107 @@
           {key: 'young_years', value: 'neverending'.repeat(1490)},
         ]), 
       )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
-
+  
       expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;
       const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);
     });
+  }
+  it('Forbids adding too many properties to a token (NFT)', async () => {
+    await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);
+  });
+  it('Forbids adding too many properties to a token (ReFungible)', async () => {
+    await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);
+  });
+});
+
+describe('ReFungible token properties permissions tests', () => {
+  let collection: number;
+  let token: number;
+
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
+      charlie = privateKeyWrapper('//Charlie');
+    });
+  });
+
+  beforeEach(async () => {
+    await usingApi(async api => {
+      collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+      token = await createItemExpectSuccess(alice, collection, 'ReFungible');
+      await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 
+      )).to.not.be.rejected;
+    });
+  });
+
+  it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {
+    await usingApi(async api => {
+      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+  
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenProperties(collection, token, [
+          {key: 'key', value: 'word'}, 
+        ]), 
+      )).to.be.rejectedWith(/common\.NoPermission/);
+    });
+  });
+
+  it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {
+    await usingApi(async api => {
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 
+      )).to.not.be.rejected;
+  
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenProperties(collection, token, [
+          {key: 'key', value: 'word'}, 
+        ]), 
+      )).to.be.not.rejected;
+
+      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+  
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenProperties(collection, token, [
+          {key: 'key', value: 'bad word'}, 
+        ]), 
+      )).to.be.rejectedWith(/common\.NoPermission/);
+    });
+  });
+
+  it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {
+    await usingApi(async api => {
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setTokenProperties(collection, token, [
+          {key: 'key', value: 'word'}, 
+        ]), 
+      )).to.be.not.rejected;
+
+      await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+  
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.deleteTokenProperties(collection, token, [
+          'key',
+        ]), 
+      )).to.be.rejectedWith(/common\.NoPermission/);
+    });
   });
 });
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);