git.delta.rocks / unique-network / refs/commits / c07e1d724b0d

difftreelog

Merge pull request #368 from UniqueNetwork/feature/CORE-386_1

bugrazoid2022-06-10parents: #3151280 #0aa9474.patch.diff
in: master
Feature/core 386 1

27 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -22,7 +22,9 @@
 pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_std::vec::Vec;
-use up_data_structs::{Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode};
+use up_data_structs::{
+	Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode, CollectionPermissions,
+};
 use alloc::format;
 
 use crate::{Pallet, CollectionHandle, Config, CollectionProperties};
@@ -47,10 +49,15 @@
 
 #[solidity_interface(name = "Collection")]
 impl<T: Config> CollectionHandle<T>
-// where
-// 	T::AccountId: From<H256>
+where
+	T::AccountId: From<[u8; 32]>,
 {
-	fn set_collection_property(&mut self, caller: caller, key: string, value: bytes) -> Result<()> {
+	fn set_collection_property(
+		&mut self,
+		caller: caller,
+		key: string,
+		value: bytes,
+	) -> Result<void> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let key = <Vec<u8>>::from(key)
 			.try_into()
@@ -83,7 +90,7 @@
 	}
 
 	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {
-		check_is_owner(caller, self)?;
+		check_is_owner_or_admin(caller, self)?;
 
 		let sponsor = T::CrossAccountId::from_eth(sponsor);
 		self.set_sponsor(sponsor.as_sub().clone())
@@ -97,14 +104,14 @@
 			.confirm_sponsorship(caller.as_sub())
 			.map_err(dispatch_to_evm::<T>)?
 		{
-			return Err(Error::Revert("Caller is not set as sponsor".into()));
+			return Err("caller is not set as sponsor".into());
 		}
 		save(self)
 	}
 
 	#[solidity(rename_selector = "setCollectionLimit")]
 	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {
-		check_is_owner(caller, self)?;
+		check_is_owner_or_admin(caller, self)?;
 		let mut limits = self.limits.clone();
 
 		match limit.as_str() {
@@ -128,7 +135,7 @@
 			}
 			_ => {
 				return Err(Error::Revert(format!(
-					"Unknown integer limit \"{}\"",
+					"unknown integer limit \"{}\"",
 					limit
 				)))
 			}
@@ -140,7 +147,7 @@
 
 	#[solidity(rename_selector = "setCollectionLimit")]
 	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {
-		check_is_owner(caller, self)?;
+		check_is_owner_or_admin(caller, self)?;
 		let mut limits = self.limits.clone();
 
 		match limit.as_str() {
@@ -155,7 +162,7 @@
 			}
 			_ => {
 				return Err(Error::Revert(format!(
-					"Unknown boolean limit \"{}\"",
+					"unknown boolean limit \"{}\"",
 					limit
 				)))
 			}
@@ -169,52 +176,48 @@
 		Ok(crate::eth::collection_id_to_address(self.id))
 	}
 
-	// fn add_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
-	// 	let mut new_admin_h256 = H256::default();
-	// 	new_admin.to_little_endian(&mut new_admin_h256.0);
-	// 	let account_id = T::AccountId::from(new_admin_h256);
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let new_admin = T::CrossAccountId::from_sub(account_id);
-	// 	<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, true)
-	// 		.map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+	fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let mut new_admin_arr: [u8; 32] = Default::default();
+		new_admin.to_big_endian(&mut new_admin_arr);
+		let account_id = T::AccountId::from(new_admin_arr);
+		let new_admin = T::CrossAccountId::from_sub(account_id);
+		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
-	// fn remove_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
-	// 	let mut new_admin_h256 = H256::default();
-	// 	new_admin.to_little_endian(&mut new_admin_h256.0);
-	// 	let account_id = T::AccountId::from(new_admin_h256);
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let new_admin = T::CrossAccountId::from_sub(account_id);
-	// 	<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, false)
-	// 		.map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+	fn remove_collection_admin_substrate(
+		&self,
+		caller: caller,
+		new_admin: uint256,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let mut new_admin_arr: [u8; 32] = Default::default();
+		new_admin.to_big_endian(&mut new_admin_arr);
+		let account_id = T::AccountId::from(new_admin_arr);
+		let new_admin = T::CrossAccountId::from_sub(account_id);
+		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, false)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
 	fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result<void> {
 		let caller = T::CrossAccountId::from_eth(caller);
-		self.check_is_owner_or_admin(&caller)
-			.map_err(dispatch_to_evm::<T>)?;
 		let new_admin = T::CrossAccountId::from_eth(new_admin);
-		<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, true)
-			.map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
 	fn remove_collection_admin(&self, caller: caller, admin: address) -> Result<void> {
 		let caller = T::CrossAccountId::from_eth(caller);
-		self.check_is_owner_or_admin(&caller)
-			.map_err(dispatch_to_evm::<T>)?;
 		let admin = T::CrossAccountId::from_eth(admin);
-		<Pallet<T>>::toggle_admin(&self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
+		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
 	#[solidity(rename_selector = "setCollectionNesting")]
 	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		self.check_is_owner_or_admin(&caller)
-			.map_err(dispatch_to_evm::<T>)?;
+		check_is_owner_or_admin(caller, self)?;
 
 		let mut permissions = self.collection.permissions.clone();
 		let mut nesting = permissions.nesting().clone();
@@ -240,11 +243,9 @@
 		collections: Vec<address>,
 	) -> Result<void> {
 		if collections.is_empty() {
-			return Err("No addresses provided".into());
+			return Err("no addresses provided".into());
 		}
-		let caller = T::CrossAccountId::from_eth(caller);
-		self.check_is_owner_or_admin(&caller)
-			.map_err(dispatch_to_evm::<T>)?;
+		check_is_owner_or_admin(caller, self)?;
 
 		let mut permissions = self.collection.permissions.clone();
 		match enable {
@@ -280,27 +281,34 @@
 	}
 
 	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		self.check_is_owner_or_admin(&caller)
-			.map_err(dispatch_to_evm::<T>)?;
-		self.collection.permissions.access = Some(match mode {
-			0 => AccessMode::Normal,
-			1 => AccessMode::AllowList,
-			_ => return Err("Not supported access mode".into()),
-		});
-		save(self)?;
-		Ok(())
+		check_is_owner_or_admin(caller, self)?;
+		let permissions = CollectionPermissions {
+			access: Some(match mode {
+				0 => AccessMode::Normal,
+				1 => AccessMode::AllowList,
+				_ => return Err("not supported access mode".into()),
+			}),
+			..Default::default()
+		};
+		self.collection.permissions = <Pallet<T>>::clamp_permissions(
+			self.collection.mode.clone(),
+			&self.collection.permissions,
+			permissions,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		save(self)
 	}
 
 	fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {
-		let caller = check_is_owner_or_admin(caller, self)?;
+		let caller = T::CrossAccountId::from_eth(caller);
 		let user = T::CrossAccountId::from_eth(user);
 		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
 	fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {
-		let caller = check_is_owner_or_admin(caller, self)?;
+		let caller = T::CrossAccountId::from_eth(caller);
 		let user = T::CrossAccountId::from_eth(user);
 		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
@@ -308,18 +316,19 @@
 
 	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {
 		check_is_owner_or_admin(caller, self)?;
-		self.collection.permissions.mint_mode = Some(mode);
-		save(self)?;
-		Ok(())
-	}
-}
+		let permissions = CollectionPermissions {
+			mint_mode: Some(mode),
+			..Default::default()
+		};
+		self.collection.permissions = <Pallet<T>>::clamp_permissions(
+			self.collection.mode.clone(),
+			&self.collection.permissions,
+			permissions,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 
-fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<void> {
-	let caller = T::CrossAccountId::from_eth(caller);
-	collection
-		.check_is_owner(&caller)
-		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
-	Ok(())
+		save(self)
+	}
 }
 
 fn check_is_owner_or_admin<T: Config>(
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -148,7 +148,7 @@
 					.saturating_mul(writes),
 			))
 	}
-	pub fn save(self) -> Result<(), DispatchError> {
+	pub fn save(self) -> DispatchResult {
 		<CollectionById<T>>::insert(self.id, self.collection);
 		Ok(())
 	}
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -152,12 +152,15 @@
 		via("CollectionHandle<T>", common_mut, Collection)
 	)
 )]
-impl<T: Config> FungibleHandle<T> {}
+impl<T: Config> FungibleHandle<T> where T::AccountId: From<[u8; 32]> {}
 
 generate_stubgen!(gen_impl, UniqueFungibleCall<()>, true);
 generate_stubgen!(gen_iface, UniqueFungibleCall<()>, false);
 
-impl<T: Config> CommonEvmHandler for FungibleHandle<T> {
+impl<T: Config> CommonEvmHandler for FungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
 	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueFungible.raw");
 
 	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -583,13 +583,16 @@
 		TokenProperties,
 	)
 )]
-impl<T: Config> NonfungibleHandle<T> {}
+impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> {}
 
 // Not a tests, but code generators
 generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);
 generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);
 
-impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {
+impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
 	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");
 
 	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -297,7 +297,40 @@
 	}
 }
 
-// Selector: 6aea9834
+// Selector: 780e9d63
+contract ERC721Enumerable is Dummy, ERC165 {
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
+		return 0;
+	}
+
+	// Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		public
+		view
+		returns (uint256)
+	{
+		require(false, stub_error);
+		owner;
+		index;
+		dummy;
+		return 0;
+	}
+
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
+
+// Selector: 7d9262e6
 contract Collection is Dummy, ERC165 {
 	// Selector: setCollectionProperty(string,bytes) 2f073f66
 	function setCollectionProperty(string memory key, bytes memory value)
@@ -366,6 +399,20 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
+	// Selector: addCollectionAdminSubstrate(uint256) 5730062b
+	function addCollectionAdminSubstrate(uint256 newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+	function removeCollectionAdminSubstrate(uint256 newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
 	// Selector: addCollectionAdmin(address) 92e462c7
 	function addCollectionAdmin(address newAdmin) public view {
 		require(false, stub_error);
@@ -423,39 +470,6 @@
 		require(false, stub_error);
 		mode;
 		dummy = 0;
-	}
-}
-
-// Selector: 780e9d63
-contract ERC721Enumerable is Dummy, ERC165 {
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		index;
-		dummy;
-		return 0;
-	}
-
-	// Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		public
-		view
-		returns (uint256)
-	{
-		require(false, stub_error);
-		owner;
-		index;
-		dummy;
-		return 0;
-	}
-
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
 	}
 }
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -491,7 +491,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			target_collection.check_is_owner(&sender)?;
+			target_collection.check_is_owner_or_admin(&sender)?;
 			target_collection.check_is_internal()?;
 
 			target_collection.set_sponsor(new_sponsor.clone())?;
@@ -867,7 +867,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_internal()?;
-			target_collection.check_is_owner(&sender)?;
+			target_collection.check_is_owner_or_admin(&sender)?;
 			let old_limit = &target_collection.limits;
 
 			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
@@ -889,7 +889,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_internal()?;
-			target_collection.check_is_owner(&sender)?;
+			target_collection.check_is_owner_or_admin(&sender)?;
 			let old_limit = &target_collection.permissions;
 
 			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
