difftreelog
Merge pull request #422 from UniqueNetwork/feature/CORE-410
in: master
Feature/core 410 Properties for RFT
21 files changed
Cargo.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",
pallets/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,
pallets/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)?}
}
pallets/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))
}
}
pallets/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"
pallets/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.
pallets/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);
pallets/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> {
pallets/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)
+ }
}
pallets/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))
}
primitives/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 = [
primitives/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
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -534,7 +534,12 @@
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
+
pub pieces: u128,
+
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
+ pub properties: CollectionPropertiesVec,
}
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
@@ -568,6 +573,8 @@
pub const_data: BoundedVec<u8, CustomDataLimit>,
#[derivative(Debug(format_with = "bounded::map_debug"))]
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
+ pub properties: CollectionPropertiesVec,
}
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
tests/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;
+ }
});
});
tests/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]>;
tests/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 */
tests/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>
tests/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) */
tests/src/nesting/properties.test.tsdiffbeforeafterboth2import usingApi, {executeTransaction} from '../substrate/substrate-api';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {3import {4 addCollectionAdminExpectSuccess,4 addCollectionAdminExpectSuccess,5 CollectionMode,5 createCollectionExpectSuccess,6 createCollectionExpectSuccess,6 setCollectionPermissionsExpectSuccess,7 setCollectionPermissionsExpectSuccess,7 createItemExpectSuccess,8 createItemExpectSuccess,23 });24 });24 });25 });252626 it('Makes sure collectionById supplies required fields', async () => {27 async function testMakeSureSuppliesRequired(mode: CollectionMode) {27 await usingApi(async api => {28 await usingApi(async api => {28 const collectionId = await createCollectionExpectSuccess();29 const collectionId = await createCollectionExpectSuccess({mode: mode});293030 const collectionOption = await api.rpc.unique.collectionById(collectionId);31 const collectionOption = await api.rpc.unique.collectionById(collectionId);31 expect(collectionOption.isSome).to.be.true;32 expect(collectionOption.isSome).to.be.true;57 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);58 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);58 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);59 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);59 });60 });61 }6263 it('Makes sure collectionById supplies required fields for NFT', async () => {64 await testMakeSureSuppliesRequired({type: 'NFT'});60 });65 });6667 it('Makes sure collectionById supplies required fields for ReFungible', async () => {68 await testMakeSureSuppliesRequired({type: 'ReFungible'});69 });61});70});627163// ---------- COLLECTION PROPERTIES72// ---------- COLLECTION PROPERTIES79 });88 });80 });89 });81909182 it('Sets properties for a collection', async () => {92 async function testSetsPropertiesForCollection(mode: string) {83 await usingApi(async api => {93 await usingApi(async api => {84 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));94 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));85 const {collectionId} = getCreateCollectionResult(events);95 const {collectionId} = getCreateCollectionResult(events);869687 // As owner97 // As owner106 {key: 'black_hole', value: ''},116 {key: 'black_hole', value: ''},107 ]);117 ]);108 });118 });119 }120 it('Sets properties for a NFT collection', async () => {121 await testSetsPropertiesForCollection('NFT');109 });122 });123 it('Sets properties for a ReFungible collection', async () => {124 await testSetsPropertiesForCollection('ReFungible');125 });110126111 it('Check valid names for collection properties keys', async () => {127 async function testCheckValidNames(mode: string) {112 await usingApi(async api => {128 await usingApi(async api => {113 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));129 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));114 const {collectionId} = getCreateCollectionResult(events);130 const {collectionId} = getCreateCollectionResult(events);115131 116 // alpha symbols132 // alpha symbols117 await expect(executeTransaction(133 await expect(executeTransaction(118 api, 134 api, 119 bob, 135 bob, 120 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 136 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 121 )).to.not.be.rejected;137 )).to.not.be.rejected;122138 123 // numeric symbols139 // numeric symbols124 await expect(executeTransaction(140 await expect(executeTransaction(125 api, 141 api, 126 bob, 142 bob, 127 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 143 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 128 )).to.not.be.rejected;144 )).to.not.be.rejected;129145 130 // underscore symbol146 // underscore symbol131 await expect(executeTransaction(147 await expect(executeTransaction(132 api, 148 api, 133 bob, 149 bob, 134 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 150 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 135 )).to.not.be.rejected;151 )).to.not.be.rejected;136152 137 // dash symbol153 // dash symbol138 await expect(executeTransaction(154 await expect(executeTransaction(139 api, 155 api, 140 bob, 156 bob, 141 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 157 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 142 )).to.not.be.rejected;158 )).to.not.be.rejected;143159 144 // underscore symbol160 // underscore symbol145 await expect(executeTransaction(161 await expect(executeTransaction(146 api, 162 api, 147 bob, 163 bob, 148 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 164 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 149 )).to.not.be.rejected;165 )).to.not.be.rejected;150166 151 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];167 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];152 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();168 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();153 expect(properties).to.be.deep.equal([169 expect(properties).to.be.deep.equal([158 {key: 'build.rs', value: ''},174 {key: 'build.rs', value: ''},159 ]);175 ]);160 });176 });177 }178 it('Check valid names for NFT collection properties keys', async () => {179 await testCheckValidNames('NFT');161 });180 });181 it('Check valid names for ReFungible collection properties keys', async () => {182 await testCheckValidNames('ReFungible');183 });162184163 it('Changes properties of a collection', async () => {185 async function testChangesProperties(mode: CollectionMode) {164 await usingApi(async api => {186 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess();187 const collection = await createCollectionExpectSuccess({mode: mode});166188 167 await expect(executeTransaction(189 await expect(executeTransaction(168 api, 190 api, 169 alice, 191 alice, 170 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 192 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 171 )).to.not.be.rejected;193 )).to.not.be.rejected;172194 173 // Mutate the properties195 // Mutate the properties174 await expect(executeTransaction(196 await expect(executeTransaction(175 api, 197 api, 176 alice, 198 alice, 177 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 199 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 178 )).to.not.be.rejected;200 )).to.not.be.rejected;179201 180 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();202 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();181 expect(properties).to.be.deep.equal([203 expect(properties).to.be.deep.equal([182 {key: 'electron', value: 'bonded'},204 {key: 'electron', value: 'bonded'},183 {key: 'black_hole', value: 'LIGO'},205 {key: 'black_hole', value: 'LIGO'},184 ]);206 ]);185 });207 });208 }209 it('Changes properties of a NFT collection', async () => {210 await testChangesProperties({type: 'NFT'});186 });211 });212 it('Changes properties of a ReFungible collection', async () => {213 await testChangesProperties({type: 'ReFungible'});214 });187215188 it('Deletes properties of a collection', async () => {216 async function testDeleteProperties(mode: CollectionMode) {189 await usingApi(async api => {217 await usingApi(async api => {190 const collection = await createCollectionExpectSuccess();218 const collection = await createCollectionExpectSuccess({mode: mode});191219 192 await expect(executeTransaction(220 await expect(executeTransaction(193 api, 221 api, 194 alice, 222 alice, 195 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 223 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 196 )).to.not.be.rejected;224 )).to.not.be.rejected;197225 198 await expect(executeTransaction(226 await expect(executeTransaction(199 api, 227 api, 200 alice, 228 alice, 201 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 229 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 202 )).to.not.be.rejected;230 )).to.not.be.rejected;203231 204 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();232 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();205 expect(properties).to.be.deep.equal([233 expect(properties).to.be.deep.equal([206 {key: 'black_hole', value: 'LIGO'},234 {key: 'black_hole', value: 'LIGO'},207 ]);235 ]);208 });236 }); 237 }238 it('Deletes properties of a NFT collection', async () => {239 await testDeleteProperties({type: 'NFT'});209 });240 });241 it('Deletes properties of a ReFungible collection', async () => {242 await testDeleteProperties({type: 'ReFungible'});243 });210});244});211245212describe('Negative Integration Test: Collection Properties', () => {246describe('Negative Integration Test: Collection Properties', () => {217 });251 });218 });252 });219 253 220 it('Fails to set properties in a collection if not its onwer/administrator', async () => {254 async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {221 await usingApi(async api => {255 await usingApi(async api => {222 const collection = await createCollectionExpectSuccess();256 const collection = await createCollectionExpectSuccess({mode: mode});223257 224 await expect(executeTransaction(258 await expect(executeTransaction(225 api, 259 api, 226 bob, 260 bob, 230 const properties = (await api.query.common.collectionProperties(collection)).toJSON();264 const properties = (await api.query.common.collectionProperties(collection)).toJSON();231 expect(properties.map).to.be.empty;265 expect(properties.map).to.be.empty;232 expect(properties.consumedSpace).to.equal(0);266 expect(properties.consumedSpace).to.equal(0);233 });267 }); 268 }269 it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {270 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});234 });271 });272 it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {273 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});274 });235 275 236 it('Fails to set properties that exceed the limits', async () => {276 async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {237 await usingApi(async api => {277 await usingApi(async api => {238 const collection = await createCollectionExpectSuccess();278 const collection = await createCollectionExpectSuccess({mode: mode});239 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 279 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 240280 241 // Mute the general tx parsing error, too many bytes to process281 // Mute the general tx parsing error, too many bytes to process242 {282 {243 console.error = () => {};283 console.error = () => {};247 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 287 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 248 )).to.be.rejected;288 )).to.be.rejected;249 }289 }250290 251 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();291 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();252 expect(properties).to.be.empty;292 expect(properties).to.be.empty;253293 254 await expect(executeTransaction(294 await expect(executeTransaction(255 api, 295 api, 256 alice, 296 alice, 259 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 299 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 260 ]), 300 ]), 261 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);301 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);262302 263 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();303 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();264 expect(properties).to.be.empty;304 expect(properties).to.be.empty;265 });305 }); 306 }307 it('Fails to set properties that exceed the limits (NFT)', async () => {308 await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});266 });309 });310 it('Fails to set properties that exceed the limits (ReFungible)', async () => {311 await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});312 });267 313 268 it('Fails to set more properties than it is allowed', async () => {314 async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {269 await usingApi(async api => {315 await usingApi(async api => {270 const collection = await createCollectionExpectSuccess();316 const collection = await createCollectionExpectSuccess({mode: mode});271317 272 const propertiesToBeSet = [];318 const propertiesToBeSet = [];273 for (let i = 0; i < 65; i++) {319 for (let i = 0; i < 65; i++) {274 propertiesToBeSet.push({320 propertiesToBeSet.push({275 key: 'electron_' + i,321 key: 'electron_' + i,276 value: Math.random() > 0.5 ? 'high' : 'low',322 value: Math.random() > 0.5 ? 'high' : 'low',277 });323 });278 }324 }279325 280 await expect(executeTransaction(326 await expect(executeTransaction(281 api, 327 api, 282 alice, 328 alice, 283 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 329 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 284 )).to.be.rejectedWith(/common\.PropertyLimitReached/);330 )).to.be.rejectedWith(/common\.PropertyLimitReached/);285331 286 const properties = (await api.query.common.collectionProperties(collection)).toJSON();332 const properties = (await api.query.common.collectionProperties(collection)).toJSON();287 expect(properties.map).to.be.empty;333 expect(properties.map).to.be.empty;288 expect(properties.consumedSpace).to.equal(0);334 expect(properties.consumedSpace).to.equal(0);289 });335 }); 336 }337 it('Fails to set more properties than it is allowed (NFT)', async () => {338 await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});290 });339 });291292 it('Fails to set properties with invalid names', async () => {340 it('Fails to set more properties than it is allowed (ReFungible)', async () => {341 await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});342 });343 344 async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {293 await usingApi(async api => {345 await usingApi(async api => {294 const collection = await createCollectionExpectSuccess();346 const collection = await createCollectionExpectSuccess({mode: mode});295347 296 const invalidProperties = [348 const invalidProperties = [297 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],349 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],298 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],350 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],299 [{key: 'déjà vu', value: 'hmm...'}],351 [{key: 'déjà vu', value: 'hmm...'}],300 ];352 ];301353 302 for (let i = 0; i < invalidProperties.length; i++) {354 for (let i = 0; i < invalidProperties.length; i++) {303 await expect(executeTransaction(355 await expect(executeTransaction(304 api, 356 api, 305 alice, 357 alice, 306 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 358 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 307 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);359 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);308 }360 }309361 310 await expect(executeTransaction(362 await expect(executeTransaction(311 api, 363 api, 312 alice, 364 alice, 313 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 365 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 314 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);366 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);315367 316 await expect(executeTransaction(368 await expect(executeTransaction(317 api, 369 api, 318 alice, 370 alice, 319 api.tx.unique.setCollectionProperties(collection, [371 api.tx.unique.setCollectionProperties(collection, [320 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},372 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},321 ]), 373 ]), 322 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;374 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;323375 324 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');376 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');325377 326 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();378 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();327 expect(properties).to.be.deep.equal([379 expect(properties).to.be.deep.equal([328 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},380 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},329 ]);381 ]);330382 331 for (let i = 0; i < invalidProperties.length; i++) {383 for (let i = 0; i < invalidProperties.length; i++) {332 await expect(executeTransaction(384 await expect(executeTransaction(333 api, 385 api, 336 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);388 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);337 }389 }338 });390 });391 }392 it('Fails to set properties with invalid names (NFT)', async () => {393 await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});339 });394 });395 it('Fails to set properties with invalid names (ReFungible)', async () => {396 await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});397 });340});398});341399342// ---------- ACCESS RIGHTS400// ---------- ACCESS RIGHTS357 });415 });358 });416 });359 417 360 it('Sets access rights to properties of a collection', async () => {418 async function testSetsAccessRightsToProperties(mode: CollectionMode) {361 await usingApi(async api => {419 await usingApi(async api => {362 const collection = await createCollectionExpectSuccess();420 const collection = await createCollectionExpectSuccess({mode: mode});363421 364 await expect(executeTransaction(422 await expect(executeTransaction(365 api, 423 api, 366 alice, 424 alice, 367 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 425 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 368 )).to.not.be.rejected;426 )).to.not.be.rejected;369427 370 await addCollectionAdminExpectSuccess(alice, collection, bob.address);428 await addCollectionAdminExpectSuccess(alice, collection, bob.address);371429 372 await expect(executeTransaction(430 await expect(executeTransaction(373 api, 431 api, 374 alice, 432 alice, 375 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 433 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 376 )).to.not.be.rejected;434 )).to.not.be.rejected;377435 378 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();436 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();379 expect(propertyRights).to.be.deep.equal([437 expect(propertyRights).to.be.deep.equal([380 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},438 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},381 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},439 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},382 ]);440 ]);383 });441 }); 442 }443 it('Sets access rights to properties of a collection (NFT)', async () => {444 await testSetsAccessRightsToProperties({type: 'NFT'});384 });445 });446 it('Sets access rights to properties of a collection (ReFungible)', async () => {447 await testSetsAccessRightsToProperties({type: 'ReFungible'});448 });385 449 386 it('Changes access rights to properties of a collection', async () => {450 async function testChangesAccessRightsToProperty(mode: CollectionMode) {387 await usingApi(async api => {451 await usingApi(async api => {388 const collection = await createCollectionExpectSuccess();452 const collection = await createCollectionExpectSuccess({mode: mode});389453 390 await expect(executeTransaction(454 await expect(executeTransaction(391 api, 455 api, 392 alice, 456 alice, 393 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 457 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 394 )).to.not.be.rejected;458 )).to.not.be.rejected;395459 396 await expect(executeTransaction(460 await expect(executeTransaction(397 api, 461 api, 398 alice, 462 alice, 399 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 463 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 400 )).to.not.be.rejected;464 )).to.not.be.rejected;401465 402 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();466 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();403 expect(propertyRights).to.be.deep.equal([467 expect(propertyRights).to.be.deep.equal([404 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},468 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},405 ]);469 ]);406 });470 });471 }472 it('Changes access rights to properties of a NFT collection', async () => {473 await testChangesAccessRightsToProperty({type: 'NFT'});407 });474 });475 it('Changes access rights to properties of a ReFungible collection', async () => {476 await testChangesAccessRightsToProperty({type: 'ReFungible'});477 });408});478});409479410describe('Negative Integration Test: Access Rights to Token Properties', () => {480describe('Negative Integration Test: Access Rights to Token Properties', () => {415 });485 });416 });486 });417487418 it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {488 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {419 await usingApi(async api => {489 await usingApi(async api => {420 const collection = await createCollectionExpectSuccess();490 const collection = await createCollectionExpectSuccess({mode: mode});421491 422 await expect(executeTransaction(492 await expect(executeTransaction(423 api, 493 api, 424 bob, 494 bob, 425 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 495 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 426 )).to.be.rejectedWith(/common\.NoPermission/);496 )).to.be.rejectedWith(/common\.NoPermission/);427497 428 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();498 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();429 expect(propertyRights).to.be.empty;499 expect(propertyRights).to.be.empty;430 });500 });501 }502 it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {503 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});431 });504 });505 it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {506 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});507 });432508433 it('Prevents from adding too many possible properties', async () => {509 async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {434 await usingApi(async api => {510 await usingApi(async api => {435 const collection = await createCollectionExpectSuccess();511 const collection = await createCollectionExpectSuccess({mode: mode});436512 437 const constitution = [];513 const constitution = [];438 for (let i = 0; i < 65; i++) {514 for (let i = 0; i < 65; i++) {439 constitution.push({515 constitution.push({440 key: 'property_' + i,516 key: 'property_' + i,441 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},517 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},442 });518 });443 }519 }444520 445 await expect(executeTransaction(521 await expect(executeTransaction(446 api, 522 api, 447 alice, 523 alice, 448 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 524 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 449 )).to.be.rejectedWith(/common\.PropertyLimitReached/);525 )).to.be.rejectedWith(/common\.PropertyLimitReached/);450526 451 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();527 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();452 expect(propertyRights).to.be.empty;528 expect(propertyRights).to.be.empty;453 });529 }); 530 }531 it('Prevents from adding too many possible properties (NFT)', async () => {532 await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});454 });533 });534 it('Prevents from adding too many possible properties (ReFungible)', async () => {535 await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});536 });455537456 it('Prevents access rights to be modified if constant', async () => {538 async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {457 await usingApi(async api => {539 await usingApi(async api => {458 const collection = await createCollectionExpectSuccess();540 const collection = await createCollectionExpectSuccess({mode: mode});459541 460 await expect(executeTransaction(542 await expect(executeTransaction(461 api, 543 api, 462 alice, 544 alice, 463 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 545 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 464 )).to.not.be.rejected;546 )).to.not.be.rejected;465547 466 await expect(executeTransaction(548 await expect(executeTransaction(467 api, 549 api, 468 alice, 550 alice, 469 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 551 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 470 )).to.be.rejectedWith(/common\.NoPermission/);552 )).to.be.rejectedWith(/common\.NoPermission/);471553 472 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();554 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();473 expect(propertyRights).to.deep.equal([555 expect(propertyRights).to.deep.equal([474 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},556 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},475 ]);557 ]);476 });558 }); 559 }560 it('Prevents access rights to be modified if constant (NFT)', async () => {561 await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});477 });562 });563 it('Prevents access rights to be modified if constant (ReFungible)', async () => {564 await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});565 });478566479 it('Prevents adding properties with invalid names', async () => {567 async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {480 await usingApi(async api => {568 await usingApi(async api => {481 const collection = await createCollectionExpectSuccess();569 const collection = await createCollectionExpectSuccess({mode: mode});482570 483 const invalidProperties = [571 const invalidProperties = [484 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],572 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],485 [{key: 'G#4', permission: {tokenOwner: true}}],573 [{key: 'G#4', permission: {tokenOwner: true}}],486 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],574 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],487 ];575 ];488576 489 for (let i = 0; i < invalidProperties.length; i++) {577 for (let i = 0; i < invalidProperties.length; i++) {490 await expect(executeTransaction(578 await expect(executeTransaction(491 api, 579 api, 492 alice, 580 alice, 493 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 581 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 494 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);582 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);495 }583 }496584 497 await expect(executeTransaction(585 await expect(executeTransaction(498 api, 586 api, 499 alice, 587 alice, 500 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 588 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 501 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);589 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);502590 503 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string591 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string504 await expect(executeTransaction(592 await expect(executeTransaction(505 api, 593 api, 508 {key: correctKey, permission: {collectionAdmin: true}},596 {key: correctKey, permission: {collectionAdmin: true}},509 ]), 597 ]), 510 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;598 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;511599 512 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');600 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');513601 514 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();602 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();515 expect(propertyRights).to.be.deep.equal([603 expect(propertyRights).to.be.deep.equal([516 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},604 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},517 ]);605 ]);518 });606 });607 }608 it('Prevents adding properties with invalid names (NFT)', async () => {609 await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});519 });610 });611 it('Prevents adding properties with invalid names (ReFungible)', async () => {612 await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});613 });520});614});521615522// ---------- TOKEN PROPERTIES616// ---------- TOKEN PROPERTIES523617524describe('Integration Test: Token Properties', () => {618describe('Integration Test: Token Properties', () => {525 let collection: number;526 let token: number;527 let nestedToken: number;528 let permissions: {permission: any, signers: IKeyringPair[]}[];619 let permissions: {permission: any, signers: IKeyringPair[]}[];529620530 before(async () => {621 before(async () => {531 await usingApi(async (api, privateKeyWrapper) => {622 await usingApi(async (api, privateKeyWrapper) => {532 alice = privateKeyWrapper('//Alice');623 alice = privateKeyWrapper('//Alice'); // collection owner533 bob = privateKeyWrapper('//Bob');624 bob = privateKeyWrapper('//Bob'); // collection admin534 charlie = privateKeyWrapper('//Charlie');625 charlie = privateKeyWrapper('//Charlie'); // token owner535626536 permissions = [627 permissions = [537 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},628 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},543 ];634 ];544 });635 });545 });636 });546547 beforeEach(async () => {548 await usingApi(async () => {549 collection = await createCollectionExpectSuccess();550 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});551552 token = await createItemExpectSuccess(alice, collection, 'NFT');553 nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});554555 await addCollectionAdminExpectSuccess(alice, collection, bob.address);556 await transferExpectSuccess(collection, token, alice, charlie);557 });558 });559 637 560 it('Reads yet empty properties of a token', async () => {638 async function testReadsYetEmptyProperties(mode: CollectionMode) {561 await usingApi(async api => {639 await usingApi(async api => {562 const collection = await createCollectionExpectSuccess();640 const collection = await createCollectionExpectSuccess({mode: mode});563 const token = await createItemExpectSuccess(alice, collection, 'NFT');641 const token = await createItemExpectSuccess(alice, collection, mode.type);564 642 565 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();643 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();566 expect(properties.map).to.be.empty;644 expect(properties.map).to.be.empty;567 expect(properties.consumedSpace).to.be.equal(0);645 expect(properties.consumedSpace).to.be.equal(0);568646 569 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;647 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;570 expect(tokenData).to.be.empty;648 expect(tokenData).to.be.empty;571 });649 });650 }651 it('Reads yet empty properties of a token (NFT)', async () => {652 await testReadsYetEmptyProperties({type: 'NFT'});572 });653 });654 it('Reads yet empty properties of a token (ReFungible)', async () => {655 await testReadsYetEmptyProperties({type: 'ReFungible'});656 });573657574 it('Assigns properties to a token according to permissions', async () => {658 async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {575 await usingApi(async api => {659 await usingApi(async api => {660 const collection = await createCollectionExpectSuccess({mode: mode});661 const token = await createItemExpectSuccess(alice, collection, mode.type);662 await addCollectionAdminExpectSuccess(alice, collection, bob.address);663 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);664576 const propertyKeys: string[] = [];665 const propertyKeys: string[] = [];577 let i = 0;666 let i = 0;578 for (const permission of permissions) {667 for (const permission of permissions) {603 expect(tokensData[i].value).to.be.equal('Serotonin increase');692 expect(tokensData[i].value).to.be.equal('Serotonin increase');604 }693 }605 });694 });695 }696 it('Assigns properties to a token according to permissions (NFT)', async () => {697 await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);606 });698 });699 it('Assigns properties to a token according to permissions (ReFungible)', async () => {700 await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);701 });607702608 it('Changes properties of a token according to permissions', async () => {703 async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {609 await usingApi(async api => {704 await usingApi(async api => {705 const collection = await createCollectionExpectSuccess({mode: mode});706 const token = await createItemExpectSuccess(alice, collection, mode.type);707 await addCollectionAdminExpectSuccess(alice, collection, bob.address);708 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);709610 const propertyKeys: string[] = [];710 const propertyKeys: string[] = [];611 let i = 0;711 let i = 0;612 for (const permission of permissions) {712 for (const permission of permissions) {615 for (const signer of permission.signers) {715 for (const signer of permission.signers) {616 const key = i + '_' + signer.address;716 const key = i + '_' + signer.address;617 propertyKeys.push(key);717 propertyKeys.push(key);618718 619 await expect(executeTransaction(719 await expect(executeTransaction(620 api, 720 api, 621 alice, 721 alice, 622 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 722 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 623 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;723 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;624724 625 await expect(executeTransaction(725 await expect(executeTransaction(626 api, 726 api, 627 signer, 727 signer, 628 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 728 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 629 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;729 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;630730 631 await expect(executeTransaction(731 await expect(executeTransaction(632 api, 732 api, 633 signer, 733 signer, 634 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 734 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 635 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;735 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;636 }736 }637737 638 i++;738 i++;639 }739 }640740 641 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];741 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];642 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];742 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];643 for (let i = 0; i < properties.length; i++) {743 for (let i = 0; i < properties.length; i++) {644 expect(properties[i].value).to.be.equal('Serotonin stable');744 expect(properties[i].value).to.be.equal('Serotonin stable');645 expect(tokensData[i].value).to.be.equal('Serotonin stable');745 expect(tokensData[i].value).to.be.equal('Serotonin stable');646 }746 }647 });747 });748 }749 it('Changes properties of a token according to permissions (NFT)', async () => {750 await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);648 });751 });752 it('Changes properties of a token according to permissions (ReFungible)', async () => {753 await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);754 });649755650 it('Deletes properties of a token according to permissions', async () => {756 async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {651 await usingApi(async api => {757 await usingApi(async api => {758 const collection = await createCollectionExpectSuccess({mode: mode});759 const token = await createItemExpectSuccess(alice, collection, mode.type);760 await addCollectionAdminExpectSuccess(alice, collection, bob.address);761 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);762652 const propertyKeys: string[] = [];763 const propertyKeys: string[] = [];653 let i = 0;764 let i = 0;654765 655 for (const permission of permissions) {766 for (const permission of permissions) {656 if (!permission.permission.mutable) continue;767 if (!permission.permission.mutable) continue;657 768 658 for (const signer of permission.signers) {769 for (const signer of permission.signers) {659 const key = i + '_' + signer.address;770 const key = i + '_' + signer.address;660 propertyKeys.push(key);771 propertyKeys.push(key);661772 662 await expect(executeTransaction(773 await expect(executeTransaction(663 api, 774 api, 664 alice, 775 alice, 665 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 776 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 666 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;777 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;667778 668 await expect(executeTransaction(779 await expect(executeTransaction(669 api, 780 api, 670 signer, 781 signer, 671 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 782 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 672 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;783 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;673784 674 await expect(executeTransaction(785 await expect(executeTransaction(675 api, 786 api, 676 signer, 787 signer, 680 791 681 i++;792 i++;682 }793 }683794 684 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];795 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];685 expect(properties).to.be.empty;796 expect(properties).to.be.empty;686 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];797 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];687 expect(tokensData).to.be.empty;798 expect(tokensData).to.be.empty;688 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);799 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);689 });800 });801 }802 it('Deletes properties of a token according to permissions (NFT)', async () => {803 await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);690 });804 });805 it('Deletes properties of a token according to permissions (ReFungible)', async () => {806 await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);807 });691808692 it('Assigns properties to a nested token according to permissions', async () => {809 it('Assigns properties to a nested token according to permissions', async () => {693 await usingApi(async api => {810 await usingApi(async api => {811 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});812 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});813 const token = await createItemExpectSuccess(alice, collection, 'NFT');814 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});815 await addCollectionAdminExpectSuccess(alice, collection, bob.address);816 await transferExpectSuccess(collection, token, alice, charlie);817694 const propertyKeys: string[] = [];818 const propertyKeys: string[] = [];695 let i = 0;819 let i = 0;696 for (const permission of permissions) {820 for (const permission of permissions) {725849726 it('Changes properties of a nested token according to permissions', async () => {850 it('Changes properties of a nested token according to permissions', async () => {727 await usingApi(async api => {851 await usingApi(async api => {852 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});853 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});854 const token = await createItemExpectSuccess(alice, collection, 'NFT');855 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});856 await addCollectionAdminExpectSuccess(alice, collection, bob.address);857 await transferExpectSuccess(collection, token, alice, charlie);858728 const propertyKeys: string[] = [];859 const propertyKeys: string[] = [];729 let i = 0;860 let i = 0;730 for (const permission of permissions) {861 for (const permission of permissions) {767898768 it('Deletes properties of a nested token according to permissions', async () => {899 it('Deletes properties of a nested token according to permissions', async () => {769 await usingApi(async api => {900 await usingApi(async api => {901 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});902 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});903 const token = await createItemExpectSuccess(alice, collection, 'NFT');904 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});905 await addCollectionAdminExpectSuccess(alice, collection, bob.address);906 await transferExpectSuccess(collection, token, alice, charlie);907770 const propertyKeys: string[] = [];908 const propertyKeys: string[] = [];771 let i = 0;909 let i = 0;772910832 });970 });833 });971 });834972835 beforeEach(async () => {973 async function prepare(mode: CollectionMode, pieces: number) {836 collection = await createCollectionExpectSuccess();974 collection = await createCollectionExpectSuccess({mode: mode});837 token = await createItemExpectSuccess(alice, collection, 'NFT');975 token = await createItemExpectSuccess(alice, collection, mode.type);838 await addCollectionAdminExpectSuccess(alice, collection, bob.address);976 await addCollectionAdminExpectSuccess(alice, collection, bob.address);839 await transferExpectSuccess(collection, token, alice, charlie);977 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);840 978 841 await usingApi(async api => {979 await usingApi(async api => {842 let i = 0;980 let i = 0;848 alice, 986 alice, 849 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 987 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 850 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;988 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;851989 852 await expect(executeTransaction(990 await expect(executeTransaction(853 api, 991 api, 854 signer, 992 signer, 855 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 993 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 856 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;994 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;857995 858 i++;996 i++;859 }997 }860998 861 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;999 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;862 });1000 }); 863 });1001 }8641002865 it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {1003 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {1004 await prepare(mode, pieces);1005 866 await usingApi(async api => {1006 await usingApi(async api => {867 let i = -1;1007 let i = -1;868 for (const forbiddance of constitution) {1008 for (const forbiddance of constitution) {869 i++;1009 i++;870 if (!forbiddance.permission.mutable) continue;1010 if (!forbiddance.permission.mutable) continue;8711011 872 await expect(executeTransaction(1012 await expect(executeTransaction(873 api, 1013 api, 874 forbiddance.sinner, 1014 forbiddance.sinner, 875 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1015 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 876 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1016 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);8771017 878 await expect(executeTransaction(1018 await expect(executeTransaction(879 api, 1019 api, 880 forbiddance.sinner, 1020 forbiddance.sinner, 881 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 1021 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 882 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1022 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);883 }1023 }8841024 885 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1025 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();886 expect(properties.consumedSpace).to.be.equal(originalSpace);1026 expect(properties.consumedSpace).to.be.equal(originalSpace);887 });1027 });1028 }1029 it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {1030 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);888 });1031 });1032 it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {1033 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);1034 });8891035890 it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {1036 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {1037 await prepare(mode, pieces);1038 891 await usingApi(async api => {1039 await usingApi(async api => {892 let i = -1;1040 let i = -1;893 for (const permission of constitution) {1041 for (const permission of constitution) {894 i++;1042 i++;895 if (permission.permission.mutable) continue;1043 if (permission.permission.mutable) continue;8961044 897 await expect(executeTransaction(1045 await expect(executeTransaction(898 api, 1046 api, 899 permission.signers[0], 1047 permission.signers[0], 900 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1048 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 901 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1049 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);9021050 903 await expect(executeTransaction(1051 await expect(executeTransaction(904 api, 1052 api, 905 permission.signers[0], 1053 permission.signers[0], 906 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 1054 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 907 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1055 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);908 }1056 }9091057 910 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1058 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();911 expect(properties.consumedSpace).to.be.equal(originalSpace);1059 expect(properties.consumedSpace).to.be.equal(originalSpace);912 });1060 }); 1061 }1062 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {1063 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);913 });1064 });1065 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {1066 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);1067 });9141068915 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {1069 async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {1070 await prepare(mode, pieces);1071916 await usingApi(async api => {1072 await usingApi(async api => {917 await expect(executeTransaction(1073 await expect(executeTransaction(925 alice, 1081 alice, 926 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 1082 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 927 ), 'on setting a new non-permitted property').to.not.be.rejected;1083 ), 'on setting a new non-permitted property').to.not.be.rejected;9281084 929 await expect(executeTransaction(1085 await expect(executeTransaction(930 api, 1086 api, 931 alice, 1087 alice, 932 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 1088 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 933 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);1089 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);9341090 935 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;1091 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;936 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1092 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();937 expect(properties.consumedSpace).to.be.equal(originalSpace);1093 expect(properties.consumedSpace).to.be.equal(originalSpace);938 });1094 });1095 }1096 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {1097 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);939 });1098 });1099 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {1100 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);1101 });9401102941 it('Forbids adding too many properties to a token', async () => {1103 async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {1104 await prepare(mode, pieces);1105942 await usingApi(async api => {1106 await usingApi(async api => {943 await expect(executeTransaction(1107 await expect(executeTransaction(948 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1112 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},949 ]), 1113 ]), 950 ), 'on setting a new non-permitted property').to.not.be.rejected;1114 ), 'on setting a new non-permitted property').to.not.be.rejected;9511115 952 // Mute the general tx parsing error1116 // Mute the general tx parsing error953 {1117 {954 console.error = () => {};1118 console.error = () => {};958 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 1122 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 959 )).to.be.rejected;1123 )).to.be.rejected;960 }1124 }9611125 962 await expect(executeTransaction(1126 await expect(executeTransaction(963 api, 1127 api, 964 alice, 1128 alice, 967 {key: 'young_years', value: 'neverending'.repeat(1490)},1131 {key: 'young_years', value: 'neverending'.repeat(1490)},968 ]), 1132 ]), 969 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);1133 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);9701134 971 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;1135 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;972 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1136 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();973 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1137 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1138 });1139 }1140 it('Forbids adding too many properties to a token (NFT)', async () => {1141 await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);1142 });1143 it('Forbids adding too many properties to a token (ReFungible)', async () => {1144 await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);1145 });1146});11471148describe('ReFungible token properties permissions tests', () => {1149 let collection: number;1150 let token: number;11511152 before(async () => {1153 await usingApi(async (api, privateKeyWrapper) => {1154 alice = privateKeyWrapper('//Alice');1155 bob = privateKeyWrapper('//Bob');1156 charlie = privateKeyWrapper('//Charlie');1157 });1158 });11591160 beforeEach(async () => {1161 await usingApi(async api => {1162 collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});1163 token = await createItemExpectSuccess(alice, collection, 'ReFungible');1164 await addCollectionAdminExpectSuccess(alice, collection, bob.address);11651166 await expect(executeTransaction(1167 api, 1168 alice, 1169 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1170 )).to.not.be.rejected;1171 });1172 });11731174 it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {1175 await usingApi(async api => {1176 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1177 1178 await expect(executeTransaction(1179 api, 1180 alice, 1181 api.tx.unique.setTokenProperties(collection, token, [1182 {key: 'key', value: 'word'}, 1183 ]), 1184 )).to.be.rejectedWith(/common\.NoPermission/);1185 });1186 });11871188 it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {1189 await usingApi(async api => {1190 await expect(executeTransaction(1191 api, 1192 alice, 1193 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1194 )).to.not.be.rejected;1195 1196 await expect(executeTransaction(1197 api, 1198 alice, 1199 api.tx.unique.setTokenProperties(collection, token, [1200 {key: 'key', value: 'word'}, 1201 ]), 1202 )).to.be.not.rejected;12031204 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1205 1206 await expect(executeTransaction(1207 api, 1208 alice, 1209 api.tx.unique.setTokenProperties(collection, token, [1210 {key: 'key', value: 'bad word'}, 1211 ]), 1212 )).to.be.rejectedWith(/common\.NoPermission/);1213 });1214 });12151216 it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {1217 await usingApi(async api => {1218 await expect(executeTransaction(1219 api, 1220 alice, 1221 api.tx.unique.setTokenProperties(collection, token, [1222 {key: 'key', value: 'word'}, 1223 ]), 1224 )).to.be.not.rejected;12251226 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1227 1228 await expect(executeTransaction(1229 api, 1230 alice, 1231 api.tx.unique.deleteTokenProperties(collection, token, [1232 'key',1233 ]), 1234 )).to.be.rejectedWith(/common\.NoPermission/);974 });1235 });975 });1236 });976});1237});tests/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);
+ });
+ });
+});
tests/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);