before · primitives/data-structs/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::{20	convert::{TryFrom, TryInto},21	fmt,22};23use frame_support::{24	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},25	traits::Get,26	parameter_types,27};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, Permill};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use frame_support::{BoundedVec, traits::ConstU32};36use derivative::Derivative;37use scale_info::TypeInfo;3839// RMRK40use rmrk_traits::{41	CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,42	ResourceTypes, BasicResource, ComposableResource, SlotResource,43};44pub use rmrk_traits::{45	primitives::{46		CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,47		PartId as RmrkPartId, ResourceId as RmrkResourceId,48	},49	NftChild as RmrkNftChild, AccountIdOrCollectionNftTuple as RmrkAccountIdOrCollectionNftTuple,50	FixedPart as RmrkFixedPart, SlotPart as RmrkSlotPart, EquippableList as RmrkEquippableList,51};5253mod bounded;54pub mod budget;55pub mod mapping;56mod migration;5758pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;59pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;60pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;6162pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {63	100_00064} else {65	1066};67pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {68	100_00069} else {70	1071};72pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {73	204874} else {75	1076};77pub const COLLECTION_ADMINS_LIMIT: u32 = 5;78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;79pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {80	1_000_00081} else {82	1083};8485// Timeouts for item types in passed blocks86pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;87pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;88pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;8990pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9192// Schema limits93pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;94pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;95pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;9697pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;9899pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;100pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;101pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;102103pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;104pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;105pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;106107pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;108pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;109110// RMRK constants111pub const RMRK_STRING_LIMIT: u32 = 128;112pub const RMRK_COLLECTION_SYMBOL_LIMIT: u32 = 100;113pub const RMRK_RESOURCE_SYMBOL_LIMIT: u32 = 10;114pub const RMRK_KEY_LIMIT: u32 = 32;115pub const RMRK_VALUE_LIMIT: u32 = 256;116117/// How much items can be created per single118/// create_many call119pub const MAX_ITEMS_PER_BATCH: u32 = 200;120121pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;122123#[derive(124	Encode,125	Decode,126	PartialEq,127	Eq,128	PartialOrd,129	Ord,130	Clone,131	Copy,132	Debug,133	Default,134	TypeInfo,135	MaxEncodedLen,136)]137#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]138pub struct CollectionId(pub u32);139impl EncodeLike<u32> for CollectionId {}140impl EncodeLike<CollectionId> for u32 {}141142#[derive(143	Encode,144	Decode,145	PartialEq,146	Eq,147	PartialOrd,148	Ord,149	Clone,150	Copy,151	Debug,152	Default,153	TypeInfo,154	MaxEncodedLen,155)]156#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]157pub struct TokenId(pub u32);158impl EncodeLike<u32> for TokenId {}159impl EncodeLike<TokenId> for u32 {}160161impl TokenId {162	pub fn try_next(self) -> Result<TokenId, ArithmeticError> {163		self.0164			.checked_add(1)165			.ok_or(ArithmeticError::Overflow)166			.map(Self)167	}168}169170impl From<TokenId> for U256 {171	fn from(t: TokenId) -> Self {172		t.0.into()173	}174}175176impl TryFrom<U256> for TokenId {177	type Error = &'static str;178179	fn try_from(value: U256) -> Result<Self, Self::Error> {180		Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))181	}182}183184#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]185#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]186pub struct TokenData<CrossAccountId> {187	pub properties: Vec<Property>,188	pub owner: Option<CrossAccountId>,189}190191pub struct OverflowError;192impl From<OverflowError> for &'static str {193	fn from(_: OverflowError) -> Self {194		"overflow occured"195	}196}197198pub type DecimalPoints = u8;199200#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]201#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]202pub enum CollectionMode {203	NFT,204	// decimal points205	Fungible(DecimalPoints),206	ReFungible,207}208209impl CollectionMode {210	pub fn id(&self) -> u8 {211		match self {212			CollectionMode::NFT => 1,213			CollectionMode::Fungible(_) => 2,214			CollectionMode::ReFungible => 3,215		}216	}217}218219pub trait SponsoringResolve<AccountId, Call> {220	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;221}222223#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]224#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]225pub enum AccessMode {226	Normal,227	AllowList,228}229impl Default for AccessMode {230	fn default() -> Self {231		Self::Normal232	}233}234235#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]236#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]237pub enum SchemaVersion {238	ImageURL,239	Unique,240}241impl Default for SchemaVersion {242	fn default() -> Self {243		Self::ImageURL244	}245}246247#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]248#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]249pub struct Ownership<AccountId> {250	pub owner: AccountId,251	pub fraction: u128,252}253254#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]255#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]256pub enum SponsorshipState<AccountId> {257	/// The fees are applied to the transaction sender258	Disabled,259	Unconfirmed(AccountId),260	/// Transactions are sponsored by specified account261	Confirmed(AccountId),262}263264impl<AccountId> SponsorshipState<AccountId> {265	pub fn sponsor(&self) -> Option<&AccountId> {266		match self {267			Self::Confirmed(sponsor) => Some(sponsor),268			_ => None,269		}270	}271272	pub fn pending_sponsor(&self) -> Option<&AccountId> {273		match self {274			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),275			_ => None,276		}277	}278279	pub fn confirmed(&self) -> bool {280		matches!(self, Self::Confirmed(_))281	}282}283284impl<T> Default for SponsorshipState<T> {285	fn default() -> Self {286		Self::Disabled287	}288}289290/// Used in storage291#[struct_versioning::versioned(version = 2, upper)]292#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]293pub struct Collection<AccountId> {294	pub owner: AccountId,295	pub mode: CollectionMode,296	#[version(..2)]297	pub access: AccessMode,298	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,299	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,300	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,301302	#[version(..2)]303	pub mint_mode: bool,304305	#[version(..2)]306	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,307308	#[version(..2)]309	pub schema_version: SchemaVersion,310	pub sponsorship: SponsorshipState<AccountId>,311312	pub limits: CollectionLimits,313314	#[version(2.., upper(Default::default()))]315	pub permissions: CollectionPermissions,316317	/// Marks that this collection is not "unique", and managed from external.318	#[version(2.., upper(false))]319	pub external_collection: bool,320321	#[version(..2)]322	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,323324	#[version(..2)]325	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,326327	#[version(..2)]328	pub meta_update_permission: MetaUpdatePermission,329}330331/// Used in RPC calls332#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]333#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]334pub struct RpcCollection<AccountId> {335	pub owner: AccountId,336	pub mode: CollectionMode,337	pub name: Vec<u16>,338	pub description: Vec<u16>,339	pub token_prefix: Vec<u8>,340	pub sponsorship: SponsorshipState<AccountId>,341	pub limits: CollectionLimits,342	pub permissions: CollectionPermissions,343	pub token_property_permissions: Vec<PropertyKeyPermission>,344	pub properties: Vec<Property>,345	pub read_only: bool,346}347348#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]349#[derivative(Debug, Default(bound = ""))]350pub struct CreateCollectionData<AccountId> {351	#[derivative(Default(value = "CollectionMode::NFT"))]352	pub mode: CollectionMode,353	pub access: Option<AccessMode>,354	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,355	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,356	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,357	pub pending_sponsor: Option<AccountId>,358	pub limits: Option<CollectionLimits>,359	pub permissions: Option<CollectionPermissions>,360	pub token_property_permissions: CollectionPropertiesPermissionsVec,361	pub properties: CollectionPropertiesVec,362}363364pub type CollectionPropertiesPermissionsVec =365	BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;366367pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;368369/// All fields are wrapped in `Option`s, where None means chain default370// When adding/removing fields from this struct - don't forget to also update clamp_limits371#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]372#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]373pub struct CollectionLimits {374	pub account_token_ownership_limit: Option<u32>,375	pub sponsored_data_size: Option<u32>,376377	/// FIXME should we delete this or repurpose it?378	/// None - setVariableMetadata is not sponsored379	/// Some(v) - setVariableMetadata is sponsored380	///           if there is v block between txs381	pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,382	pub token_limit: Option<u32>,383384	// Timeouts for item types in passed blocks385	pub sponsor_transfer_timeout: Option<u32>,386	pub sponsor_approve_timeout: Option<u32>,387	pub owner_can_transfer: Option<bool>,388	pub owner_can_destroy: Option<bool>,389	pub transfers_enabled: Option<bool>,390}391392impl CollectionLimits {393	pub fn account_token_ownership_limit(&self) -> u32 {394		self.account_token_ownership_limit395			.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)396			.min(MAX_TOKEN_OWNERSHIP)397	}398	pub fn sponsored_data_size(&self) -> u32 {399		self.sponsored_data_size400			.unwrap_or(CUSTOM_DATA_LIMIT)401			.min(CUSTOM_DATA_LIMIT)402	}403	pub fn token_limit(&self) -> u32 {404		self.token_limit405			.unwrap_or(COLLECTION_TOKEN_LIMIT)406			.min(COLLECTION_TOKEN_LIMIT)407	}408	pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {409		self.sponsor_transfer_timeout410			.unwrap_or(default)411			.min(MAX_SPONSOR_TIMEOUT)412	}413	pub fn sponsor_approve_timeout(&self) -> u32 {414		self.sponsor_approve_timeout415			.unwrap_or(SPONSOR_APPROVE_TIMEOUT)416			.min(MAX_SPONSOR_TIMEOUT)417	}418	pub fn owner_can_transfer(&self) -> bool {419		self.owner_can_transfer.unwrap_or(true)420	}421	pub fn owner_can_destroy(&self) -> bool {422		self.owner_can_destroy.unwrap_or(true)423	}424	pub fn transfers_enabled(&self) -> bool {425		self.transfers_enabled.unwrap_or(true)426	}427	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {428		match self429			.sponsored_data_rate_limit430			.unwrap_or(SponsoringRateLimit::SponsoringDisabled)431		{432			SponsoringRateLimit::SponsoringDisabled => None,433			SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),434		}435	}436}437438// When adding/removing fields from this struct - don't forget to also update clamp_limits439#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]440#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]441pub struct CollectionPermissions {442	pub access: Option<AccessMode>,443	pub mint_mode: Option<bool>,444	pub nesting: Option<NestingPermissions>,445}446447impl CollectionPermissions {448	pub fn access(&self) -> AccessMode {449		self.access.unwrap_or(AccessMode::Normal)450	}451	pub fn mint_mode(&self) -> bool {452		self.mint_mode.unwrap_or(false)453	}454	pub fn nesting(&self) -> &NestingPermissions {455		static DEFAULT: NestingPermissions = NestingPermissions {456			token_owner: false,457			admin: false,458			restricted: None,459460			permissive: false,461		};462		self.nesting.as_ref().unwrap_or(&DEFAULT)463	}464}465466type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;467468#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]469#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]470#[derivative(Debug)]471pub struct OwnerRestrictedSet(472	#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]473	#[derivative(Debug(format_with = "bounded::set_debug"))]474	pub OwnerRestrictedSetInner,475);476impl OwnerRestrictedSet {477	pub fn new() -> Self {478		Self(Default::default())479	}480}481impl core::ops::Deref for OwnerRestrictedSet {482	type Target = OwnerRestrictedSetInner;483	fn deref(&self) -> &Self::Target {484		&self.0485	}486}487impl core::ops::DerefMut for OwnerRestrictedSet {488	fn deref_mut(&mut self) -> &mut Self::Target {489		&mut self.0490	}491}492493#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]494#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]495#[derivative(Debug)]496pub struct NestingPermissions {497	/// Owner of token can nest tokens under it498	pub token_owner: bool,499	/// Admin of token collection can nest tokens under token500	pub admin: bool,501	/// If set - only tokens from specified collections can be nested502	pub restricted: Option<OwnerRestrictedSet>,503504	/// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`505	pub permissive: bool,506}507508#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]509#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]510pub enum SponsoringRateLimit {511	SponsoringDisabled,512	Blocks(u32),513}514515#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]516#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]517#[derivative(Debug)]518pub struct CreateNftData {519	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]520	#[derivative(Debug(format_with = "bounded::vec_debug"))]521	pub properties: CollectionPropertiesVec,522}523524#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]525#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]526pub struct CreateFungibleData {527	pub value: u128,528}529530#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]531#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]532#[derivative(Debug)]533pub struct CreateReFungibleData {534	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]535	#[derivative(Debug(format_with = "bounded::vec_debug"))]536	pub const_data: BoundedVec<u8, CustomDataLimit>,537	pub pieces: u128,538}539540#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]541#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]542pub enum MetaUpdatePermission {543	ItemOwner,544	Admin,545	None,546}547548#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]549#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]550pub enum CreateItemData {551	NFT(CreateNftData),552	Fungible(CreateFungibleData),553	ReFungible(CreateReFungibleData),554}555556#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]557#[derivative(Debug)]558pub struct CreateNftExData<CrossAccountId> {559	#[derivative(Debug(format_with = "bounded::vec_debug"))]560	pub properties: CollectionPropertiesVec,561	pub owner: CrossAccountId,562}563564#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]565#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]566pub struct CreateRefungibleExData<CrossAccountId> {567	#[derivative(Debug(format_with = "bounded::vec_debug"))]568	pub const_data: BoundedVec<u8, CustomDataLimit>,569	#[derivative(Debug(format_with = "bounded::map_debug"))]570	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,571}572573#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]574#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]575pub enum CreateItemExData<CrossAccountId> {576	NFT(577		#[derivative(Debug(format_with = "bounded::vec_debug"))]578		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,579	),580	Fungible(581		#[derivative(Debug(format_with = "bounded::map_debug"))]582		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,583	),584	/// Many tokens, each may have only one owner585	RefungibleMultipleItems(586		#[derivative(Debug(format_with = "bounded::vec_debug"))]587		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,588	),589	/// Single token, which may have many owners590	RefungibleMultipleOwners(CreateRefungibleExData<CrossAccountId>),591}592593impl CreateItemData {594	pub fn data_size(&self) -> usize {595		match self {596			CreateItemData::ReFungible(data) => data.const_data.len(),597			_ => 0,598		}599	}600}601602impl From<CreateNftData> for CreateItemData {603	fn from(item: CreateNftData) -> Self {604		CreateItemData::NFT(item)605	}606}607608impl From<CreateReFungibleData> for CreateItemData {609	fn from(item: CreateReFungibleData) -> Self {610		CreateItemData::ReFungible(item)611	}612}613614impl From<CreateFungibleData> for CreateItemData {615	fn from(item: CreateFungibleData) -> Self {616		CreateItemData::Fungible(item)617	}618}619620#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]621#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]622// todo possibly rename to be used generally as an address pair623pub struct TokenChild {624	pub token: TokenId,625	pub collection: CollectionId,626}627628#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]629#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]630pub struct CollectionStats {631	pub created: u32,632	pub destroyed: u32,633	pub alive: u32,634}635636#[derive(Encode, Decode, Clone, Debug)]637#[cfg_attr(feature = "std", derive(PartialEq))]638pub struct PhantomType<T>(core::marker::PhantomData<T>);639640impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {641	type Identity = PhantomType<T>;642643	fn type_info() -> scale_info::Type {644		use scale_info::{645			Type, Path,646			build::{FieldsBuilder, UnnamedFields},647			type_params,648		};649		Type::builder()650			.path(Path::new("up_data_structs", "PhantomType"))651			.type_params(type_params!(T))652			.composite(<FieldsBuilder<UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()))653	}654}655impl<T> MaxEncodedLen for PhantomType<T> {656	fn max_encoded_len() -> usize {657		0658	}659}660661pub type PropertyKey = BoundedVec<u8, ConstU32<MAX_PROPERTY_KEY_LENGTH>>;662pub type PropertyValue = BoundedVec<u8, ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;663664#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]665#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]666pub struct PropertyPermission {667	pub mutable: bool,668	pub collection_admin: bool,669	pub token_owner: bool,670}671672impl PropertyPermission {673	pub fn none() -> Self {674		Self {675			mutable: true,676			collection_admin: false,677			token_owner: false,678		}679	}680}681682#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]683#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]684pub struct Property {685	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]686	pub key: PropertyKey,687688	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]689	pub value: PropertyValue,690}691692impl Into<(PropertyKey, PropertyValue)> for Property {693	fn into(self) -> (PropertyKey, PropertyValue) {694		(self.key, self.value)695	}696}697698#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]699#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]700pub struct PropertyKeyPermission {701	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]702	pub key: PropertyKey,703704	pub permission: PropertyPermission,705}706707impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {708	fn into(self) -> (PropertyKey, PropertyPermission) {709		(self.key, self.permission)710	}711}712713#[derive(Debug)]714pub enum PropertiesError {715	NoSpaceForProperty,716	PropertyLimitReached,717	InvalidCharacterInPropertyKey,718	PropertyKeyIsTooLong,719	EmptyPropertyKey,720}721722#[derive(Clone, Copy)]723pub enum PropertyScope {724	None,725	Rmrk,726}727728impl PropertyScope {729	pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {730		let scope_str: &[u8] = match self {731			Self::None => return Ok(key),732			Self::Rmrk => b"rmrk",733		};734735		[scope_str, b":", key.as_slice()]736			.concat()737			.try_into()738			.map_err(|_| PropertiesError::PropertyKeyIsTooLong)739	}740}741742pub trait TrySetProperty: Sized {743	type Value;744745	fn try_scoped_set(746		&mut self,747		scope: PropertyScope,748		key: PropertyKey,749		value: Self::Value,750	) -> Result<(), PropertiesError>;751752	fn try_scoped_set_from_iter<I, KV>(753		&mut self,754		scope: PropertyScope,755		iter: I,756	) -> Result<(), PropertiesError>757	where758		I: Iterator<Item = KV>,759		KV: Into<(PropertyKey, Self::Value)>,760	{761		for kv in iter {762			let (key, value) = kv.into();763			self.try_scoped_set(scope, key, value)?;764		}765766		Ok(())767	}768769	fn try_set(&mut self, key: PropertyKey, value: Self::Value) -> Result<(), PropertiesError> {770		self.try_scoped_set(PropertyScope::None, key, value)771	}772773	fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>774	where775		I: Iterator<Item = KV>,776		KV: Into<(PropertyKey, Self::Value)>,777	{778		self.try_scoped_set_from_iter(PropertyScope::None, iter)779	}780}781782#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]783#[derivative(Default(bound = ""))]784pub struct PropertiesMap<Value>(785	BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,786);787788impl<Value> PropertiesMap<Value> {789	pub fn new() -> Self {790		Self(BoundedBTreeMap::new())791	}792793	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {794		Self::check_property_key(key)?;795796		Ok(self.0.remove(key))797	}798799	pub fn get(&self, key: &PropertyKey) -> Option<&Value> {800		self.0.get(key)801	}802803	pub fn contains_key(&self, key: &PropertyKey) -> bool {804		self.0.contains_key(key)805	}806807	fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {808		if key.is_empty() {809			return Err(PropertiesError::EmptyPropertyKey);810		}811812		for byte in key.as_slice().iter() {813			let byte = *byte;814815			if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' {816				return Err(PropertiesError::InvalidCharacterInPropertyKey);817			}818		}819820		Ok(())821	}822}823824impl<Value> IntoIterator for PropertiesMap<Value> {825	type Item = (PropertyKey, Value);826	type IntoIter = <827		BoundedBTreeMap<828			PropertyKey,829			Value,830			ConstU32<MAX_PROPERTIES_PER_ITEM>831		> as IntoIterator832	>::IntoIter;833834	fn into_iter(self) -> Self::IntoIter {835		self.0.into_iter()836	}837}838839impl<Value> TrySetProperty for PropertiesMap<Value> {840	type Value = Value;841842	fn try_scoped_set(843		&mut self,844		scope: PropertyScope,845		key: PropertyKey,846		value: Self::Value,847	) -> Result<(), PropertiesError> {848		Self::check_property_key(&key)?;849850		let key = scope.apply(key)?;851		self.0852			.try_insert(key, value)853			.map_err(|_| PropertiesError::PropertyLimitReached)?;854855		Ok(())856	}857}858859pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;860861#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, MaxEncodedLen)]862pub struct Properties {863	map: PropertiesMap<PropertyValue>,864	consumed_space: u32,865	space_limit: u32,866}867868impl Properties {869	pub fn new(space_limit: u32) -> Self {870		Self {871			map: PropertiesMap::new(),872			consumed_space: 0,873			space_limit,874		}875	}876877	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {878		let value = self.map.remove(key)?;879880		if let Some(ref value) = value {881			let value_len = value.len() as u32;882			self.consumed_space -= value_len;883		}884885		Ok(value)886	}887888	pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {889		self.map.get(key)890	}891}892893impl IntoIterator for Properties {894	type Item = (PropertyKey, PropertyValue);895	type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;896897	fn into_iter(self) -> Self::IntoIter {898		self.map.into_iter()899	}900}901902impl TrySetProperty for Properties {903	type Value = PropertyValue;904905	fn try_scoped_set(906		&mut self,907		scope: PropertyScope,908		key: PropertyKey,909		value: Self::Value,910	) -> Result<(), PropertiesError> {911		let value_len = value.len();912913		if self.consumed_space as usize + value_len > self.space_limit as usize914			&& !cfg!(feature = "runtime-benchmarks")915		{916			return Err(PropertiesError::NoSpaceForProperty);917		}918919		self.map.try_scoped_set(scope, key, value)?;920921		self.consumed_space += value_len as u32;922923		Ok(())924	}925}926927pub struct CollectionProperties;928929impl Get<Properties> for CollectionProperties {930	fn get() -> Properties {931		Properties::new(MAX_COLLECTION_PROPERTIES_SIZE)932	}933}934935pub struct TokenProperties;936937impl Get<Properties> for TokenProperties {938	fn get() -> Properties {939		Properties::new(MAX_TOKEN_PROPERTIES_SIZE)940	}941}942943// RMRK944// todo document?945parameter_types! {946	#[derive(PartialEq, TypeInfo)]947	pub const RmrkStringLimit: u32 = 128;948	#[derive(PartialEq)]949	pub const RmrkCollectionSymbolLimit: u32 = 100;950	#[derive(PartialEq)]951	pub const RmrkResourceSymbolLimit: u32 = 10;952	#[derive(PartialEq)]953	pub const RmrkKeyLimit: u32 = 32;954	#[derive(PartialEq)]955	pub const RmrkValueLimit: u32 = 256;956	#[derive(PartialEq)]957	pub const RmrkMaxCollectionsEquippablePerPart: u32 = 100;958	#[derive(PartialEq)]959	pub const RmrkPartsLimit: u32 = 3;960	#[derive(PartialEq)]961	pub const RmrkMaxPriorities: u32 = 3;962}963964impl From<RmrkCollectionId> for CollectionId {965	fn from(id: RmrkCollectionId) -> Self {966		Self(id)967	}968}969970impl From<RmrkNftId> for TokenId {971	fn from(id: RmrkNftId) -> Self {972		Self(id)973	}974}975976pub type RmrkCollectionInfo<AccountId> =977	CollectionInfo<RmrkString, RmrkCollectionSymbol, AccountId>;978pub type RmrkInstanceInfo<AccountId> = NftInfo<AccountId, Permill, RmrkString>;979pub type RmrkResourceInfo = ResourceInfo<RmrkString, RmrkBoundedParts>;980pub type RmrkPropertyInfo = PropertyInfo<RmrkKeyString, RmrkValueString>;981pub type RmrkBaseInfo<AccountId> = BaseInfo<AccountId, RmrkString>;982pub type RmrkPartType =983	PartType<RmrkString, BoundedVec<RmrkCollectionId, RmrkMaxCollectionsEquippablePerPart>>;984pub type RmrkThemeProperty = ThemeProperty<RmrkString>;985pub type RmrkTheme = Theme<RmrkString, Vec<RmrkThemeProperty>>;986pub type RmrkResourceTypes = ResourceTypes<RmrkString, RmrkBoundedParts>;987988pub type RmrkBasicResource = BasicResource<RmrkString>;989pub type RmrkComposableResource = ComposableResource<RmrkString, RmrkBoundedParts>;990pub type RmrkSlotResource = SlotResource<RmrkString>;991992pub type RmrkString = BoundedVec<u8, RmrkStringLimit>;993pub type RmrkCollectionSymbol = BoundedVec<u8, RmrkCollectionSymbolLimit>;994pub type RmrkKeyString = BoundedVec<u8, RmrkKeyLimit>;995pub type RmrkValueString = BoundedVec<u8, RmrkValueLimit>;996pub type RmrkBoundedResource = BoundedVec<u8, RmrkResourceSymbolLimit>;997pub type RmrkBoundedParts = BoundedVec<RmrkPartId, RmrkPartsLimit>; // todo make sure it is needed998999pub type RmrkRpcString = Vec<u8>;1000pub type RmrkThemeName = RmrkRpcString;1001pub type RmrkPropertyKey = RmrkRpcString;
after · primitives/data-structs/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::{20	convert::{TryFrom, TryInto},21	fmt,22};23use frame_support::{24	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},25	traits::Get,26	parameter_types,27};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, Permill};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use frame_support::{BoundedVec, traits::ConstU32};36use derivative::Derivative;37use scale_info::TypeInfo;3839// RMRK40use rmrk_traits::{41	CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,42	ResourceTypes, BasicResource, ComposableResource, SlotResource,43};44pub use rmrk_traits::{45	primitives::{46		CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,47		PartId as RmrkPartId, ResourceId as RmrkResourceId,48	},49	NftChild as RmrkNftChild, AccountIdOrCollectionNftTuple as RmrkAccountIdOrCollectionNftTuple,50	FixedPart as RmrkFixedPart, SlotPart as RmrkSlotPart, EquippableList as RmrkEquippableList,51};5253mod bounded;54pub mod budget;55pub mod mapping;56mod migration;5758pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;59pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;60pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;6162pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {63	100_00064} else {65	1066};67pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {68	100_00069} else {70	1071};72pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {73	204874} else {75	1076};77pub const COLLECTION_ADMINS_LIMIT: u32 = 5;78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;79pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {80	1_000_00081} else {82	1083};8485// Timeouts for item types in passed blocks86pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;87pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;88pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;8990pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9192// Schema limits93pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;94pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;95pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;9697pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;9899pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;100pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;101pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;102103pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;104pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;105pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;106107pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;108pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;109110// RMRK constants111pub const RMRK_STRING_LIMIT: u32 = 128;112pub const RMRK_COLLECTION_SYMBOL_LIMIT: u32 = 100;113pub const RMRK_RESOURCE_SYMBOL_LIMIT: u32 = 10;114pub const RMRK_KEY_LIMIT: u32 = 32;115pub const RMRK_VALUE_LIMIT: u32 = 256;116117/// How much items can be created per single118/// create_many call119pub const MAX_ITEMS_PER_BATCH: u32 = 200;120121pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;122123#[derive(124	Encode,125	Decode,126	PartialEq,127	Eq,128	PartialOrd,129	Ord,130	Clone,131	Copy,132	Debug,133	Default,134	TypeInfo,135	MaxEncodedLen,136)]137#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]138pub struct CollectionId(pub u32);139impl EncodeLike<u32> for CollectionId {}140impl EncodeLike<CollectionId> for u32 {}141142#[derive(143	Encode,144	Decode,145	PartialEq,146	Eq,147	PartialOrd,148	Ord,149	Clone,150	Copy,151	Debug,152	Default,153	TypeInfo,154	MaxEncodedLen,155)]156#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]157pub struct TokenId(pub u32);158impl EncodeLike<u32> for TokenId {}159impl EncodeLike<TokenId> for u32 {}160161impl TokenId {162	pub fn try_next(self) -> Result<TokenId, ArithmeticError> {163		self.0164			.checked_add(1)165			.ok_or(ArithmeticError::Overflow)166			.map(Self)167	}168}169170impl From<TokenId> for U256 {171	fn from(t: TokenId) -> Self {172		t.0.into()173	}174}175176impl TryFrom<U256> for TokenId {177	type Error = &'static str;178179	fn try_from(value: U256) -> Result<Self, Self::Error> {180		Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))181	}182}183184#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]185#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]186pub struct TokenData<CrossAccountId> {187	pub properties: Vec<Property>,188	pub owner: Option<CrossAccountId>,189}190191pub struct OverflowError;192impl From<OverflowError> for &'static str {193	fn from(_: OverflowError) -> Self {194		"overflow occured"195	}196}197198pub type DecimalPoints = u8;199200#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]201#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]202pub enum CollectionMode {203	NFT,204	// decimal points205	Fungible(DecimalPoints),206	ReFungible,207}208209impl CollectionMode {210	pub fn id(&self) -> u8 {211		match self {212			CollectionMode::NFT => 1,213			CollectionMode::Fungible(_) => 2,214			CollectionMode::ReFungible => 3,215		}216	}217}218219pub trait SponsoringResolve<AccountId, Call> {220	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;221}222223#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]224#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]225pub enum AccessMode {226	Normal,227	AllowList,228}229impl Default for AccessMode {230	fn default() -> Self {231		Self::Normal232	}233}234235#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]236#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]237pub enum SchemaVersion {238	ImageURL,239	Unique,240}241impl Default for SchemaVersion {242	fn default() -> Self {243		Self::ImageURL244	}245}246247#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]248#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]249pub struct Ownership<AccountId> {250	pub owner: AccountId,251	pub fraction: u128,252}253254#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]255#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]256pub enum SponsorshipState<AccountId> {257	/// The fees are applied to the transaction sender258	Disabled,259	Unconfirmed(AccountId),260	/// Transactions are sponsored by specified account261	Confirmed(AccountId),262}263264impl<AccountId> SponsorshipState<AccountId> {265	pub fn sponsor(&self) -> Option<&AccountId> {266		match self {267			Self::Confirmed(sponsor) => Some(sponsor),268			_ => None,269		}270	}271272	pub fn pending_sponsor(&self) -> Option<&AccountId> {273		match self {274			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),275			_ => None,276		}277	}278279	pub fn confirmed(&self) -> bool {280		matches!(self, Self::Confirmed(_))281	}282}283284impl<T> Default for SponsorshipState<T> {285	fn default() -> Self {286		Self::Disabled287	}288}289290/// Used in storage291#[struct_versioning::versioned(version = 2, upper)]292#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]293pub struct Collection<AccountId> {294	pub owner: AccountId,295	pub mode: CollectionMode,296	#[version(..2)]297	pub access: AccessMode,298	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,299	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,300	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,301302	#[version(..2)]303	pub mint_mode: bool,304305	#[version(..2)]306	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,307308	#[version(..2)]309	pub schema_version: SchemaVersion,310	pub sponsorship: SponsorshipState<AccountId>,311312	pub limits: CollectionLimits,313314	#[version(2.., upper(Default::default()))]315	pub permissions: CollectionPermissions,316317	/// Marks that this collection is not "unique", and managed from external.318	#[version(2.., upper(false))]319	pub external_collection: bool,320321	#[version(..2)]322	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,323324	#[version(..2)]325	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,326327	#[version(..2)]328	pub meta_update_permission: MetaUpdatePermission,329}330331/// Used in RPC calls332#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]333#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]334pub struct RpcCollection<AccountId> {335	pub owner: AccountId,336	pub mode: CollectionMode,337	pub name: Vec<u16>,338	pub description: Vec<u16>,339	pub token_prefix: Vec<u8>,340	pub sponsorship: SponsorshipState<AccountId>,341	pub limits: CollectionLimits,342	pub permissions: CollectionPermissions,343	pub token_property_permissions: Vec<PropertyKeyPermission>,344	pub properties: Vec<Property>,345	pub read_only: bool,346}347348#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]349#[derivative(Debug, Default(bound = ""))]350pub struct CreateCollectionData<AccountId> {351	#[derivative(Default(value = "CollectionMode::NFT"))]352	pub mode: CollectionMode,353	pub access: Option<AccessMode>,354	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,355	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,356	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,357	pub pending_sponsor: Option<AccountId>,358	pub limits: Option<CollectionLimits>,359	pub permissions: Option<CollectionPermissions>,360	pub token_property_permissions: CollectionPropertiesPermissionsVec,361	pub properties: CollectionPropertiesVec,362}363364pub type CollectionPropertiesPermissionsVec =365	BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;366367pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;368369/// All fields are wrapped in `Option`s, where None means chain default370// When adding/removing fields from this struct - don't forget to also update clamp_limits371#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]372#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]373pub struct CollectionLimits {374	pub account_token_ownership_limit: Option<u32>,375	pub sponsored_data_size: Option<u32>,376377	/// FIXME should we delete this or repurpose it?378	/// None - setVariableMetadata is not sponsored379	/// Some(v) - setVariableMetadata is sponsored380	///           if there is v block between txs381	pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,382	pub token_limit: Option<u32>,383384	// Timeouts for item types in passed blocks385	pub sponsor_transfer_timeout: Option<u32>,386	pub sponsor_approve_timeout: Option<u32>,387	pub owner_can_transfer: Option<bool>,388	pub owner_can_destroy: Option<bool>,389	pub transfers_enabled: Option<bool>,390}391392impl CollectionLimits {393	pub fn account_token_ownership_limit(&self) -> u32 {394		self.account_token_ownership_limit395			.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)396			.min(MAX_TOKEN_OWNERSHIP)397	}398	pub fn sponsored_data_size(&self) -> u32 {399		self.sponsored_data_size400			.unwrap_or(CUSTOM_DATA_LIMIT)401			.min(CUSTOM_DATA_LIMIT)402	}403	pub fn token_limit(&self) -> u32 {404		self.token_limit405			.unwrap_or(COLLECTION_TOKEN_LIMIT)406			.min(COLLECTION_TOKEN_LIMIT)407	}408	pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {409		self.sponsor_transfer_timeout410			.unwrap_or(default)411			.min(MAX_SPONSOR_TIMEOUT)412	}413	pub fn sponsor_approve_timeout(&self) -> u32 {414		self.sponsor_approve_timeout415			.unwrap_or(SPONSOR_APPROVE_TIMEOUT)416			.min(MAX_SPONSOR_TIMEOUT)417	}418	pub fn owner_can_transfer(&self) -> bool {419		self.owner_can_transfer.unwrap_or(true)420	}421	pub fn owner_can_destroy(&self) -> bool {422		self.owner_can_destroy.unwrap_or(true)423	}424	pub fn transfers_enabled(&self) -> bool {425		self.transfers_enabled.unwrap_or(true)426	}427	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {428		match self429			.sponsored_data_rate_limit430			.unwrap_or(SponsoringRateLimit::SponsoringDisabled)431		{432			SponsoringRateLimit::SponsoringDisabled => None,433			SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),434		}435	}436}437438// When adding/removing fields from this struct - don't forget to also update clamp_limits439#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]440#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]441pub struct CollectionPermissions {442	pub access: Option<AccessMode>,443	pub mint_mode: Option<bool>,444	pub nesting: Option<NestingPermissions>,445}446447impl CollectionPermissions {448	pub fn access(&self) -> AccessMode {449		self.access.unwrap_or(AccessMode::Normal)450	}451	pub fn mint_mode(&self) -> bool {452		self.mint_mode.unwrap_or(false)453	}454	pub fn nesting(&self) -> &NestingPermissions {455		static DEFAULT: NestingPermissions = NestingPermissions {456			token_owner: false,457			admin: false,458			restricted: None,459460			permissive: false,461		};462		self.nesting.as_ref().unwrap_or(&DEFAULT)463	}464}465466type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;467468#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]469#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]470#[derivative(Debug)]471pub struct OwnerRestrictedSet(472	#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]473	#[derivative(Debug(format_with = "bounded::set_debug"))]474	pub OwnerRestrictedSetInner,475);476impl OwnerRestrictedSet {477	pub fn new() -> Self {478		Self(Default::default())479	}480}481impl core::ops::Deref for OwnerRestrictedSet {482	type Target = OwnerRestrictedSetInner;483	fn deref(&self) -> &Self::Target {484		&self.0485	}486}487impl core::ops::DerefMut for OwnerRestrictedSet {488	fn deref_mut(&mut self) -> &mut Self::Target {489		&mut self.0490	}491}492493#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]494#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]495#[derivative(Debug)]496pub struct NestingPermissions {497	/// Owner of token can nest tokens under it498	pub token_owner: bool,499	/// Admin of token collection can nest tokens under token500	pub admin: bool,501	/// If set - only tokens from specified collections can be nested502	pub restricted: Option<OwnerRestrictedSet>,503504	/// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`505	pub permissive: bool,506}507508#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]509#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]510pub enum SponsoringRateLimit {511	SponsoringDisabled,512	Blocks(u32),513}514515#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]516#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]517#[derivative(Debug)]518pub struct CreateNftData {519	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]520	#[derivative(Debug(format_with = "bounded::vec_debug"))]521	pub properties: CollectionPropertiesVec,522}523524#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]525#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]526pub struct CreateFungibleData {527	pub value: u128,528}529530#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]531#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]532#[derivative(Debug)]533pub struct CreateReFungibleData {534	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]535	#[derivative(Debug(format_with = "bounded::vec_debug"))]536	pub const_data: BoundedVec<u8, CustomDataLimit>,537	pub pieces: u128,538}539540#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]541#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]542pub enum MetaUpdatePermission {543	ItemOwner,544	Admin,545	None,546}547548#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]549#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]550pub enum CreateItemData {551	NFT(CreateNftData),552	Fungible(CreateFungibleData),553	ReFungible(CreateReFungibleData),554}555556#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]557#[derivative(Debug)]558pub struct CreateNftExData<CrossAccountId> {559	#[derivative(Debug(format_with = "bounded::vec_debug"))]560	pub properties: CollectionPropertiesVec,561	pub owner: CrossAccountId,562}563564#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]565#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]566pub struct CreateRefungibleExData<CrossAccountId> {567	#[derivative(Debug(format_with = "bounded::vec_debug"))]568	pub const_data: BoundedVec<u8, CustomDataLimit>,569	#[derivative(Debug(format_with = "bounded::map_debug"))]570	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,571}572573#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]574#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]575pub enum CreateItemExData<CrossAccountId> {576	NFT(577		#[derivative(Debug(format_with = "bounded::vec_debug"))]578		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,579	),580	Fungible(581		#[derivative(Debug(format_with = "bounded::map_debug"))]582		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,583	),584	/// Many tokens, each may have only one owner585	RefungibleMultipleItems(586		#[derivative(Debug(format_with = "bounded::vec_debug"))]587		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,588	),589	/// Single token, which may have many owners590	RefungibleMultipleOwners(CreateRefungibleExData<CrossAccountId>),591}592593impl CreateItemData {594	pub fn data_size(&self) -> usize {595		match self {596			CreateItemData::ReFungible(data) => data.const_data.len(),597			_ => 0,598		}599	}600}601602impl From<CreateNftData> for CreateItemData {603	fn from(item: CreateNftData) -> Self {604		CreateItemData::NFT(item)605	}606}607608impl From<CreateReFungibleData> for CreateItemData {609	fn from(item: CreateReFungibleData) -> Self {610		CreateItemData::ReFungible(item)611	}612}613614impl From<CreateFungibleData> for CreateItemData {615	fn from(item: CreateFungibleData) -> Self {616		CreateItemData::Fungible(item)617	}618}619620#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]621#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]622// todo possibly rename to be used generally as an address pair623pub struct TokenChild {624	pub token: TokenId,625	pub collection: CollectionId,626}627628#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]629#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]630pub struct CollectionStats {631	pub created: u32,632	pub destroyed: u32,633	pub alive: u32,634}635636#[derive(Encode, Decode, Clone, Debug)]637#[cfg_attr(feature = "std", derive(PartialEq))]638pub struct PhantomType<T>(core::marker::PhantomData<T>);639640impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {641	type Identity = PhantomType<T>;642643	fn type_info() -> scale_info::Type {644		use scale_info::{645			Type, Path,646			build::{FieldsBuilder, UnnamedFields},647			type_params,648		};649		Type::builder()650			.path(Path::new("up_data_structs", "PhantomType"))651			.type_params(type_params!(T))652			.composite(<FieldsBuilder<UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()))653	}654}655impl<T> MaxEncodedLen for PhantomType<T> {656	fn max_encoded_len() -> usize {657		0658	}659}660661pub type PropertyKey = BoundedVec<u8, ConstU32<MAX_PROPERTY_KEY_LENGTH>>;662pub type PropertyValue = BoundedVec<u8, ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;663664#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]665#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]666pub struct PropertyPermission {667	pub mutable: bool,668	pub collection_admin: bool,669	pub token_owner: bool,670}671672impl PropertyPermission {673	pub fn none() -> Self {674		Self {675			mutable: true,676			collection_admin: false,677			token_owner: false,678		}679	}680}681682#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]683#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]684pub struct Property {685	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]686	pub key: PropertyKey,687688	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]689	pub value: PropertyValue,690}691692impl Into<(PropertyKey, PropertyValue)> for Property {693	fn into(self) -> (PropertyKey, PropertyValue) {694		(self.key, self.value)695	}696}697698#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]699#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]700pub struct PropertyKeyPermission {701	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]702	pub key: PropertyKey,703704	pub permission: PropertyPermission,705}706707impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {708	fn into(self) -> (PropertyKey, PropertyPermission) {709		(self.key, self.permission)710	}711}712713#[derive(Debug)]714pub enum PropertiesError {715	NoSpaceForProperty,716	PropertyLimitReached,717	InvalidCharacterInPropertyKey,718	PropertyKeyIsTooLong,719	EmptyPropertyKey,720}721722#[derive(Clone, Copy)]723pub enum PropertyScope {724	None,725	Rmrk,726}727728impl PropertyScope {729	pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {730		let scope_str: &[u8] = match self {731			Self::None => return Ok(key),732			Self::Rmrk => b"rmrk",733		};734735		[scope_str, b":", key.as_slice()]736			.concat()737			.try_into()738			.map_err(|_| PropertiesError::PropertyKeyIsTooLong)739	}740}741742pub trait TrySetProperty: Sized {743	type Value;744745	fn try_scoped_set(746		&mut self,747		scope: PropertyScope,748		key: PropertyKey,749		value: Self::Value,750	) -> Result<(), PropertiesError>;751752	fn try_scoped_set_from_iter<I, KV>(753		&mut self,754		scope: PropertyScope,755		iter: I,756	) -> Result<(), PropertiesError>757	where758		I: Iterator<Item = KV>,759		KV: Into<(PropertyKey, Self::Value)>,760	{761		for kv in iter {762			let (key, value) = kv.into();763			self.try_scoped_set(scope, key, value)?;764		}765766		Ok(())767	}768769	fn try_set(&mut self, key: PropertyKey, value: Self::Value) -> Result<(), PropertiesError> {770		self.try_scoped_set(PropertyScope::None, key, value)771	}772773	fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>774	where775		I: Iterator<Item = KV>,776		KV: Into<(PropertyKey, Self::Value)>,777	{778		self.try_scoped_set_from_iter(PropertyScope::None, iter)779	}780}781782#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]783#[derivative(Default(bound = ""))]784pub struct PropertiesMap<Value>(785	BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,786);787788impl<Value> PropertiesMap<Value> {789	pub fn new() -> Self {790		Self(BoundedBTreeMap::new())791	}792793	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {794		Self::check_property_key(key)?;795796		Ok(self.0.remove(key))797	}798799	pub fn get(&self, key: &PropertyKey) -> Option<&Value> {800		self.0.get(key)801	}802803	pub fn contains_key(&self, key: &PropertyKey) -> bool {804		self.0.contains_key(key)805	}806807	fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {808		if key.is_empty() {809			return Err(PropertiesError::EmptyPropertyKey);810		}811812		for byte in key.as_slice().iter() {813			let byte = *byte;814815			if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' && byte != b'.' {816				return Err(PropertiesError::InvalidCharacterInPropertyKey);817			}818		}819820		Ok(())821	}822}823824impl<Value> IntoIterator for PropertiesMap<Value> {825	type Item = (PropertyKey, Value);826	type IntoIter = <827		BoundedBTreeMap<828			PropertyKey,829			Value,830			ConstU32<MAX_PROPERTIES_PER_ITEM>831		> as IntoIterator832	>::IntoIter;833834	fn into_iter(self) -> Self::IntoIter {835		self.0.into_iter()836	}837}838839impl<Value> TrySetProperty for PropertiesMap<Value> {840	type Value = Value;841842	fn try_scoped_set(843		&mut self,844		scope: PropertyScope,845		key: PropertyKey,846		value: Self::Value,847	) -> Result<(), PropertiesError> {848		Self::check_property_key(&key)?;849850		let key = scope.apply(key)?;851		self.0852			.try_insert(key, value)853			.map_err(|_| PropertiesError::PropertyLimitReached)?;854855		Ok(())856	}857}858859pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;860861#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, MaxEncodedLen)]862pub struct Properties {863	map: PropertiesMap<PropertyValue>,864	consumed_space: u32,865	space_limit: u32,866}867868impl Properties {869	pub fn new(space_limit: u32) -> Self {870		Self {871			map: PropertiesMap::new(),872			consumed_space: 0,873			space_limit,874		}875	}876877	pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {878		let value = self.map.remove(key)?;879880		if let Some(ref value) = value {881			let value_len = value.len() as u32;882			self.consumed_space -= value_len;883		}884885		Ok(value)886	}887888	pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {889		self.map.get(key)890	}891}892893impl IntoIterator for Properties {894	type Item = (PropertyKey, PropertyValue);895	type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;896897	fn into_iter(self) -> Self::IntoIter {898		self.map.into_iter()899	}900}901902impl TrySetProperty for Properties {903	type Value = PropertyValue;904905	fn try_scoped_set(906		&mut self,907		scope: PropertyScope,908		key: PropertyKey,909		value: Self::Value,910	) -> Result<(), PropertiesError> {911		let value_len = value.len();912913		if self.consumed_space as usize + value_len > self.space_limit as usize914			&& !cfg!(feature = "runtime-benchmarks")915		{916			return Err(PropertiesError::NoSpaceForProperty);917		}918919		self.map.try_scoped_set(scope, key, value)?;920921		self.consumed_space += value_len as u32;922923		Ok(())924	}925}926927pub struct CollectionProperties;928929impl Get<Properties> for CollectionProperties {930	fn get() -> Properties {931		Properties::new(MAX_COLLECTION_PROPERTIES_SIZE)932	}933}934935pub struct TokenProperties;936937impl Get<Properties> for TokenProperties {938	fn get() -> Properties {939		Properties::new(MAX_TOKEN_PROPERTIES_SIZE)940	}941}942943// RMRK944// todo document?945parameter_types! {946	#[derive(PartialEq, TypeInfo)]947	pub const RmrkStringLimit: u32 = 128;948	#[derive(PartialEq)]949	pub const RmrkCollectionSymbolLimit: u32 = 100;950	#[derive(PartialEq)]951	pub const RmrkResourceSymbolLimit: u32 = 10;952	#[derive(PartialEq)]953	pub const RmrkKeyLimit: u32 = 32;954	#[derive(PartialEq)]955	pub const RmrkValueLimit: u32 = 256;956	#[derive(PartialEq)]957	pub const RmrkMaxCollectionsEquippablePerPart: u32 = 100;958	#[derive(PartialEq)]959	pub const RmrkPartsLimit: u32 = 3;960	#[derive(PartialEq)]961	pub const RmrkMaxPriorities: u32 = 3;962}963964impl From<RmrkCollectionId> for CollectionId {965	fn from(id: RmrkCollectionId) -> Self {966		Self(id)967	}968}969970impl From<RmrkNftId> for TokenId {971	fn from(id: RmrkNftId) -> Self {972		Self(id)973	}974}975976pub type RmrkCollectionInfo<AccountId> =977	CollectionInfo<RmrkString, RmrkCollectionSymbol, AccountId>;978pub type RmrkInstanceInfo<AccountId> = NftInfo<AccountId, Permill, RmrkString>;979pub type RmrkResourceInfo = ResourceInfo<RmrkString, RmrkBoundedParts>;980pub type RmrkPropertyInfo = PropertyInfo<RmrkKeyString, RmrkValueString>;981pub type RmrkBaseInfo<AccountId> = BaseInfo<AccountId, RmrkString>;982pub type RmrkPartType =983	PartType<RmrkString, BoundedVec<RmrkCollectionId, RmrkMaxCollectionsEquippablePerPart>>;984pub type RmrkThemeProperty = ThemeProperty<RmrkString>;985pub type RmrkTheme = Theme<RmrkString, Vec<RmrkThemeProperty>>;986pub type RmrkResourceTypes = ResourceTypes<RmrkString, RmrkBoundedParts>;987988pub type RmrkBasicResource = BasicResource<RmrkString>;989pub type RmrkComposableResource = ComposableResource<RmrkString, RmrkBoundedParts>;990pub type RmrkSlotResource = SlotResource<RmrkString>;991992pub type RmrkString = BoundedVec<u8, RmrkStringLimit>;993pub type RmrkCollectionSymbol = BoundedVec<u8, RmrkCollectionSymbolLimit>;994pub type RmrkKeyString = BoundedVec<u8, RmrkKeyLimit>;995pub type RmrkValueString = BoundedVec<u8, RmrkValueLimit>;996pub type RmrkBoundedResource = BoundedVec<u8, RmrkResourceSymbolLimit>;997pub type RmrkBoundedParts = BoundedVec<RmrkPartId, RmrkPartsLimit>; // todo make sure it is needed998999pub type RmrkRpcString = Vec<u8>;1000pub type RmrkThemeName = RmrkRpcString;1001pub type RmrkPropertyKey = RmrkRpcString;
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -112,6 +112,7 @@
 		+ pallet_fungible::Config
 		+ pallet_nonfungible::Config
 		+ pallet_refungible::Config,
+	T::AccountId: From<[u8; 32]>,
 {
 	fn is_reserved(target: &H160) -> bool {
 		map_eth_to_id(target).is_some()
modifiedruntime/tests/src/tests.rsdiffbeforeafterboth
--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -1264,7 +1264,6 @@
 		let collection1_id =
 			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));
 		let origin1 = Origin::signed(1);
-		let origin2 = Origin::signed(2);
 
 		// Add collection admins 2 and 3
 		assert_ok!(Unique::add_collection_admin(
@@ -1273,7 +1272,7 @@
 			account(2)
 		));
 		assert_ok!(Unique::add_collection_admin(
-			origin1,
+			origin1.clone(),
 			collection1_id,
 			account(3)
 		));
@@ -1289,7 +1288,7 @@
 
 		// remove admin 3
 		assert_ok!(Unique::remove_collection_admin(
-			origin2,
+			origin1,
 			CollectionId(1),
 			account(3)
 		));
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -174,7 +174,24 @@
 	function finishMinting() external returns (bool);
 }
 
-// Selector: 6aea9834
+// Selector: 780e9d63
+interface ERC721Enumerable is Dummy, ERC165 {
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) external view returns (uint256);
+
+	// Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		external
+		view
+		returns (uint256);
+
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() external view returns (uint256);
+}
+
+// Selector: 7d9262e6
 interface Collection is Dummy, ERC165 {
 	// Selector: setCollectionProperty(string,bytes) 2f073f66
 	function setCollectionProperty(string memory key, bytes memory value)
@@ -206,6 +223,12 @@
 	// Selector: contractAddress() f6b4dfb4
 	function contractAddress() external view returns (address);
 
+	// Selector: addCollectionAdminSubstrate(uint256) 5730062b
+	function addCollectionAdminSubstrate(uint256 newAdmin) external view;
+
+	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+	function removeCollectionAdminSubstrate(uint256 newAdmin) external view;
+
 	// Selector: addCollectionAdmin(address) 92e462c7
 	function addCollectionAdmin(address newAdmin) external view;
 
@@ -230,23 +253,6 @@
 
 	// Selector: setCollectionMintMode(bool) 00018e84
 	function setCollectionMintMode(bool mode) external;
-}
-
-// Selector: 780e9d63
-interface ERC721Enumerable is Dummy, ERC165 {
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) external view returns (uint256);
-
-	// Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		external
-		view
-		returns (uint256);
-
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() external view returns (uint256);
 }
 
 // Selector: d74d154f
addedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -0,0 +1,296 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// 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 {expect} from 'chai';
+import privateKey from '../substrate/privateKey';
+import {
+  createEthAccount,
+  createEthAccountWithBalance, 
+  evmCollection, 
+  evmCollectionHelpers, 
+  getCollectionAddressFromResult, 
+  itWeb3,
+} from './util/helpers';
+
+describe('Add collection admins', () => {
+  itWeb3('Add admin by owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = await createEthAccount(web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+      .to.be.eq(newAdmin.toLocaleLowerCase());
+  });
+
+  itWeb3('Add substrate admin by owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = privateKey('//Alice');
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
+      .to.be.eq(newAdmin.address.toLocaleLowerCase());
+  });
+
+  itWeb3('(!negative tests!) Add admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const admin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(admin).send();
+    
+    const user = await createEthAccount(web3);
+    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(1);
+    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+      .to.be.eq(admin.toLocaleLowerCase());
+  });
+
+  itWeb3('(!negative tests!) Add admin by USER is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const notAdmin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    
+    const user = await createEthAccount(web3);
+    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(0);
+  });
+
+  itWeb3('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const admin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(admin).send();
+
+    const notAdmin = privateKey('//Alice');
+    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin.addressRaw).call({from: admin}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(1);
+    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+      .to.be.eq(admin.toLocaleLowerCase());
+  });
+  
+  itWeb3('(!negative tests!) Add substrate admin by USER is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const notAdmin0 = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    const notAdmin1 = privateKey('//Alice');
+    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin1.addressRaw).call({from: notAdmin0}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(0);
+  });
+});
+
+describe('Remove collection admins', () => {
+  itWeb3('Remove admin by owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = await createEthAccount(web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
+    {
+      const adminList = await api.rpc.unique.adminlist(collectionId);
+      expect(adminList.length).to.be.eq(1);
+      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+        .to.be.eq(newAdmin.toLocaleLowerCase());
+    }
+
+    await collectionEvm.methods.removeCollectionAdmin(newAdmin).send();
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(0);
+  });
+
+  itWeb3('Remove substrate admin by owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = privateKey('//Alice');
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();
+    {
+      const adminList = await api.rpc.unique.adminlist(collectionId);
+      expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
+        .to.be.eq(newAdmin.address.toLocaleLowerCase());
+    }
+    
+    await collectionEvm.methods.removeCollectionAdminSubstrate(newAdmin.addressRaw).send();
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(0);
+  });
+
+  itWeb3('(!negative tests!) Remove admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+
+    const admin0 = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    await collectionEvm.methods.addCollectionAdmin(admin0).send();
+    const admin1 = await createEthAccount(web3);
+    await collectionEvm.methods.addCollectionAdmin(admin1).send();
+
+    await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0}))
+      .to.be.rejectedWith('NoPermission');
+    {
+      const adminList = await api.rpc.unique.adminlist(collectionId);
+      expect(adminList.length).to.be.eq(2);
+      expect(adminList.toString().toLocaleLowerCase())
+        .to.be.deep.contains(admin0.toLocaleLowerCase())
+        .to.be.deep.contains(admin1.toLocaleLowerCase());
+    }
+  });
+
+  itWeb3('(!negative tests!) Remove admin by USER is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+
+    const admin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    await collectionEvm.methods.addCollectionAdmin(admin).send();
+    const notAdmin = await createEthAccount(web3);
+
+    await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin}))
+      .to.be.rejectedWith('NoPermission');
+    {
+      const adminList = await api.rpc.unique.adminlist(collectionId);
+      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+        .to.be.eq(admin.toLocaleLowerCase());
+      expect(adminList.length).to.be.eq(1);
+    }
+  });
+
+  itWeb3('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const adminSub = privateKey('//Alice');
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();
+    const adminEth = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    await collectionEvm.methods.addCollectionAdmin(adminEth).send();
+
+    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: adminEth}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(2);
+    expect(adminList.toString().toLocaleLowerCase())
+      .to.be.deep.contains(adminSub.address.toLocaleLowerCase())
+      .to.be.deep.contains(adminEth.toLocaleLowerCase());
+  });
+
+  itWeb3('(!negative tests!) Remove substrate admin by USER is not allowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const adminSub = privateKey('//Alice');
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();
+    const notAdminEth = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: notAdminEth}))
+      .to.be.rejectedWith('NoPermission');
+
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList.length).to.be.eq(1);
+    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
+      .to.be.eq(adminSub.address.toLocaleLowerCase());
+  });
+});
\ No newline at end of file
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -233,7 +233,7 @@
     const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
     expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
-    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
 
     await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
@@ -241,14 +241,13 @@
     expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
 
     const user = createEthAccount(web3);
-    let nextTokenId = await collectionEvm.methods.nextTokenId().call();
+    const nextTokenId = await collectionEvm.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
 
     const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
     expect(oldPermissions.mintMode).to.be.false;
     expect(oldPermissions.access).to.be.equal('Normal');
 
-    //TODO: change value, when enum generated
     await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
     await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
     await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
@@ -260,34 +259,35 @@
     const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
     const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
 
-    nextTokenId = await collectionEvm.methods.nextTokenId().call({from: user});
-    expect(nextTokenId).to.be.equal('1');
-    result = await collectionEvm.methods.mintWithTokenURI(
-      user,
-      nextTokenId,
-      'Test URI',
-    ).send({from: user});
-    const events = normalizeEvents(result.events);
-    events[0].address = events[0].address.toLocaleLowerCase();
+    {
+      const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await collectionEvm.methods.mintWithTokenURI(
+        user,
+        nextTokenId,
+        'Test URI',
+      ).send({from: user});
+      const events = normalizeEvents(result.events);
 
-    expect(events).to.be.deep.equal([
-      {
-        address: collectionIdAddress.toLocaleLowerCase(),
-        event: 'Transfer',
-        args: {
-          from: '0x0000000000000000000000000000000000000000',
-          to: user,
-          tokenId: nextTokenId,
+      expect(events).to.be.deep.equal([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: user,
+            tokenId: nextTokenId,
+          },
         },
-      },
-    ]);
+      ]);
 
-    expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+      const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
 
-    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
-    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
-    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
-    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
+      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+      expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+    }
   });
 
   itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {
@@ -302,7 +302,7 @@
     const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
     expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
-    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
     const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
@@ -314,6 +314,7 @@
     
     const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
     const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+    
   
     const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
     const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
modifiedtests/src/eth/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createCollection.test.ts
+++ b/tests/src/eth/createCollection.test.ts
@@ -80,7 +80,7 @@
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
     const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
     expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
-    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
     const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
@@ -208,7 +208,7 @@
       const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
-        .call()).to.be.rejectedWith('Caller is not set as sponsor');
+        .call()).to.be.rejectedWith('caller is not set as sponsor');
     }
     {
       await expect(contractEvmFromNotOwner.methods
@@ -225,6 +225,6 @@
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     await expect(collectionEvm.methods
       .setCollectionLimit('badLimit', 'true')
-      .call()).to.be.rejectedWith('Unknown boolean limit "badLimit"');
+      .call()).to.be.rejectedWith('unknown boolean limit "badLimit"');
   });
 });
\ No newline at end of file
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -91,6 +91,15 @@
   },
   {
     "inputs": [
+      { "internalType": "uint256", "name": "newAdmin", "type": "uint256" }
+    ],
+    "name": "addCollectionAdminSubstrate",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "addToCollectionAllowList",
@@ -300,6 +309,15 @@
   },
   {
     "inputs": [
+      { "internalType": "uint256", "name": "newAdmin", "type": "uint256" }
+    ],
+    "name": "removeCollectionAdminSubstrate",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "removeFromCollectionAllowList",
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -5,7 +5,7 @@
 import type { BTreeMap, Bytes, Option, U256, U8aFixed, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types-codec';
 import type { AnyNumber, ITuple } from '@polkadot/types-codec/types';
 import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime';
-import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportWeightsPerDispatchClassU64, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUnqSchedulerScheduledV3, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsTokenChild } from '@polkadot/types/lookup';
+import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportWeightsPerDispatchClassU64, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUniqueSchedulerScheduledV3, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsTokenChild } from '@polkadot/types/lookup';
 import type { Observable } from '@polkadot/types/types';
 
 declare module '@polkadot/api-base/types/storage' {
@@ -436,7 +436,7 @@
       /**
        * Items to be executed, indexed by the block number that they should be executed on.
        **/
-      agenda: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<Option<PalletUnqSchedulerScheduledV3>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+      agenda: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<Option<PalletUniqueSchedulerScheduledV3>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
        * Lookup from identity to the block number and index of the task.
        **/
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -1,7 +1,7 @@
 // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
 /* eslint-disable */
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUnqSchedulerCall, PalletUnqSchedulerError, PalletUnqSchedulerEvent, PalletUnqSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default';
 import type { Data, StorageKey } from '@polkadot/types';
 import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, OptionBool, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -819,10 +819,10 @@
     PalletUniqueCall: PalletUniqueCall;
     PalletUniqueError: PalletUniqueError;
     PalletUniqueRawEvent: PalletUniqueRawEvent;
-    PalletUnqSchedulerCall: PalletUnqSchedulerCall;
-    PalletUnqSchedulerError: PalletUnqSchedulerError;
-    PalletUnqSchedulerEvent: PalletUnqSchedulerEvent;
-    PalletUnqSchedulerScheduledV3: PalletUnqSchedulerScheduledV3;
+    PalletUniqueSchedulerCall: PalletUniqueSchedulerCall;
+    PalletUniqueSchedulerError: PalletUniqueSchedulerError;
+    PalletUniqueSchedulerEvent: PalletUniqueSchedulerEvent;
+    PalletUniqueSchedulerScheduledV3: PalletUniqueSchedulerScheduledV3;
     PalletVersion: PalletVersion;
     PalletXcmCall: PalletXcmCall;
     PalletXcmError: PalletXcmError;
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -1784,8 +1784,8 @@
   readonly type: 'CollectionSponsorRemoved' | 'CollectionAdminAdded' | 'CollectionOwnedChanged' | 'CollectionSponsorSet' | 'SponsorshipConfirmed' | 'CollectionAdminRemoved' | 'AllowListAddressRemoved' | 'AllowListAddressAdded' | 'CollectionLimitSet' | 'CollectionPermissionSet';
 }
 
-/** @name PalletUnqSchedulerCall */
-export interface PalletUnqSchedulerCall extends Enum {
+/** @name PalletUniqueSchedulerCall */
+export interface PalletUniqueSchedulerCall extends Enum {
   readonly isScheduleNamed: boolean;
   readonly asScheduleNamed: {
     readonly id: U8aFixed;
@@ -1809,8 +1809,8 @@
   readonly type: 'ScheduleNamed' | 'CancelNamed' | 'ScheduleNamedAfter';
 }
 
-/** @name PalletUnqSchedulerError */
-export interface PalletUnqSchedulerError extends Enum {
+/** @name PalletUniqueSchedulerError */
+export interface PalletUniqueSchedulerError extends Enum {
   readonly isFailedToSchedule: boolean;
   readonly isNotFound: boolean;
   readonly isTargetBlockNumberInPast: boolean;
@@ -1818,8 +1818,8 @@
   readonly type: 'FailedToSchedule' | 'NotFound' | 'TargetBlockNumberInPast' | 'RescheduleNoChange';
 }
 
-/** @name PalletUnqSchedulerEvent */
-export interface PalletUnqSchedulerEvent extends Enum {
+/** @name PalletUniqueSchedulerEvent */
+export interface PalletUniqueSchedulerEvent extends Enum {
   readonly isScheduled: boolean;
   readonly asScheduled: {
     readonly when: u32;
@@ -1845,8 +1845,8 @@
   readonly type: 'Scheduled' | 'Canceled' | 'Dispatched' | 'CallLookupFailed';
 }
 
-/** @name PalletUnqSchedulerScheduledV3 */
-export interface PalletUnqSchedulerScheduledV3 extends Struct {
+/** @name PalletUniqueSchedulerScheduledV3 */
+export interface PalletUniqueSchedulerScheduledV3 extends Struct {
   readonly maybeId: Option<U8aFixed>;
   readonly priority: u8;
   readonly call: FrameSupportScheduleMaybeHashed;
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1526,9 +1526,9 @@
     users: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>'
   },
   /**
-   * Lookup206: pallet_unq_scheduler::pallet::Call<T>
+   * Lookup206: pallet_unique_scheduler::pallet::Call<T>
    **/
-  PalletUnqSchedulerCall: {
+  PalletUniqueSchedulerCall: {
     _enum: {
       schedule_named: {
         id: '[u8;16]',
@@ -2181,9 +2181,9 @@
     }
   },
   /**
-   * Lookup283: pallet_unq_scheduler::pallet::Event<T>
+   * Lookup283: pallet_unique_scheduler::pallet::Event<T>
    **/
-  PalletUnqSchedulerEvent: {
+  PalletUniqueSchedulerEvent: {
     _enum: {
       Scheduled: {
         when: 'u32',
@@ -2589,9 +2589,9 @@
     _enum: ['CollectionDecimalPointLimitExceeded', 'ConfirmUnsetSponsorFail', 'EmptyArgument']
   },
   /**
-   * Lookup344: pallet_unq_scheduler::ScheduledV3<frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>, BlockNumber, opal_runtime::OriginCaller, sp_core::crypto::AccountId32>
+   * Lookup344: pallet_unique_scheduler::ScheduledV3<frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>, BlockNumber, opal_runtime::OriginCaller, sp_core::crypto::AccountId32>
    **/
-  PalletUnqSchedulerScheduledV3: {
+  PalletUniqueSchedulerScheduledV3: {
     maybeId: 'Option<[u8;16]>',
     priority: 'u8',
     call: 'FrameSupportScheduleMaybeHashed',
@@ -2748,9 +2748,9 @@
    **/
   SpCoreVoid: 'Null',
   /**
-   * Lookup351: pallet_unq_scheduler::pallet::Error<T>
+   * Lookup351: pallet_unique_scheduler::pallet::Error<T>
    **/
-  PalletUnqSchedulerError: {
+  PalletUniqueSchedulerError: {
     _enum: ['FailedToSchedule', 'NotFound', 'TargetBlockNumberInPast', 'RescheduleNoChange']
   },
   /**
modifiedtests/src/interfaces/registry.tsdiffbeforeafterboth
--- a/tests/src/interfaces/registry.ts
+++ b/tests/src/interfaces/registry.ts
@@ -1,7 +1,7 @@
 // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
 /* eslint-disable */
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUnqSchedulerCall, PalletUnqSchedulerError, PalletUnqSchedulerEvent, PalletUnqSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
 
 declare module '@polkadot/types/types/registry' {
   export interface InterfaceTypes {
@@ -133,10 +133,10 @@
     PalletUniqueCall: PalletUniqueCall;
     PalletUniqueError: PalletUniqueError;
     PalletUniqueRawEvent: PalletUniqueRawEvent;
-    PalletUnqSchedulerCall: PalletUnqSchedulerCall;
-    PalletUnqSchedulerError: PalletUnqSchedulerError;
-    PalletUnqSchedulerEvent: PalletUnqSchedulerEvent;
-    PalletUnqSchedulerScheduledV3: PalletUnqSchedulerScheduledV3;
+    PalletUniqueSchedulerCall: PalletUniqueSchedulerCall;
+    PalletUniqueSchedulerError: PalletUniqueSchedulerError;
+    PalletUniqueSchedulerEvent: PalletUniqueSchedulerEvent;
+    PalletUniqueSchedulerScheduledV3: PalletUniqueSchedulerScheduledV3;
     PalletXcmCall: PalletXcmCall;
     PalletXcmError: PalletXcmError;
     PalletXcmEvent: PalletXcmEvent;
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1650,8 +1650,8 @@
     readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
   }
 
-  /** @name PalletUnqSchedulerCall (206) */
-  export interface PalletUnqSchedulerCall extends Enum {
+  /** @name PalletUniqueSchedulerCall (206) */
+  export interface PalletUniqueSchedulerCall extends Enum {
     readonly isScheduleNamed: boolean;
     readonly asScheduleNamed: {
       readonly id: U8aFixed;
@@ -2350,8 +2350,8 @@
     readonly type: 'CollectionSponsorRemoved' | 'CollectionAdminAdded' | 'CollectionOwnedChanged' | 'CollectionSponsorSet' | 'SponsorshipConfirmed' | 'CollectionAdminRemoved' | 'AllowListAddressRemoved' | 'AllowListAddressAdded' | 'CollectionLimitSet' | 'CollectionPermissionSet';
   }
 
-  /** @name PalletUnqSchedulerEvent (283) */
-  export interface PalletUnqSchedulerEvent extends Enum {
+  /** @name PalletUniqueSchedulerEvent (283) */
+  export interface PalletUniqueSchedulerEvent extends Enum {
     readonly isScheduled: boolean;
     readonly asScheduled: {
       readonly when: u32;
@@ -2805,8 +2805,8 @@
     readonly type: 'CollectionDecimalPointLimitExceeded' | 'ConfirmUnsetSponsorFail' | 'EmptyArgument';
   }
 
-  /** @name PalletUnqSchedulerScheduledV3 (344) */
-  export interface PalletUnqSchedulerScheduledV3 extends Struct {
+  /** @name PalletUniqueSchedulerScheduledV3 (344) */
+  export interface PalletUniqueSchedulerScheduledV3 extends Struct {
     readonly maybeId: Option<U8aFixed>;
     readonly priority: u8;
     readonly call: FrameSupportScheduleMaybeHashed;
@@ -2864,8 +2864,8 @@
   /** @name SpCoreVoid (350) */
   export type SpCoreVoid = Null;
 
-  /** @name PalletUnqSchedulerError (351) */
-  export interface PalletUnqSchedulerError extends Enum {
+  /** @name PalletUniqueSchedulerError (351) */
+  export interface PalletUniqueSchedulerError extends Enum {
     readonly isFailedToSchedule: boolean;
     readonly isNotFound: boolean;
     readonly isTargetBlockNumberInPast: boolean;
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -106,6 +106,58 @@
     });
   });
 
+  it('Check valid names for collection properties keys', async () => {
+    await usingApi(async api => {
+      const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+      const {collectionId} = getCreateCollectionResult(events);
+
+      // alpha symbols
+      await expect(executeTransaction(
+        api, 
+        bob, 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 
+      )).to.not.be.rejected;
+
+      // numeric symbols
+      await expect(executeTransaction(
+        api, 
+        bob, 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 
+      )).to.not.be.rejected;
+
+      // underscore symbol
+      await expect(executeTransaction(
+        api, 
+        bob, 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 
+      )).to.not.be.rejected;
+
+      // dash symbol
+      await expect(executeTransaction(
+        api, 
+        bob, 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 
+      )).to.not.be.rejected;
+
+      // underscore symbol
+      await expect(executeTransaction(
+        api, 
+        bob, 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 
+      )).to.not.be.rejected;
+
+      const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];
+      const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();
+      expect(properties).to.be.deep.equal([
+        {key: 'alpha', value: ''},
+        {key: '123', value: ''},
+        {key: 'black_hole', value: ''},
+        {key: 'semi-automatic', value: ''},
+        {key: 'build.rs', value: ''},
+      ]);
+    });
+  });
+
   it('Changes properties of a collection', async () => {
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess();
@@ -241,7 +293,7 @@
 
       const invalidProperties = [
         [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],
-        [{key: 'Mr.Sandman', value: 'Bring me a gene'}],
+        [{key: 'Mr/Sandman', value: 'Bring me a gene'}],
         [{key: 'déjà vu', value: 'hmm...'}],
       ];
 
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -118,11 +118,17 @@
       const bob = privateKeyWrapper('//Bob');
       const charlie = privateKeyWrapper('//Charlie');
 
+      const addBobAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
+      await submitTransactionAsync(alice, addBobAdminTx);
+      const addCharlieAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
+      await submitTransactionAsync(alice, addCharlieAdminTx);
+
       const adminListAfterAddAdmin = await getAdminList(api, collectionId);
       expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
 
       const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await expect(submitTransactionAsync(charlie, removeAdminTx)).to.be.rejected;
+      await expect(submitTransactionExpectFailAsync(charlie, removeAdminTx)).to.be.rejected;
 
       const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
       expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
modifiedtests/src/setCollectionLimits.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -46,6 +46,7 @@
   before(async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
       collectionIdForTesting = await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
     });
   });
@@ -115,6 +116,21 @@
     });
   });
 
+  it('execute setCollectionLimits from admin collection', async () => {
+    await addCollectionAdminExpectSuccess(alice, collectionIdForTesting, bob.address);
+    await usingApi(async (api: ApiPromise) => {
+      tx = api.tx.unique.setCollectionLimits(
+        collectionIdForTesting,
+        {
+          accountTokenOwnershipLimit,
+          sponsoredDataSize,
+          // sponsoredMintSize,
+          tokenLimit,
+        },
+      );
+      await expect(submitTransactionAsync(bob, tx)).to.be.not.rejected;
+    });
+  });
 });
 
 describe('setCollectionLimits negative', () => {
@@ -143,21 +159,6 @@
     });
   });
   it('execute setCollectionLimits from user who is not owner of this collection', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      tx = api.tx.unique.setCollectionLimits(
-        collectionIdForTesting,
-        {
-          accountTokenOwnershipLimit,
-          sponsoredDataSize,
-          // sponsoredMintSize,
-          tokenLimit,
-        },
-      );
-      await expect(submitTransactionExpectFailAsync(bob, tx)).to.be.rejected;
-    });
-  });
-  it('execute setCollectionLimits from admin collection', async () => {
-    await addCollectionAdminExpectSuccess(alice, collectionIdForTesting, bob.address);
     await usingApi(async (api: ApiPromise) => {
       tx = api.tx.unique.setCollectionLimits(
         collectionIdForTesting,
modifiedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -65,6 +65,11 @@
     await setCollectionSponsorExpectSuccess(collectionId, bob.address);
     await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
   });
+  it('Collection admin add sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address, '//Bob');
+  });
 });
 
 describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
@@ -93,10 +98,5 @@
     const collectionId = await createCollectionExpectSuccess();
     await destroyCollectionExpectSuccess(collectionId);
     await setCollectionSponsorExpectFailure(collectionId, bob.address);
-  });
-  it('(!negative test!) Collection admin add sponsor', async () => {
-    const collectionId = await createCollectionExpectSuccess();
-    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
-    await setCollectionSponsorExpectFailure(collectionId, charlie.address, '//Bob');
   });
 });
modifiedtests/src/setMintPermission.test.tsdiffbeforeafterboth
--- a/tests/src/setMintPermission.test.ts
+++ b/tests/src/setMintPermission.test.ts
@@ -67,6 +67,14 @@
       await setMintPermissionExpectSuccess(alice, collectionId, false);
     });
   });
+
+  it('Collection admin success on set', async () => {
+    await usingApi(async () => {
+      const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+      await setMintPermissionExpectSuccess(bob, collectionId, true);
+    });
+  });
 });
 
 describe('Negative Integration Test setMintPermission', () => {
@@ -100,14 +108,6 @@
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await enableAllowListExpectSuccess(alice, collectionId);
     await setMintPermissionExpectFailure(bob, collectionId, true);
-  });
-
-  it('Collection admin fails on set', async () => {
-    await usingApi(async () => {
-      const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-      await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
-      await setMintPermissionExpectFailure(bob, collectionId, true);
-    });
   });
 
   it('ensure non-allow-listed non-privileged address can\'t mint tokens', async () => {
modifiedtests/src/setPublicAccessMode.test.tsdiffbeforeafterboth
--- a/tests/src/setPublicAccessMode.test.ts
+++ b/tests/src/setPublicAccessMode.test.ts
@@ -103,22 +103,23 @@
       await expect(submitTransactionExpectFailAsync(bob, tx)).to.be.rejected;
     });
   });
-});
 
-describe('Negative Integration Test ext. collection admin setPublicAccessMode(): ', () => {
-  before(async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      alice = privateKeyWrapper('//Alice');
-      bob = privateKeyWrapper('//Bob');
-    });
-  });
   it('setPublicAccessMode by collection admin', async () => {
     await usingApi(async (api: ApiPromise) => {
       // tslint:disable-next-line: no-bitwise
       const collectionId = await createCollectionExpectSuccess();
       await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
       const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: 'AllowList'});
-      await expect(submitTransactionExpectFailAsync(bob, tx)).to.be.rejected;
+      await expect(submitTransactionExpectFailAsync(bob, tx)).to.be.not.rejected;
+    });
+  });
+});
+
+describe('Negative Integration Test ext. collection admin setPublicAccessMode(): ', () => {
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
     });
   });
 });