git.delta.rocks / unique-network / refs/commits / 1a56db748b0a

difftreelog

doc: architectural changes

Farhad Hakimov2022-07-11parent: #12c8c8f.patch.diff
in: master

10 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -42,6 +42,7 @@
 #[rpc(server)]
 #[async_trait]
 pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {
+	/// Get tokens owned by account
 	#[method(name = "unique_accountTokens")]
 	fn account_tokens(
 		&self,
@@ -49,12 +50,14 @@
 		account: CrossAccountId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenId>>;
+	/// Get tokens contained in collection
 	#[method(name = "unique_collectionTokens")]
 	fn collection_tokens(
 		&self,
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenId>>;
+	/// Check if token exists
 	#[method(name = "unique_tokenExists")]
 	fn token_exists(
 		&self,
@@ -62,7 +65,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<bool>;
-
+	/// Get token owner
 	#[method(name = "unique_tokenOwner")]
 	fn token_owner(
 		&self,
@@ -70,6 +73,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CrossAccountId>>;
+	/// Get token owner, in case of nested token - find the parent recursively
 	#[method(name = "unique_topmostTokenOwner")]
 	fn topmost_token_owner(
 		&self,
@@ -77,6 +81,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CrossAccountId>>;
+	/// Get tokens nested directly into the token
 	#[method(name = "unique_tokenChildren")]
 	fn token_children(
 		&self,
@@ -84,7 +89,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenChild>>;
-
+	/// Get collection properties
 	#[method(name = "unique_collectionProperties")]
 	fn collection_properties(
 		&self,
@@ -92,7 +97,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<Property>>;
-
+	/// Get token properties
 	#[method(name = "unique_tokenProperties")]
 	fn token_properties(
 		&self,
@@ -101,7 +106,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<Property>>;
-
+	/// Get property permissions
 	#[method(name = "unique_propertyPermissions")]
 	fn property_permissions(
 		&self,
@@ -109,7 +114,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<PropertyKeyPermission>>;
-
+	/// Get token data
 	#[method(name = "unique_tokenData")]
 	fn token_data(
 		&self,
@@ -118,9 +123,10 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<TokenData<CrossAccountId>>;
-
+	/// Get amount of unique collection tokens
 	#[method(name = "unique_totalSupply")]
 	fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;
+	/// Get owned amount of any user tokens
 	#[method(name = "unique_accountBalance")]
 	fn account_balance(
 		&self,
@@ -128,6 +134,7 @@
 		account: CrossAccountId,
 		at: Option<BlockHash>,
 	) -> Result<u32>;
+	/// Get owned amount of specific account token
 	#[method(name = "unique_balance")]
 	fn balance(
 		&self,
@@ -136,6 +143,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<String>;
+	/// Get allowed amount
 	#[method(name = "unique_allowance")]
 	fn allowance(
 		&self,
@@ -145,19 +153,21 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<String>;
-
+	/// Get admin list
 	#[method(name = "unique_adminlist")]
 	fn adminlist(
 		&self,
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<CrossAccountId>>;
+	/// Get allowlist
 	#[method(name = "unique_allowlist")]
 	fn allowlist(
 		&self,
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<CrossAccountId>>;
+	/// Check if user is allowed to use collection
 	#[method(name = "unique_allowed")]
 	fn allowed(
 		&self,
@@ -165,17 +175,20 @@
 		user: CrossAccountId,
 		at: Option<BlockHash>,
 	) -> Result<bool>;
+	/// Get last token ID created in a collection
 	#[method(name = "unique_lastTokenId")]
 	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;
+	/// Get collection by specified ID
 	#[method(name = "unique_collectionById")]
 	fn collection_by_id(
 		&self,
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Option<RpcCollection<AccountId>>>;
+	/// Get collection stats
 	#[method(name = "unique_collectionStats")]
 	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
-
+	/// Get number of blocks when sponsored transaction is available
 	#[method(name = "unique_nextSponsored")]
 	fn next_sponsored(
 		&self,
@@ -184,14 +197,14 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<u64>>;
-
+	/// Get effective collection limits
 	#[method(name = "unique_effectiveCollectionLimits")]
 	fn effective_collection_limits(
 		&self,
 		collection_id: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CollectionLimits>>;
-
+	/// Get total pieces of token
 	#[method(name = "unique_totalPieces")]
 	fn total_pieces(
 		&self,
@@ -304,6 +317,7 @@
 		fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;
 
 		#[method(name = "rmrk_themeNames")]
+		/// Get Base's theme names
 		fn theme_names(
 			&self,
 			base_id: RmrkBaseId,
@@ -311,6 +325,7 @@
 		) -> Result<Vec<RmrkThemeName>>;
 
 		#[method(name = "rmrk_themes")]
+		/// Get Theme info -- name, properties, and inherit flag
 		fn theme(
 			&self,
 			base_id: RmrkBaseId,
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -299,46 +299,48 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: Globally unique identifier of collection.
+		/// * collection_id: Globally unique identifier of collection that has been destroyed.
 		CollectionDestroyed(CollectionId),
 
 		/// New item was created.
 		///
 		/// # Arguments
 		///
-		/// * collection_id: Id of the collection where item was created.
+		/// * collection_id: ID of the collection where the item was created.
 		///
-		/// * item_id: Id of an item. Unique within the collection.
+		/// * item_id: ID of the item. Unique within the collection.
 		///
-		/// * recipient: Owner of newly created item
+		/// * recipient: Owner of the newly created item.
 		///
-		/// * amount: Always 1 for NFT
+		/// * amount: The amount of tokens that were created (always 1 for NFT).
 		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),
 
 		/// Collection item was burned.
 		///
 		/// # Arguments
 		///
-		/// * collection_id.
+		/// * collection_id: Identifier of the collection to which the burned NFT belonged.
 		///
 		/// * item_id: Identifier of burned NFT.
 		///
-		/// * owner: which user has destroyed its tokens
+		/// * owner: Which user has destroyed their tokens.
 		///
-		/// * amount: Always 1 for NFT
+		/// * amount: The amount of tokens that were destroyed (always 1 for NFT).
 		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),
 
-		/// Item was transferred
+		/// Item was transferred.
+		/// 
+		/// # Arguments
 		///
-		/// * collection_id: Id of collection to which item is belong
+		/// * collection_id: ID of the collection to which the item belongs.
 		///
-		/// * item_id: Id of an item
+		/// * item_id: ID of the item trasnferred.
 		///
-		/// * sender: Original owner of item
+		/// * sender: Original owner of the item.
 		///
-		/// * recipient: New owner of item
+		/// * recipient: New owner of the item.
 		///
-		/// * amount: Always 1 for NFT
+		/// * amount: The amount of tokens that were transferred (always 1 for NFT).
 		Transfer(
 			CollectionId,
 			TokenId,
@@ -347,6 +349,10 @@
 			u128,
 		),
 
+		/// Sponsoring allowance was approved.
+		/// 
+		/// # Arguments
+		/// 
 		/// * collection_id
 		///
 		/// * item_id
@@ -364,14 +370,53 @@
 			u128,
 		),
 
+		/// Collection property was added or edited.
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: ID of the collection, whose property was just set.
+		/// 
+		/// * property_key: Key of the property that was just set.
 		CollectionPropertySet(CollectionId, PropertyKey),
 
+		/// Collection property was deleted.
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: ID of the collection, whose property was just deleted.
+		/// 
+		/// * property_key: Key of the property that was just deleted.
 		CollectionPropertyDeleted(CollectionId, PropertyKey),
 
+		/// Item property was added or edited.
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: ID of the collection, whose token's property was just set.
+		/// 
+		/// * item_id: ID of the item, whose property was just set.
+		/// 
+		/// * property_key: Key of the property that was just set.
 		TokenPropertySet(CollectionId, TokenId, PropertyKey),
 
+		/// Item property was deleted.
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: ID of the collection, whose token's property was just deleted.
+		/// 
+		/// * item_id: ID of the item, whose property was just deleted.
+		/// 
+		/// * property_key: Key of the property that was just deleted.
 		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),
 
+		/// Token property permission was added or updated for a collection.
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: ID of the collection, whose permissions were just set/updated.
+		/// 
+		/// * property_key: Key of the property of the set/updated permission.
 		PropertyPermissionSet(CollectionId, PropertyKey),
 	}
 
@@ -413,26 +458,26 @@
 		/// Metadata flag frozen
 		MetadataFlagFrozen,
 
-		/// Item not exists.
+		/// Item does not exist
 		TokenNotFound,
-		/// Item balance not enough.
+		/// Item is balance not enough
 		TokenValueTooLow,
-		/// Requested value more than approved.
+		/// Requested value is more than the approved
 		ApprovedValueTooLow,
 		/// Tried to approve more than owned
 		CantApproveMoreThanOwned,
 
 		/// Can't transfer tokens to ethereum zero address
 		AddressIsZero,
-		/// Target collection doesn't supports this operation
+		/// Target collection doesn't support this operation
 		UnsupportedOperation,
 
-		/// Not sufficient funds to perform action
+		/// Insufficient funds to perform an action
 		NotSufficientFounds,
 
-		/// User not passed nesting rule
+		/// User does not satisfy the nesting rule
 		UserIsNotAllowedToNest,
-		/// Only tokens from specific collections may nest tokens under this
+		/// Only tokens from specific collections may nest tokens under this one
 		SourceCollectionIsNotAllowedToNest,
 
 		/// Tried to store more data than allowed in collection field
@@ -447,7 +492,7 @@
 		/// Property key is too long
 		PropertyKeyIsTooLong,
 
-		/// Only ASCII letters, digits, and '_', '-' are allowed
+		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed
 		InvalidCharacterInPropertyKey,
 
 		/// Empty property keys are forbidden
@@ -460,8 +505,11 @@
 		CollectionIsInternal,
 	}
 
+	/// The number of created collections. Essentially contains the last collection ID.
 	#[pallet::storage]
 	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
+
+	/// The number of destroyed collections
 	#[pallet::storage]
 	pub type DestroyedCollectionCount<T> =
 		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
@@ -486,6 +534,7 @@
 		OnEmpty = up_data_structs::CollectionProperties,
 	>;
 
+	/// Token permissions of a collection
 	#[pallet::storage]
 	#[pallet::getter(fn property_permissions)]
 	pub type CollectionPropertyPermissions<T> = StorageMap<
@@ -495,6 +544,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Amount of collection admins
 	#[pallet::storage]
 	pub type AdminAmount<T> = StorageMap<
 		Hasher = Blake2_128Concat,
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -44,6 +44,7 @@
 pub mod erc;
 pub mod weights;
 
+/// todo:doc?
 pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
@@ -78,10 +79,12 @@
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
 
+	/// Total amount of fungible tokens inside a collection.
 	#[pallet::storage]
 	pub type TotalSupply<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;
 
+	/// Amount of tokens owned by an account inside a collection.
 	#[pallet::storage]
 	pub type Balance<T: Config> = StorageNMap<
 		Key = (
@@ -92,6 +95,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// todo:doc
 	#[pallet::storage]
 	pub type Allowance<T: Config> = StorageNMap<
 		Key = (
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/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 erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31	PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32	AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37	eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859#[struct_versioning::versioned(version = 2, upper)]60#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]61pub struct ItemData<CrossAccountId> {62	#[version(..2)]63	pub const_data: BoundedVec<u8, CustomDataLimit>,6465	#[version(..2)]66	pub variable_data: BoundedVec<u8, CustomDataLimit>,6768	pub owner: CrossAccountId,69}7071#[frame_support::pallet]72pub mod pallet {73	use super::*;74	use frame_support::{75		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,76	};77	use frame_system::pallet_prelude::*;78	use up_data_structs::{CollectionId, TokenId};79	use super::weights::WeightInfo;8081	#[pallet::error]82	pub enum Error<T> {83		/// Not Nonfungible item data used to mint in Nonfungible collection.84		NotNonfungibleDataUsedToMintFungibleCollectionToken,85		/// Used amount > 1 with NFT86		NonfungibleItemsHaveNoAmount,87		/// Unable to burn NFT with children88		CantBurnNftWithChildren,89	}9091	#[pallet::config]92	pub trait Config:93		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config94	{95		type WeightInfo: WeightInfo;96	}9798	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);99100	#[pallet::pallet]101	#[pallet::storage_version(STORAGE_VERSION)]102	#[pallet::generate_store(pub(super) trait Store)]103	pub struct Pallet<T>(_);104105	#[pallet::storage]106	pub type TokensMinted<T: Config> =107		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;108	#[pallet::storage]109	pub type TokensBurnt<T: Config> =110		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112	#[pallet::storage]113	pub type TokenData<T: Config> = StorageNMap<114		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),115		Value = ItemData<T::CrossAccountId>,116		QueryKind = OptionQuery,117	>;118119	#[pallet::storage]120	#[pallet::getter(fn token_properties)]121	pub type TokenProperties<T: Config> = StorageNMap<122		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),123		Value = Properties,124		QueryKind = ValueQuery,125		OnEmpty = up_data_structs::TokenProperties,126	>;127128	#[pallet::storage]129	#[pallet::getter(fn token_aux_property)]130	pub type TokenAuxProperties<T: Config> = StorageNMap<131		Key = (132			Key<Twox64Concat, CollectionId>,133			Key<Twox64Concat, TokenId>,134			Key<Twox64Concat, PropertyScope>,135			Key<Twox64Concat, PropertyKey>,136		),137		Value = AuxPropertyValue,138		QueryKind = OptionQuery,139	>;140141	/// Used to enumerate tokens owned by account142	#[pallet::storage]143	pub type Owned<T: Config> = StorageNMap<144		Key = (145			Key<Twox64Concat, CollectionId>,146			Key<Blake2_128Concat, T::CrossAccountId>,147			Key<Twox64Concat, TokenId>,148		),149		Value = bool,150		QueryKind = ValueQuery,151	>;152153	/// Used to enumerate token's children154	#[pallet::storage]155	#[pallet::getter(fn token_children)]156	pub type TokenChildren<T: Config> = StorageNMap<157		Key = (158			Key<Twox64Concat, CollectionId>,159			Key<Twox64Concat, TokenId>,160			Key<Twox64Concat, (CollectionId, TokenId)>,161		),162		Value = bool,163		QueryKind = ValueQuery,164	>;165166	#[pallet::storage]167	pub type AccountBalance<T: Config> = StorageNMap<168		Key = (169			Key<Twox64Concat, CollectionId>,170			Key<Blake2_128Concat, T::CrossAccountId>,171		),172		Value = u32,173		QueryKind = ValueQuery,174	>;175176	#[pallet::storage]177	pub type Allowance<T: Config> = StorageNMap<178		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),179		Value = T::CrossAccountId,180		QueryKind = OptionQuery,181	>;182183	#[pallet::hooks]184	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {185		fn on_runtime_upgrade() -> Weight {186			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {187				let mut had_consts = BTreeSet::new();188				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(189					|(collection, token), v| {190						let mut props = vec![];191						if !v.const_data.is_empty() {192							props.push(Property {193								key: b"_old_constData".to_vec().try_into().unwrap(),194								value: v195									.const_data196									.clone()197									.into_inner()198									.try_into()199									.expect("const too long"),200							});201							had_consts.insert(collection);202						}203						if !v.variable_data.is_empty() {204							props.push(Property {205								key: b"_old_variableData".to_vec().try_into().unwrap(),206								value: v207									.variable_data208									.clone()209									.into_inner()210									.try_into()211									.expect("variable too long"),212							})213						}214						if !props.is_empty() {215							Self::set_scoped_token_properties(216								collection,217								token,218								PropertyScope::None,219								props.into_iter(),220							)221							.expect("existing token data exceeds property storage");222						}223						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))224					},225				);226				for collection in had_consts {227					<PalletCommon<T>>::set_property_permission_unchecked(228						collection,229						PropertyKeyPermission {230							key: b"_old_constData".to_vec().try_into().unwrap(),231							permission: PropertyPermission {232								mutable: false,233								collection_admin: true,234								token_owner: false,235							},236						},237					)238					.expect("failed to configure permission");239				}240			}241242			0243		}244	}245}246247pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);248impl<T: Config> NonfungibleHandle<T> {249	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {250		Self(inner)251	}252	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {253		self.0254	}255	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {256		&mut self.0257	}258}259impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {260	fn recorder(&self) -> &SubstrateRecorder<T> {261		self.0.recorder()262	}263	fn into_recorder(self) -> SubstrateRecorder<T> {264		self.0.into_recorder()265	}266}267impl<T: Config> Deref for NonfungibleHandle<T> {268	type Target = pallet_common::CollectionHandle<T>;269270	fn deref(&self) -> &Self::Target {271		&self.0272	}273}274275impl<T: Config> Pallet<T> {276	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {277		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)278	}279	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {280		<TokenData<T>>::contains_key((collection.id, token))281	}282283	pub fn set_scoped_token_property(284		collection_id: CollectionId,285		token_id: TokenId,286		scope: PropertyScope,287		property: Property,288	) -> DispatchResult {289		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {290			properties.try_scoped_set(scope, property.key, property.value)291		})292		.map_err(<CommonError<T>>::from)?;293294		Ok(())295	}296297	pub fn set_scoped_token_properties(298		collection_id: CollectionId,299		token_id: TokenId,300		scope: PropertyScope,301		properties: impl Iterator<Item = Property>,302	) -> DispatchResult {303		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {304			stored_properties.try_scoped_set_from_iter(scope, properties)305		})306		.map_err(<CommonError<T>>::from)?;307308		Ok(())309	}310311	pub fn try_mutate_token_aux_property<R, E>(312		collection_id: CollectionId,313		token_id: TokenId,314		scope: PropertyScope,315		key: PropertyKey,316		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,317	) -> Result<R, E> {318		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)319	}320321	pub fn remove_token_aux_property(322		collection_id: CollectionId,323		token_id: TokenId,324		scope: PropertyScope,325		key: PropertyKey,326	) {327		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));328	}329330	pub fn iterate_token_aux_properties(331		collection_id: CollectionId,332		token_id: TokenId,333		scope: PropertyScope,334	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {335		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))336	}337338	pub fn current_token_id(collection_id: CollectionId) -> TokenId {339		TokenId(<TokensMinted<T>>::get(collection_id))340	}341}342343// unchecked calls skips any permission checks344impl<T: Config> Pallet<T> {345	pub fn init_collection(346		owner: T::CrossAccountId,347		data: CreateCollectionData<T::AccountId>,348		is_external: bool,349	) -> Result<CollectionId, DispatchError> {350		<PalletCommon<T>>::init_collection(owner, data, is_external)351	}352	pub fn destroy_collection(353		collection: NonfungibleHandle<T>,354		sender: &T::CrossAccountId,355	) -> DispatchResult {356		let id = collection.id;357358		if Self::collection_has_tokens(id) {359			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());360		}361362		// =========363364		PalletCommon::destroy_collection(collection.0, sender)?;365366		<TokenData<T>>::remove_prefix((id,), None);367		<TokenChildren<T>>::remove_prefix((id,), None);368		<Owned<T>>::remove_prefix((id,), None);369		<TokensMinted<T>>::remove(id);370		<TokensBurnt<T>>::remove(id);371		<Allowance<T>>::remove_prefix((id,), None);372		<AccountBalance<T>>::remove_prefix((id,), None);373		Ok(())374	}375376	pub fn burn(377		collection: &NonfungibleHandle<T>,378		sender: &T::CrossAccountId,379		token: TokenId,380	) -> DispatchResult {381		let token_data =382			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;383		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);384385		if collection.permissions.access() == AccessMode::AllowList {386			collection.check_allowlist(sender)?;387		}388389		if Self::token_has_children(collection.id, token) {390			return Err(<Error<T>>::CantBurnNftWithChildren.into());391		}392393		let burnt = <TokensBurnt<T>>::get(collection.id)394			.checked_add(1)395			.ok_or(ArithmeticError::Overflow)?;396397		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))398			.checked_sub(1)399			.ok_or(ArithmeticError::Overflow)?;400401		// =========402403		if balance == 0 {404			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));405		} else {406			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);407		}408409		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);410411		<Owned<T>>::remove((collection.id, &token_data.owner, token));412		<TokensBurnt<T>>::insert(collection.id, burnt);413		<TokenData<T>>::remove((collection.id, token));414		<TokenProperties<T>>::remove((collection.id, token));415		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);416		let old_spender = <Allowance<T>>::take((collection.id, token));417418		if let Some(old_spender) = old_spender {419			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(420				collection.id,421				token,422				token_data.owner.clone(),423				old_spender,424				0,425			));426		}427428		<PalletEvm<T>>::deposit_log(429			ERC721Events::Transfer {430				from: *token_data.owner.as_eth(),431				to: H160::default(),432				token_id: token.into(),433			}434			.to_log(collection_id_to_address(collection.id)),435		);436		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(437			collection.id,438			token,439			token_data.owner,440			1,441		));442		Ok(())443	}444445	#[transactional]446	pub fn burn_recursively(447		collection: &NonfungibleHandle<T>,448		sender: &T::CrossAccountId,449		token: TokenId,450		self_budget: &dyn Budget,451		breadth_budget: &dyn Budget,452	) -> DispatchResultWithPostInfo {453		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);454455		let current_token_account =456			T::CrossTokenAddressMapping::token_to_address(collection.id, token);457458		let mut weight = 0 as Weight;459460		// This method is transactional, if user in fact doesn't have permissions to remove token -461		// tokens removed here will be restored after rejected transaction462		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {463			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);464			let PostDispatchInfo { actual_weight, .. } =465				<PalletStructure<T>>::burn_item_recursively(466					current_token_account.clone(),467					collection,468					token,469					self_budget,470					breadth_budget,471				)?;472			if let Some(actual_weight) = actual_weight {473				weight = weight.saturating_add(actual_weight);474			}475		}476477		Self::burn(collection, sender, token)?;478		DispatchResultWithPostInfo::Ok(PostDispatchInfo {479			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),480			pays_fee: Pays::Yes,481		})482	}483484	#[transactional]485	fn modify_token_properties(486		collection: &NonfungibleHandle<T>,487		sender: &T::CrossAccountId,488		token_id: TokenId,489		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,490		is_token_create: bool,491		nesting_budget: &dyn Budget,492	) -> DispatchResult {493		let mut collection_admin_status = None;494		let mut token_owner_result = None;495496		let mut is_collection_admin =497			|| *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));498499		let mut is_token_owner = || {500			*token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {501				let is_owned = <PalletStructure<T>>::check_indirectly_owned(502					sender.clone(),503					collection.id,504					token_id,505					None,506					nesting_budget,507				)?;508509				Ok(is_owned)510			})511		};512513		for (key, value) in properties {514			let permission = <PalletCommon<T>>::property_permissions(collection.id)515				.get(&key)516				.cloned()517				.unwrap_or_else(PropertyPermission::none);518519			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))520				.get(&key)521				.is_some();522523			match permission {524				PropertyPermission { mutable: false, .. } if is_property_exists => {525					return Err(<CommonError<T>>::NoPermission.into());526				}527528				PropertyPermission {529					collection_admin,530					token_owner,531					..532				} => {533					//TODO: investigate threats during public minting.534					if is_token_create && (collection_admin || token_owner) && value.is_some() {535						// Pass536					} else if collection_admin && is_collection_admin() {537						// Pass538					} else if token_owner && is_token_owner()? {539						// Pass540					} else {541						fail!(<CommonError<T>>::NoPermission);542					}543				}544			}545546			match value {547				Some(value) => {548					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {549						properties.try_set(key.clone(), value)550					})551					.map_err(<CommonError<T>>::from)?;552553					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(554						collection.id,555						token_id,556						key,557					));558				}559				None => {560					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {561						properties.remove(&key)562					})563					.map_err(<CommonError<T>>::from)?;564565					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(566						collection.id,567						token_id,568						key,569					));570				}571			}572		}573574		Ok(())575	}576577	pub fn set_token_properties(578		collection: &NonfungibleHandle<T>,579		sender: &T::CrossAccountId,580		token_id: TokenId,581		properties: impl Iterator<Item = Property>,582		is_token_create: bool,583		nesting_budget: &dyn Budget,584	) -> DispatchResult {585		Self::modify_token_properties(586			collection,587			sender,588			token_id,589			properties.map(|p| (p.key, Some(p.value))),590			is_token_create,591			nesting_budget,592		)593	}594595	pub fn set_token_property(596		collection: &NonfungibleHandle<T>,597		sender: &T::CrossAccountId,598		token_id: TokenId,599		property: Property,600		nesting_budget: &dyn Budget,601	) -> DispatchResult {602		let is_token_create = false;603604		Self::set_token_properties(605			collection,606			sender,607			token_id,608			[property].into_iter(),609			is_token_create,610			nesting_budget,611		)612	}613614	pub fn delete_token_properties(615		collection: &NonfungibleHandle<T>,616		sender: &T::CrossAccountId,617		token_id: TokenId,618		property_keys: impl Iterator<Item = PropertyKey>,619		nesting_budget: &dyn Budget,620	) -> DispatchResult {621		let is_token_create = false;622623		Self::modify_token_properties(624			collection,625			sender,626			token_id,627			property_keys.into_iter().map(|key| (key, None)),628			is_token_create,629			nesting_budget,630		)631	}632633	pub fn delete_token_property(634		collection: &NonfungibleHandle<T>,635		sender: &T::CrossAccountId,636		token_id: TokenId,637		property_key: PropertyKey,638		nesting_budget: &dyn Budget,639	) -> DispatchResult {640		Self::delete_token_properties(641			collection,642			sender,643			token_id,644			[property_key].into_iter(),645			nesting_budget,646		)647	}648649	pub fn set_collection_properties(650		collection: &NonfungibleHandle<T>,651		sender: &T::CrossAccountId,652		properties: Vec<Property>,653	) -> DispatchResult {654		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)655	}656657	pub fn delete_collection_properties(658		collection: &CollectionHandle<T>,659		sender: &T::CrossAccountId,660		property_keys: Vec<PropertyKey>,661	) -> DispatchResult {662		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)663	}664665	pub fn set_token_property_permissions(666		collection: &CollectionHandle<T>,667		sender: &T::CrossAccountId,668		property_permissions: Vec<PropertyKeyPermission>,669	) -> DispatchResult {670		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)671	}672673	pub fn set_property_permission(674		collection: &CollectionHandle<T>,675		sender: &T::CrossAccountId,676		permission: PropertyKeyPermission,677	) -> DispatchResult {678		<PalletCommon<T>>::set_property_permission(collection, sender, permission)679	}680681	pub fn transfer(682		collection: &NonfungibleHandle<T>,683		from: &T::CrossAccountId,684		to: &T::CrossAccountId,685		token: TokenId,686		nesting_budget: &dyn Budget,687	) -> DispatchResult {688		ensure!(689			collection.limits.transfers_enabled(),690			<CommonError<T>>::TransferNotAllowed691		);692693		let token_data =694			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;695		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);696697		if collection.permissions.access() == AccessMode::AllowList {698			collection.check_allowlist(from)?;699			collection.check_allowlist(to)?;700		}701		<PalletCommon<T>>::ensure_correct_receiver(to)?;702703		let balance_from = <AccountBalance<T>>::get((collection.id, from))704			.checked_sub(1)705			.ok_or(<CommonError<T>>::TokenValueTooLow)?;706		let balance_to = if from != to {707			let balance_to = <AccountBalance<T>>::get((collection.id, to))708				.checked_add(1)709				.ok_or(ArithmeticError::Overflow)?;710711			ensure!(712				balance_to < collection.limits.account_token_ownership_limit(),713				<CommonError<T>>::AccountTokenLimitExceeded,714			);715716			Some(balance_to)717		} else {718			None719		};720721		<PalletStructure<T>>::nest_if_sent_to_token(722			from.clone(),723			to,724			collection.id,725			token,726			nesting_budget,727		)?;728729		// =========730731		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);732733		<TokenData<T>>::insert(734			(collection.id, token),735			ItemData {736				owner: to.clone(),737				..token_data738			},739		);740741		if let Some(balance_to) = balance_to {742			// from != to743			if balance_from == 0 {744				<AccountBalance<T>>::remove((collection.id, from));745			} else {746				<AccountBalance<T>>::insert((collection.id, from), balance_from);747			}748			<AccountBalance<T>>::insert((collection.id, to), balance_to);749			<Owned<T>>::remove((collection.id, from, token));750			<Owned<T>>::insert((collection.id, to, token), true);751		}752		Self::set_allowance_unchecked(collection, from, token, None, true);753754		<PalletEvm<T>>::deposit_log(755			ERC721Events::Transfer {756				from: *from.as_eth(),757				to: *to.as_eth(),758				token_id: token.into(),759			}760			.to_log(collection_id_to_address(collection.id)),761		);762		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(763			collection.id,764			token,765			from.clone(),766			to.clone(),767			1,768		));769		Ok(())770	}771772	pub fn create_multiple_items(773		collection: &NonfungibleHandle<T>,774		sender: &T::CrossAccountId,775		data: Vec<CreateItemData<T>>,776		nesting_budget: &dyn Budget,777	) -> DispatchResult {778		if !collection.is_owner_or_admin(sender) {779			ensure!(780				collection.permissions.mint_mode(),781				<CommonError<T>>::PublicMintingNotAllowed782			);783			collection.check_allowlist(sender)?;784785			for item in data.iter() {786				collection.check_allowlist(&item.owner)?;787			}788		}789790		for data in data.iter() {791			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;792		}793794		let first_token = <TokensMinted<T>>::get(collection.id);795		let tokens_minted = first_token796			.checked_add(data.len() as u32)797			.ok_or(ArithmeticError::Overflow)?;798		ensure!(799			tokens_minted <= collection.limits.token_limit(),800			<CommonError<T>>::CollectionTokenLimitExceeded801		);802803		let mut balances = BTreeMap::new();804		for data in &data {805			let balance = balances806				.entry(&data.owner)807				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));808			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;809810			ensure!(811				*balance <= collection.limits.account_token_ownership_limit(),812				<CommonError<T>>::AccountTokenLimitExceeded,813			);814		}815816		for (i, data) in data.iter().enumerate() {817			let token = TokenId(first_token + i as u32 + 1);818819			<PalletStructure<T>>::check_nesting(820				sender.clone(),821				&data.owner,822				collection.id,823				token,824				nesting_budget,825			)?;826		}827828		// =========829830		with_transaction(|| {831			for (i, data) in data.iter().enumerate() {832				let token = first_token + i as u32 + 1;833834				<TokenData<T>>::insert(835					(collection.id, token),836					ItemData {837						// const_data: data.const_data.clone(),838						owner: data.owner.clone(),839					},840				);841842				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(843					&data.owner,844					collection.id,845					TokenId(token),846				);847848				if let Err(e) = Self::set_token_properties(849					collection,850					sender,851					TokenId(token),852					data.properties.clone().into_iter(),853					true,854					nesting_budget,855				) {856					return TransactionOutcome::Rollback(Err(e));857				}858			}859			TransactionOutcome::Commit(Ok(()))860		})?;861862		<TokensMinted<T>>::insert(collection.id, tokens_minted);863		for (account, balance) in balances {864			<AccountBalance<T>>::insert((collection.id, account), balance);865		}866		for (i, data) in data.into_iter().enumerate() {867			let token = first_token + i as u32 + 1;868			<Owned<T>>::insert((collection.id, &data.owner, token), true);869870			<PalletEvm<T>>::deposit_log(871				ERC721Events::Transfer {872					from: H160::default(),873					to: *data.owner.as_eth(),874					token_id: token.into(),875				}876				.to_log(collection_id_to_address(collection.id)),877			);878			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(879				collection.id,880				TokenId(token),881				data.owner.clone(),882				1,883			));884		}885		Ok(())886	}887888	pub fn set_allowance_unchecked(889		collection: &NonfungibleHandle<T>,890		sender: &T::CrossAccountId,891		token: TokenId,892		spender: Option<&T::CrossAccountId>,893		assume_implicit_eth: bool,894	) {895		if let Some(spender) = spender {896			let old_spender = <Allowance<T>>::get((collection.id, token));897			<Allowance<T>>::insert((collection.id, token), spender);898			// In ERC721 there is only one possible approved user of token, so we set899			// approved user to spender900			<PalletEvm<T>>::deposit_log(901				ERC721Events::Approval {902					owner: *sender.as_eth(),903					approved: *spender.as_eth(),904					token_id: token.into(),905				}906				.to_log(collection_id_to_address(collection.id)),907			);908			// In Unique chain, any token can have any amount of approved users, so we need to909			// set allowance of old owner to 0, and allowance of new owner to 1910			if old_spender.as_ref() != Some(spender) {911				if let Some(old_owner) = old_spender {912					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(913						collection.id,914						token,915						sender.clone(),916						old_owner,917						0,918					));919				}920				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(921					collection.id,922					token,923					sender.clone(),924					spender.clone(),925					1,926				));927			}928		} else {929			let old_spender = <Allowance<T>>::take((collection.id, token));930			if !assume_implicit_eth {931				// In ERC721 there is only one possible approved user of token, so we set932				// approved user to zero address933				<PalletEvm<T>>::deposit_log(934					ERC721Events::Approval {935						owner: *sender.as_eth(),936						approved: H160::default(),937						token_id: token.into(),938					}939					.to_log(collection_id_to_address(collection.id)),940				);941			}942			// In Unique chain, any token can have any amount of approved users, so we need to943			// set allowance of old owner to 0944			if let Some(old_spender) = old_spender {945				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(946					collection.id,947					token,948					sender.clone(),949					old_spender,950					0,951				));952			}953		}954	}955956	pub fn set_allowance(957		collection: &NonfungibleHandle<T>,958		sender: &T::CrossAccountId,959		token: TokenId,960		spender: Option<&T::CrossAccountId>,961	) -> DispatchResult {962		if collection.permissions.access() == AccessMode::AllowList {963			collection.check_allowlist(sender)?;964			if let Some(spender) = spender {965				collection.check_allowlist(spender)?;966			}967		}968969		if let Some(spender) = spender {970			<PalletCommon<T>>::ensure_correct_receiver(spender)?;971		}972973		let token_data =974			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;975		if &token_data.owner != sender {976			ensure!(977				collection.ignores_owned_amount(sender),978				<CommonError<T>>::CantApproveMoreThanOwned979			);980		}981982		// =========983984		Self::set_allowance_unchecked(collection, sender, token, spender, false);985		Ok(())986	}987988	fn check_allowed(989		collection: &NonfungibleHandle<T>,990		spender: &T::CrossAccountId,991		from: &T::CrossAccountId,992		token: TokenId,993		nesting_budget: &dyn Budget,994	) -> DispatchResult {995		if spender.conv_eq(from) {996			return Ok(());997		}998		if collection.permissions.access() == AccessMode::AllowList {999			// `from`, `to` checked in [`transfer`]1000			collection.check_allowlist(spender)?;1001		}10021003		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {1004			return Ok(());1005		}10061007		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1008			ensure!(1009				<PalletStructure<T>>::check_indirectly_owned(1010					spender.clone(),1011					source.0,1012					source.1,1013					None,1014					nesting_budget1015				)?,1016				<CommonError<T>>::ApprovedValueTooLow,1017			);1018			return Ok(());1019		}1020		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1021			return Ok(());1022		}1023		ensure!(1024			collection.ignores_allowance(spender),1025			<CommonError<T>>::ApprovedValueTooLow1026		);1027		Ok(())1028	}10291030	pub fn transfer_from(1031		collection: &NonfungibleHandle<T>,1032		spender: &T::CrossAccountId,1033		from: &T::CrossAccountId,1034		to: &T::CrossAccountId,1035		token: TokenId,1036		nesting_budget: &dyn Budget,1037	) -> DispatchResult {1038		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10391040		// =========10411042		// Allowance is reset in [`transfer`]1043		Self::transfer(collection, from, to, token, nesting_budget)1044	}10451046	pub fn burn_from(1047		collection: &NonfungibleHandle<T>,1048		spender: &T::CrossAccountId,1049		from: &T::CrossAccountId,1050		token: TokenId,1051		nesting_budget: &dyn Budget,1052	) -> DispatchResult {1053		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10541055		// =========10561057		Self::burn(collection, from, token)1058	}10591060	pub fn check_nesting(1061		handle: &NonfungibleHandle<T>,1062		sender: T::CrossAccountId,1063		from: (CollectionId, TokenId),1064		under: TokenId,1065		nesting_budget: &dyn Budget,1066	) -> DispatchResult {1067		let nesting = handle.permissions.nesting();10681069		#[cfg(not(feature = "runtime-benchmarks"))]1070		let permissive = false;1071		#[cfg(feature = "runtime-benchmarks")]1072		let permissive = nesting.permissive;10731074		if permissive {1075			// Pass1076		} else if nesting.token_owner1077			&& <PalletStructure<T>>::check_indirectly_owned(1078				sender.clone(),1079				handle.id,1080				under,1081				Some(from),1082				nesting_budget,1083			)? {1084			// Pass1085		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1086			// Pass1087		} else {1088			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1089		}10901091		if let Some(whitelist) = &nesting.restricted {1092			ensure!(1093				whitelist.contains(&from.0),1094				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1095			);1096		}1097		Ok(())1098	}10991100	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1101		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1102	}11031104	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1105		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1106	}11071108	fn collection_has_tokens(collection_id: CollectionId) -> bool {1109		<TokenData<T>>::iter_prefix((collection_id,))1110			.next()1111			.is_some()1112	}11131114	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1115		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1116			.next()1117			.is_some()1118	}11191120	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1121		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1122			.map(|((child_collection_id, child_id), _)| TokenChild {1123				collection: child_collection_id,1124				token: child_id,1125			})1126			.collect()1127	}11281129	/// Delegated to `create_multiple_items`1130	pub fn create_item(1131		collection: &NonfungibleHandle<T>,1132		sender: &T::CrossAccountId,1133		data: CreateItemData<T>,1134		nesting_budget: &dyn Budget,1135	) -> DispatchResult {1136		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1137	}1138}
after · pallets/nonfungible/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 erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31	PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32	AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37	eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859/// Token data, stored independently from other data used to describe it.60/// Notably contains the owner account address.61#[struct_versioning::versioned(version = 2, upper)]62#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]63pub struct ItemData<CrossAccountId> {64	#[version(..2)]65	pub const_data: BoundedVec<u8, CustomDataLimit>,6667	#[version(..2)]68	pub variable_data: BoundedVec<u8, CustomDataLimit>,6970	pub owner: CrossAccountId,71}7273#[frame_support::pallet]74pub mod pallet {75	use super::*;76	use frame_support::{77		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,78	};79	use frame_system::pallet_prelude::*;80	use up_data_structs::{CollectionId, TokenId};81	use super::weights::WeightInfo;8283	#[pallet::error]84	pub enum Error<T> {85		/// Not Nonfungible item data used to mint in Nonfungible collection.86		NotNonfungibleDataUsedToMintFungibleCollectionToken,87		/// Used amount > 1 with NFT88		NonfungibleItemsHaveNoAmount,89		/// Unable to burn NFT with children90		CantBurnNftWithChildren,91	}9293	#[pallet::config]94	pub trait Config:95		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config96	{97		type WeightInfo: WeightInfo;98	}99100	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);101102	#[pallet::pallet]103	#[pallet::storage_version(STORAGE_VERSION)]104	#[pallet::generate_store(pub(super) trait Store)]105	pub struct Pallet<T>(_);106107	/// Total amount of minted tokens in a collection.108	#[pallet::storage]109	pub type TokensMinted<T: Config> =110		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112	/// Amount of burnt tokens in a collection.113	#[pallet::storage]114	pub type TokensBurnt<T: Config> =115		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;116117	/// Token data, used to partially describe a token.118	#[pallet::storage]119	pub type TokenData<T: Config> = StorageNMap<120		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),121		Value = ItemData<T::CrossAccountId>,122		QueryKind = OptionQuery,123	>;124125	/// Key-value pairs, describing the metadata of a token.126	#[pallet::storage]127	#[pallet::getter(fn token_properties)]128	pub type TokenProperties<T: Config> = StorageNMap<129		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),130		Value = Properties,131		QueryKind = ValueQuery,132		OnEmpty = up_data_structs::TokenProperties,133	>;134135	/// Scoped, auxiliary properties of a token, primarily used for on-chain operations.136	#[pallet::storage]137	#[pallet::getter(fn token_aux_property)]138	pub type TokenAuxProperties<T: Config> = StorageNMap<139		Key = (140			Key<Twox64Concat, CollectionId>,141			Key<Twox64Concat, TokenId>,142			Key<Twox64Concat, PropertyScope>,143			Key<Twox64Concat, PropertyKey>,144		),145		Value = AuxPropertyValue,146		QueryKind = OptionQuery,147	>;148149	/// Used to enumerate tokens owned by account.150	#[pallet::storage]151	pub type Owned<T: Config> = StorageNMap<152		Key = (153			Key<Twox64Concat, CollectionId>,154			Key<Blake2_128Concat, T::CrossAccountId>,155			Key<Twox64Concat, TokenId>,156		),157		Value = bool,158		QueryKind = ValueQuery,159	>;160161	/// Used to enumerate token's children.162	#[pallet::storage]163	#[pallet::getter(fn token_children)]164	pub type TokenChildren<T: Config> = StorageNMap<165		Key = (166			Key<Twox64Concat, CollectionId>,167			Key<Twox64Concat, TokenId>,168			Key<Twox64Concat, (CollectionId, TokenId)>,169		),170		Value = bool,171		QueryKind = ValueQuery,172	>;173174	/// Amount of tokens owned in a collection.s175	#[pallet::storage]176	pub type AccountBalance<T: Config> = StorageNMap<177		Key = (178			Key<Twox64Concat, CollectionId>,179			Key<Blake2_128Concat, T::CrossAccountId>,180		),181		Value = u32,182		QueryKind = ValueQuery,183	>;184185	/// todo doc186	#[pallet::storage]187	pub type Allowance<T: Config> = StorageNMap<188		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),189		Value = T::CrossAccountId,190		QueryKind = OptionQuery,191	>;192193	/// Upgrade from the old schema to properties.194	#[pallet::hooks]195	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {196		fn on_runtime_upgrade() -> Weight {197			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {198				let mut had_consts = BTreeSet::new();199				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(200					|(collection, token), v| {201						let mut props = vec![];202						if !v.const_data.is_empty() {203							props.push(Property {204								key: b"_old_constData".to_vec().try_into().unwrap(),205								value: v206									.const_data207									.clone()208									.into_inner()209									.try_into()210									.expect("const too long"),211							});212							had_consts.insert(collection);213						}214						if !v.variable_data.is_empty() {215							props.push(Property {216								key: b"_old_variableData".to_vec().try_into().unwrap(),217								value: v218									.variable_data219									.clone()220									.into_inner()221									.try_into()222									.expect("variable too long"),223							})224						}225						if !props.is_empty() {226							Self::set_scoped_token_properties(227								collection,228								token,229								PropertyScope::None,230								props.into_iter(),231							)232							.expect("existing token data exceeds property storage");233						}234						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))235					},236				);237				for collection in had_consts {238					<PalletCommon<T>>::set_property_permission_unchecked(239						collection,240						PropertyKeyPermission {241							key: b"_old_constData".to_vec().try_into().unwrap(),242							permission: PropertyPermission {243								mutable: false,244								collection_admin: true,245								token_owner: false,246							},247						},248					)249					.expect("failed to configure permission");250				}251			}252253			0254		}255	}256}257258pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);259impl<T: Config> NonfungibleHandle<T> {260	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {261		Self(inner)262	}263	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {264		self.0265	}266	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {267		&mut self.0268	}269}270impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {271	fn recorder(&self) -> &SubstrateRecorder<T> {272		self.0.recorder()273	}274	fn into_recorder(self) -> SubstrateRecorder<T> {275		self.0.into_recorder()276	}277}278impl<T: Config> Deref for NonfungibleHandle<T> {279	type Target = pallet_common::CollectionHandle<T>;280281	fn deref(&self) -> &Self::Target {282		&self.0283	}284}285286impl<T: Config> Pallet<T> {287	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {288		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289	}290	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {291		<TokenData<T>>::contains_key((collection.id, token))292	}293294	pub fn set_scoped_token_property(295		collection_id: CollectionId,296		token_id: TokenId,297		scope: PropertyScope,298		property: Property,299	) -> DispatchResult {300		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {301			properties.try_scoped_set(scope, property.key, property.value)302		})303		.map_err(<CommonError<T>>::from)?;304305		Ok(())306	}307308	pub fn set_scoped_token_properties(309		collection_id: CollectionId,310		token_id: TokenId,311		scope: PropertyScope,312		properties: impl Iterator<Item = Property>,313	) -> DispatchResult {314		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {315			stored_properties.try_scoped_set_from_iter(scope, properties)316		})317		.map_err(<CommonError<T>>::from)?;318319		Ok(())320	}321322	pub fn try_mutate_token_aux_property<R, E>(323		collection_id: CollectionId,324		token_id: TokenId,325		scope: PropertyScope,326		key: PropertyKey,327		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,328	) -> Result<R, E> {329		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)330	}331332	pub fn remove_token_aux_property(333		collection_id: CollectionId,334		token_id: TokenId,335		scope: PropertyScope,336		key: PropertyKey,337	) {338		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));339	}340341	pub fn iterate_token_aux_properties(342		collection_id: CollectionId,343		token_id: TokenId,344		scope: PropertyScope,345	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {346		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))347	}348349	pub fn current_token_id(collection_id: CollectionId) -> TokenId {350		TokenId(<TokensMinted<T>>::get(collection_id))351	}352}353354// unchecked calls skips any permission checks355impl<T: Config> Pallet<T> {356	pub fn init_collection(357		owner: T::CrossAccountId,358		data: CreateCollectionData<T::AccountId>,359		is_external: bool,360	) -> Result<CollectionId, DispatchError> {361		<PalletCommon<T>>::init_collection(owner, data, is_external)362	}363	pub fn destroy_collection(364		collection: NonfungibleHandle<T>,365		sender: &T::CrossAccountId,366	) -> DispatchResult {367		let id = collection.id;368369		if Self::collection_has_tokens(id) {370			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());371		}372373		// =========374375		PalletCommon::destroy_collection(collection.0, sender)?;376377		<TokenData<T>>::remove_prefix((id,), None);378		<TokenChildren<T>>::remove_prefix((id,), None);379		<Owned<T>>::remove_prefix((id,), None);380		<TokensMinted<T>>::remove(id);381		<TokensBurnt<T>>::remove(id);382		<Allowance<T>>::remove_prefix((id,), None);383		<AccountBalance<T>>::remove_prefix((id,), None);384		Ok(())385	}386387	pub fn burn(388		collection: &NonfungibleHandle<T>,389		sender: &T::CrossAccountId,390		token: TokenId,391	) -> DispatchResult {392		let token_data =393			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;394		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);395396		if collection.permissions.access() == AccessMode::AllowList {397			collection.check_allowlist(sender)?;398		}399400		if Self::token_has_children(collection.id, token) {401			return Err(<Error<T>>::CantBurnNftWithChildren.into());402		}403404		let burnt = <TokensBurnt<T>>::get(collection.id)405			.checked_add(1)406			.ok_or(ArithmeticError::Overflow)?;407408		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))409			.checked_sub(1)410			.ok_or(ArithmeticError::Overflow)?;411412		// =========413414		if balance == 0 {415			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));416		} else {417			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);418		}419420		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);421422		<Owned<T>>::remove((collection.id, &token_data.owner, token));423		<TokensBurnt<T>>::insert(collection.id, burnt);424		<TokenData<T>>::remove((collection.id, token));425		<TokenProperties<T>>::remove((collection.id, token));426		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);427		let old_spender = <Allowance<T>>::take((collection.id, token));428429		if let Some(old_spender) = old_spender {430			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(431				collection.id,432				token,433				token_data.owner.clone(),434				old_spender,435				0,436			));437		}438439		<PalletEvm<T>>::deposit_log(440			ERC721Events::Transfer {441				from: *token_data.owner.as_eth(),442				to: H160::default(),443				token_id: token.into(),444			}445			.to_log(collection_id_to_address(collection.id)),446		);447		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(448			collection.id,449			token,450			token_data.owner,451			1,452		));453		Ok(())454	}455456	#[transactional]457	pub fn burn_recursively(458		collection: &NonfungibleHandle<T>,459		sender: &T::CrossAccountId,460		token: TokenId,461		self_budget: &dyn Budget,462		breadth_budget: &dyn Budget,463	) -> DispatchResultWithPostInfo {464		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);465466		let current_token_account =467			T::CrossTokenAddressMapping::token_to_address(collection.id, token);468469		let mut weight = 0 as Weight;470471		// This method is transactional, if user in fact doesn't have permissions to remove token -472		// tokens removed here will be restored after rejected transaction473		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {474			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);475			let PostDispatchInfo { actual_weight, .. } =476				<PalletStructure<T>>::burn_item_recursively(477					current_token_account.clone(),478					collection,479					token,480					self_budget,481					breadth_budget,482				)?;483			if let Some(actual_weight) = actual_weight {484				weight = weight.saturating_add(actual_weight);485			}486		}487488		Self::burn(collection, sender, token)?;489		DispatchResultWithPostInfo::Ok(PostDispatchInfo {490			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),491			pays_fee: Pays::Yes,492		})493	}494495	#[transactional]496	fn modify_token_properties(497		collection: &NonfungibleHandle<T>,498		sender: &T::CrossAccountId,499		token_id: TokenId,500		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,501		is_token_create: bool,502		nesting_budget: &dyn Budget,503	) -> DispatchResult {504		let mut collection_admin_status = None;505		let mut token_owner_result = None;506507		let mut is_collection_admin =508			|| *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));509510		let mut is_token_owner = || {511			*token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {512				let is_owned = <PalletStructure<T>>::check_indirectly_owned(513					sender.clone(),514					collection.id,515					token_id,516					None,517					nesting_budget,518				)?;519520				Ok(is_owned)521			})522		};523524		for (key, value) in properties {525			let permission = <PalletCommon<T>>::property_permissions(collection.id)526				.get(&key)527				.cloned()528				.unwrap_or_else(PropertyPermission::none);529530			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))531				.get(&key)532				.is_some();533534			match permission {535				PropertyPermission { mutable: false, .. } if is_property_exists => {536					return Err(<CommonError<T>>::NoPermission.into());537				}538539				PropertyPermission {540					collection_admin,541					token_owner,542					..543				} => {544					//TODO: investigate threats during public minting.545					if is_token_create && (collection_admin || token_owner) && value.is_some() {546						// Pass547					} else if collection_admin && is_collection_admin() {548						// Pass549					} else if token_owner && is_token_owner()? {550						// Pass551					} else {552						fail!(<CommonError<T>>::NoPermission);553					}554				}555			}556557			match value {558				Some(value) => {559					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {560						properties.try_set(key.clone(), value)561					})562					.map_err(<CommonError<T>>::from)?;563564					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(565						collection.id,566						token_id,567						key,568					));569				}570				None => {571					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {572						properties.remove(&key)573					})574					.map_err(<CommonError<T>>::from)?;575576					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(577						collection.id,578						token_id,579						key,580					));581				}582			}583		}584585		Ok(())586	}587588	pub fn set_token_properties(589		collection: &NonfungibleHandle<T>,590		sender: &T::CrossAccountId,591		token_id: TokenId,592		properties: impl Iterator<Item = Property>,593		is_token_create: bool,594		nesting_budget: &dyn Budget,595	) -> DispatchResult {596		Self::modify_token_properties(597			collection,598			sender,599			token_id,600			properties.map(|p| (p.key, Some(p.value))),601			is_token_create,602			nesting_budget,603		)604	}605606	pub fn set_token_property(607		collection: &NonfungibleHandle<T>,608		sender: &T::CrossAccountId,609		token_id: TokenId,610		property: Property,611		nesting_budget: &dyn Budget,612	) -> DispatchResult {613		let is_token_create = false;614615		Self::set_token_properties(616			collection,617			sender,618			token_id,619			[property].into_iter(),620			is_token_create,621			nesting_budget,622		)623	}624625	pub fn delete_token_properties(626		collection: &NonfungibleHandle<T>,627		sender: &T::CrossAccountId,628		token_id: TokenId,629		property_keys: impl Iterator<Item = PropertyKey>,630		nesting_budget: &dyn Budget,631	) -> DispatchResult {632		let is_token_create = false;633634		Self::modify_token_properties(635			collection,636			sender,637			token_id,638			property_keys.into_iter().map(|key| (key, None)),639			is_token_create,640			nesting_budget,641		)642	}643644	pub fn delete_token_property(645		collection: &NonfungibleHandle<T>,646		sender: &T::CrossAccountId,647		token_id: TokenId,648		property_key: PropertyKey,649		nesting_budget: &dyn Budget,650	) -> DispatchResult {651		Self::delete_token_properties(652			collection,653			sender,654			token_id,655			[property_key].into_iter(),656			nesting_budget,657		)658	}659660	pub fn set_collection_properties(661		collection: &NonfungibleHandle<T>,662		sender: &T::CrossAccountId,663		properties: Vec<Property>,664	) -> DispatchResult {665		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)666	}667668	pub fn delete_collection_properties(669		collection: &CollectionHandle<T>,670		sender: &T::CrossAccountId,671		property_keys: Vec<PropertyKey>,672	) -> DispatchResult {673		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)674	}675676	pub fn set_token_property_permissions(677		collection: &CollectionHandle<T>,678		sender: &T::CrossAccountId,679		property_permissions: Vec<PropertyKeyPermission>,680	) -> DispatchResult {681		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)682	}683684	pub fn set_property_permission(685		collection: &CollectionHandle<T>,686		sender: &T::CrossAccountId,687		permission: PropertyKeyPermission,688	) -> DispatchResult {689		<PalletCommon<T>>::set_property_permission(collection, sender, permission)690	}691692	pub fn transfer(693		collection: &NonfungibleHandle<T>,694		from: &T::CrossAccountId,695		to: &T::CrossAccountId,696		token: TokenId,697		nesting_budget: &dyn Budget,698	) -> DispatchResult {699		ensure!(700			collection.limits.transfers_enabled(),701			<CommonError<T>>::TransferNotAllowed702		);703704		let token_data =705			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;706		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);707708		if collection.permissions.access() == AccessMode::AllowList {709			collection.check_allowlist(from)?;710			collection.check_allowlist(to)?;711		}712		<PalletCommon<T>>::ensure_correct_receiver(to)?;713714		let balance_from = <AccountBalance<T>>::get((collection.id, from))715			.checked_sub(1)716			.ok_or(<CommonError<T>>::TokenValueTooLow)?;717		let balance_to = if from != to {718			let balance_to = <AccountBalance<T>>::get((collection.id, to))719				.checked_add(1)720				.ok_or(ArithmeticError::Overflow)?;721722			ensure!(723				balance_to < collection.limits.account_token_ownership_limit(),724				<CommonError<T>>::AccountTokenLimitExceeded,725			);726727			Some(balance_to)728		} else {729			None730		};731732		<PalletStructure<T>>::nest_if_sent_to_token(733			from.clone(),734			to,735			collection.id,736			token,737			nesting_budget,738		)?;739740		// =========741742		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);743744		<TokenData<T>>::insert(745			(collection.id, token),746			ItemData {747				owner: to.clone(),748				..token_data749			},750		);751752		if let Some(balance_to) = balance_to {753			// from != to754			if balance_from == 0 {755				<AccountBalance<T>>::remove((collection.id, from));756			} else {757				<AccountBalance<T>>::insert((collection.id, from), balance_from);758			}759			<AccountBalance<T>>::insert((collection.id, to), balance_to);760			<Owned<T>>::remove((collection.id, from, token));761			<Owned<T>>::insert((collection.id, to, token), true);762		}763		Self::set_allowance_unchecked(collection, from, token, None, true);764765		<PalletEvm<T>>::deposit_log(766			ERC721Events::Transfer {767				from: *from.as_eth(),768				to: *to.as_eth(),769				token_id: token.into(),770			}771			.to_log(collection_id_to_address(collection.id)),772		);773		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774			collection.id,775			token,776			from.clone(),777			to.clone(),778			1,779		));780		Ok(())781	}782783	pub fn create_multiple_items(784		collection: &NonfungibleHandle<T>,785		sender: &T::CrossAccountId,786		data: Vec<CreateItemData<T>>,787		nesting_budget: &dyn Budget,788	) -> DispatchResult {789		if !collection.is_owner_or_admin(sender) {790			ensure!(791				collection.permissions.mint_mode(),792				<CommonError<T>>::PublicMintingNotAllowed793			);794			collection.check_allowlist(sender)?;795796			for item in data.iter() {797				collection.check_allowlist(&item.owner)?;798			}799		}800801		for data in data.iter() {802			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;803		}804805		let first_token = <TokensMinted<T>>::get(collection.id);806		let tokens_minted = first_token807			.checked_add(data.len() as u32)808			.ok_or(ArithmeticError::Overflow)?;809		ensure!(810			tokens_minted <= collection.limits.token_limit(),811			<CommonError<T>>::CollectionTokenLimitExceeded812		);813814		let mut balances = BTreeMap::new();815		for data in &data {816			let balance = balances817				.entry(&data.owner)818				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));819			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;820821			ensure!(822				*balance <= collection.limits.account_token_ownership_limit(),823				<CommonError<T>>::AccountTokenLimitExceeded,824			);825		}826827		for (i, data) in data.iter().enumerate() {828			let token = TokenId(first_token + i as u32 + 1);829830			<PalletStructure<T>>::check_nesting(831				sender.clone(),832				&data.owner,833				collection.id,834				token,835				nesting_budget,836			)?;837		}838839		// =========840841		with_transaction(|| {842			for (i, data) in data.iter().enumerate() {843				let token = first_token + i as u32 + 1;844845				<TokenData<T>>::insert(846					(collection.id, token),847					ItemData {848						// const_data: data.const_data.clone(),849						owner: data.owner.clone(),850					},851				);852853				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(854					&data.owner,855					collection.id,856					TokenId(token),857				);858859				if let Err(e) = Self::set_token_properties(860					collection,861					sender,862					TokenId(token),863					data.properties.clone().into_iter(),864					true,865					nesting_budget,866				) {867					return TransactionOutcome::Rollback(Err(e));868				}869			}870			TransactionOutcome::Commit(Ok(()))871		})?;872873		<TokensMinted<T>>::insert(collection.id, tokens_minted);874		for (account, balance) in balances {875			<AccountBalance<T>>::insert((collection.id, account), balance);876		}877		for (i, data) in data.into_iter().enumerate() {878			let token = first_token + i as u32 + 1;879			<Owned<T>>::insert((collection.id, &data.owner, token), true);880881			<PalletEvm<T>>::deposit_log(882				ERC721Events::Transfer {883					from: H160::default(),884					to: *data.owner.as_eth(),885					token_id: token.into(),886				}887				.to_log(collection_id_to_address(collection.id)),888			);889			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(890				collection.id,891				TokenId(token),892				data.owner.clone(),893				1,894			));895		}896		Ok(())897	}898899	pub fn set_allowance_unchecked(900		collection: &NonfungibleHandle<T>,901		sender: &T::CrossAccountId,902		token: TokenId,903		spender: Option<&T::CrossAccountId>,904		assume_implicit_eth: bool,905	) {906		if let Some(spender) = spender {907			let old_spender = <Allowance<T>>::get((collection.id, token));908			<Allowance<T>>::insert((collection.id, token), spender);909			// In ERC721 there is only one possible approved user of token, so we set910			// approved user to spender911			<PalletEvm<T>>::deposit_log(912				ERC721Events::Approval {913					owner: *sender.as_eth(),914					approved: *spender.as_eth(),915					token_id: token.into(),916				}917				.to_log(collection_id_to_address(collection.id)),918			);919			// In Unique chain, any token can have any amount of approved users, so we need to920			// set allowance of old owner to 0, and allowance of new owner to 1921			if old_spender.as_ref() != Some(spender) {922				if let Some(old_owner) = old_spender {923					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(924						collection.id,925						token,926						sender.clone(),927						old_owner,928						0,929					));930				}931				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(932					collection.id,933					token,934					sender.clone(),935					spender.clone(),936					1,937				));938			}939		} else {940			let old_spender = <Allowance<T>>::take((collection.id, token));941			if !assume_implicit_eth {942				// In ERC721 there is only one possible approved user of token, so we set943				// approved user to zero address944				<PalletEvm<T>>::deposit_log(945					ERC721Events::Approval {946						owner: *sender.as_eth(),947						approved: H160::default(),948						token_id: token.into(),949					}950					.to_log(collection_id_to_address(collection.id)),951				);952			}953			// In Unique chain, any token can have any amount of approved users, so we need to954			// set allowance of old owner to 0955			if let Some(old_spender) = old_spender {956				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(957					collection.id,958					token,959					sender.clone(),960					old_spender,961					0,962				));963			}964		}965	}966967	pub fn set_allowance(968		collection: &NonfungibleHandle<T>,969		sender: &T::CrossAccountId,970		token: TokenId,971		spender: Option<&T::CrossAccountId>,972	) -> DispatchResult {973		if collection.permissions.access() == AccessMode::AllowList {974			collection.check_allowlist(sender)?;975			if let Some(spender) = spender {976				collection.check_allowlist(spender)?;977			}978		}979980		if let Some(spender) = spender {981			<PalletCommon<T>>::ensure_correct_receiver(spender)?;982		}983984		let token_data =985			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;986		if &token_data.owner != sender {987			ensure!(988				collection.ignores_owned_amount(sender),989				<CommonError<T>>::CantApproveMoreThanOwned990			);991		}992993		// =========994995		Self::set_allowance_unchecked(collection, sender, token, spender, false);996		Ok(())997	}998999	fn check_allowed(1000		collection: &NonfungibleHandle<T>,1001		spender: &T::CrossAccountId,1002		from: &T::CrossAccountId,1003		token: TokenId,1004		nesting_budget: &dyn Budget,1005	) -> DispatchResult {1006		if spender.conv_eq(from) {1007			return Ok(());1008		}1009		if collection.permissions.access() == AccessMode::AllowList {1010			// `from`, `to` checked in [`transfer`]1011			collection.check_allowlist(spender)?;1012		}10131014		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {1015			return Ok(());1016		}10171018		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1019			ensure!(1020				<PalletStructure<T>>::check_indirectly_owned(1021					spender.clone(),1022					source.0,1023					source.1,1024					None,1025					nesting_budget1026				)?,1027				<CommonError<T>>::ApprovedValueTooLow,1028			);1029			return Ok(());1030		}1031		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1032			return Ok(());1033		}1034		ensure!(1035			collection.ignores_allowance(spender),1036			<CommonError<T>>::ApprovedValueTooLow1037		);1038		Ok(())1039	}10401041	pub fn transfer_from(1042		collection: &NonfungibleHandle<T>,1043		spender: &T::CrossAccountId,1044		from: &T::CrossAccountId,1045		to: &T::CrossAccountId,1046		token: TokenId,1047		nesting_budget: &dyn Budget,1048	) -> DispatchResult {1049		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10501051		// =========10521053		// Allowance is reset in [`transfer`]1054		Self::transfer(collection, from, to, token, nesting_budget)1055	}10561057	pub fn burn_from(1058		collection: &NonfungibleHandle<T>,1059		spender: &T::CrossAccountId,1060		from: &T::CrossAccountId,1061		token: TokenId,1062		nesting_budget: &dyn Budget,1063	) -> DispatchResult {1064		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10651066		// =========10671068		Self::burn(collection, from, token)1069	}10701071	pub fn check_nesting(1072		handle: &NonfungibleHandle<T>,1073		sender: T::CrossAccountId,1074		from: (CollectionId, TokenId),1075		under: TokenId,1076		nesting_budget: &dyn Budget,1077	) -> DispatchResult {1078		let nesting = handle.permissions.nesting();10791080		#[cfg(not(feature = "runtime-benchmarks"))]1081		let permissive = false;1082		#[cfg(feature = "runtime-benchmarks")]1083		let permissive = nesting.permissive;10841085		if permissive {1086			// Pass1087		} else if nesting.token_owner1088			&& <PalletStructure<T>>::check_indirectly_owned(1089				sender.clone(),1090				handle.id,1091				under,1092				Some(from),1093				nesting_budget,1094			)? {1095			// Pass1096		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1097			// Pass1098		} else {1099			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1100		}11011102		if let Some(whitelist) = &nesting.restricted {1103			ensure!(1104				whitelist.contains(&from.0),1105				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1106			);1107		}1108		Ok(())1109	}11101111	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1112		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1113	}11141115	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1116		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1117	}11181119	fn collection_has_tokens(collection_id: CollectionId) -> bool {1120		<TokenData<T>>::iter_prefix((collection_id,))1121			.next()1122			.is_some()1123	}11241125	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1126		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1127			.next()1128			.is_some()1129	}11301131	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1132		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1133			.map(|((child_collection_id, child_id), _)| TokenChild {1134				collection: child_collection_id,1135				token: child_id,1136			})1137			.collect()1138	}11391140	/// Delegated to `create_multiple_items`1141	pub fn create_item(1142		collection: &NonfungibleHandle<T>,1143		sender: &T::CrossAccountId,1144		data: CreateItemData<T>,1145		nesting_budget: &dyn Budget,1146	) -> DispatchResult {1147		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1148	}1149}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -38,6 +38,8 @@
 pub mod weights;
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
+/// Token data, stored independently from other data used to describe it.
+/// Notably contains the token metadata.
 #[struct_versioning::versioned(version = 2, upper)]
 #[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]
 pub struct ItemData {
@@ -86,13 +88,17 @@
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
 
+	/// Total amount of minted tokens in a collection.
 	#[pallet::storage]
 	pub type TokensMinted<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+	
+	/// Amount of tokens burnt in a collection.
 	#[pallet::storage]
 	pub type TokensBurnt<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 
+	/// Token data, used to partially describe a token.
 	#[pallet::storage]
 	pub type TokenData<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -100,6 +106,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Amount of pieces a refungible token is split into.
 	#[pallet::storage]
 	pub type TotalSupply<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -107,7 +114,7 @@
 		QueryKind = ValueQuery,
 	>;
 
-	/// Used to enumerate tokens owned by account
+	/// Used to enumerate tokens owned by account.
 	#[pallet::storage]
 	pub type Owned<T: Config> = StorageNMap<
 		Key = (
@@ -119,6 +126,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Amount of tokens (not pieces) partially owned by an account within a collection.
 	#[pallet::storage]
 	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
@@ -130,6 +138,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Amount of pieces of a token owned by an account.
 	#[pallet::storage]
 	pub type Balance<T: Config> = StorageNMap<
 		Key = (
@@ -142,6 +151,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// todo:doc
 	#[pallet::storage]
 	pub type Allowance<T: Config> = StorageNMap<
 		Key = (
@@ -248,7 +258,7 @@
 		// TODO: ERC721 transfer event
 		Ok(())
 	}
-
+	
 	pub fn burn(
 		collection: &RefungibleHandle<T>,
 		owner: &T::CrossAccountId,
@@ -595,6 +605,7 @@
 		Ok(())
 	}
 
+	/// todo:doc oh look, a precedent. not pub, too. but it has an unclear use-case.
 	/// Returns allowance, which should be set after transaction
 	fn check_allowed(
 		collection: &RefungibleHandle<T>,
modifiedpallets/scheduler/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -258,6 +258,7 @@
 
 	/// A Scheduler-Runtime interface for finer payment handling.
 	pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {
+		/// Reserve the maximum spendings on a call.
 		fn reserve_balance(
 			id: ScheduledId,
 			sponsor: <T as frame_system::Config>::AccountId,
@@ -265,6 +266,7 @@
 			count: u32,
 		) -> Result<(), DispatchError>;
 
+		/// Pay for call dispatch (un-reserve) from the reserved funds, returning the change.
 		fn pay_for_call(
 			id: ScheduledId,
 			sponsor: <T as frame_system::Config>::AccountId,
@@ -280,6 +282,7 @@
 			TransactionValidityError,
 		>;
 
+		/// Release reserved funds.
 		fn cancel_reserve(
 			id: ScheduledId,
 			sponsor: <T as frame_system::Config>::AccountId,
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -25,19 +25,19 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// While searched for owner, got already checked account
+		/// While searching for the owner, encountered an already checked account, detecting a loop.
 		OuroborosDetected,
-		/// While searched for owner, encountered depth limit
+		/// While searching for the owner, reached the depth limit.
 		DepthLimit,
-		/// While iterating over children, encountered breadth limit
+		/// While iterating over children, reached the breadth limit.
 		BreadthLimit,
-		/// While searched for owner, found token owner by not-yet-existing token
+		/// Couldn't find the token owner that is a token. Perhaps, it does not yet exist. todo:doc? rephrase?
 		TokenNotFound,
 	}
 
 	#[pallet::event]
 	pub enum Event<T> {
-		/// Executed call on behalf of token
+		/// Executed call on behalf of the token.
 		Executed(DispatchResult),
 	}
 
@@ -73,11 +73,11 @@
 
 #[derive(PartialEq)]
 pub enum Parent<CrossAccountId> {
-	/// Token owned by normal account
+	/// Token owned by a normal account.
 	User(CrossAccountId),
-	/// Passed token not found
+	/// Could not find the token provided as the owner.
 	TokenNotFound,
-	/// Token owner is another token (target token still may not exist)
+	/// Token owner is another token (still, the target token may not exist).
 	Token(CollectionId, TokenId),
 }
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -101,7 +101,7 @@
 		/// * admin:  Admin address.
 		CollectionAdminAdded(CollectionId, CrossAccountId),
 
-		/// Collection owned was change
+		/// Collection owned was changed
 		///
 		/// # Arguments
 		///
@@ -137,7 +137,7 @@
 		/// * admin:  Admin address.
 		CollectionAdminRemoved(CollectionId, CrossAccountId),
 
-		/// Address was remove from allow list
+		/// Address was removed from the allow list
 		///
 		/// # Arguments
 		///
@@ -146,7 +146,7 @@
 		/// * user:  Address.
 		AllowListAddressRemoved(CollectionId, CrossAccountId),
 
-		/// Address was add to allow list
+		/// Address was added to the allow list
 		///
 		/// # Arguments
 		///
@@ -155,13 +155,18 @@
 		/// * user:  Address.
 		AllowListAddressAdded(CollectionId, CrossAccountId),
 
-		/// Collection limits was set
+		/// Collection limits were set
 		///
 		/// # Arguments
 		///
 		/// * collection_id: Globally unique collection identifier.
 		CollectionLimitSet(CollectionId),
 
+		/// Collection permissions were set
+		/// 
+		/// # Arguments
+		/// 
+		/// * collection_id: Globally unique collection identifier.
 		CollectionPermissionSet(CollectionId),
 	}
 }
@@ -198,7 +203,7 @@
 		ChainVersion: u64;
 		//#endregion
 
-		//#region Tokens transfer rate limit baskets
+		//#region Tokens transfer sponosoring rate limit baskets
 		/// (Collection id (controlled?2), who created (real))
 		/// TODO: Off chain worker should remove from this map when collection gets removed
 		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;
@@ -214,11 +219,14 @@
 		/// Collection id (controlled?2), token id (controlled?2)
 		#[deprecated]
 		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+		/// Last sponsoring of token property setting // todo:doc rephrase this and the following 
 		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
 
-		/// Approval sponsoring
+		/// Last sponsoring of NFT approval in a collection
 		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+		/// Last sponsoring of fungible tokens approval in a collection
 		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
+		/// Last sponsoring of RFT approval in a collection
 		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
 	}
 }
@@ -278,9 +286,16 @@
 			Self::create_collection_ex(origin, data)
 		}
 
-		/// This method creates a collection
+		/// Create a collection with explicit parameters.
+		/// Prefer it to the deprecated [`created_collection`] method.
+		///
+		/// # Permissions
+		///
+		/// * Anyone.
 		///
-		/// Prefer it to deprecated [`created_collection`] method
+		/// # Arguments
+		/// 
+		/// * data: explicit create-collection data.
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
 		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
@@ -293,11 +308,11 @@
 			Ok(())
 		}
 
-		/// Destroys collection if no tokens within this collection
+		/// Destroy the collection if no tokens exist within.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
+		/// * Collection Owner
 		///
 		/// # Arguments
 		///
@@ -398,7 +413,7 @@
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
+		/// * Collection Owner
 		///
 		/// # Arguments
 		///
@@ -424,40 +439,40 @@
 			target_collection.save()
 		}
 
-		/// Adds an admin of the Collection.
+		/// Adds an admin of the collection.
 		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
-		/// * Collection Admin.
+		/// * Collection Owner
+		/// * Collection Admin
 		///
 		/// # Arguments
 		///
 		/// * collection_id: ID of the Collection to add admin for.
 		///
-		/// * new_admin_id: Address of new admin to add.
+		/// * new_admin: Address of new admin to add.
 		#[weight = <SelfWeightOf<T>>::add_collection_admin()]
 		#[transactional]
-		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
+		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			collection.check_is_internal()?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
 				collection_id,
-				new_admin_id.clone()
+				new_admin.clone()
 			));
 
-			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
+			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)
 		}
 
 		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
-		/// * Collection Admin.
+		/// * Collection Owner
+		/// * Collection Admin
 		///
 		/// # Arguments
 		///
@@ -479,9 +494,12 @@
 			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
 		}
 
+		/// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
+		/// 
 		/// # Permissions
 		///
 		/// * Collection Owner
+		/// * Collection Admin
 		///
 		/// # Arguments
 		///
@@ -507,9 +525,11 @@
 			target_collection.save()
 		}
 
+		/// Confirm own sponsorship of a collection.
+		/// 
 		/// # Permissions
 		///
-		/// * Sponsor.
+		/// * The sponsor to-be
 		///
 		/// # Arguments
 		///
@@ -538,7 +558,7 @@
 		///
 		/// # Permissions
 		///
-		/// * Collection owner.
+		/// * Collection Owner
 		///
 		/// # Arguments
 		///
@@ -560,12 +580,12 @@
 			target_collection.save()
 		}
 
-		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.
+		/// Create a concrete instance of NFT Collection created with CreateCollection method.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
-		/// * Collection Admin.
+		/// * Collection Owner
+		/// * Collection Admin
 		/// * Anyone if
 		///     * Allow List is enabled, and
 		///     * Address is added to allow list, and
@@ -587,12 +607,12 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))
 		}
 
-		/// This method creates multiple items in a collection created with CreateCollection method.
+		/// Create multiple items in a collection created with CreateCollection method.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
-		/// * Collection Admin.
+		/// * Collection Owner
+		/// * Collection Admin
 		/// * Anyone if
 		///     * Allow List is enabled, and
 		///     * Address is added to allow list, and
@@ -615,6 +635,18 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))
 		}
 
+		/// Add or change collection properties.
+		///
+		/// # Permissions
+		///
+		/// * Collection Owner
+		/// * Collection Admin
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
 		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
 		#[transactional]
 		pub fn set_collection_properties(
@@ -629,6 +661,18 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))
 		}
 
+		/// Delete specified collection properties.
+		///
+		/// # Permissions
+		///
+		/// * Collection Owner
+		/// * Collection Admin
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * property_keys: a vector of keys of the properties to be deleted.
 		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
 		#[transactional]
 		pub fn delete_collection_properties(
@@ -643,6 +687,22 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))
 		}
 
+		/// Add or change token properties according to collection's permissions.
+		///
+		/// # Permissions
+		///
+		/// * Depends on collection's token property permissions and specified property mutability:
+		/// 	* Collection Owner
+		/// 	* Collection Admin
+		/// 	* Token Owner
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		/// 
+		/// * token_id.
+		///
+		/// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
 		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
 		#[transactional]
 		pub fn set_token_properties(
@@ -659,6 +719,22 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))
 		}
 
+		/// Delete specified token properties.
+		///
+		/// # Permissions
+		///
+		/// * Depends on collection's token property permissions and specified property mutability:
+		/// 	* Collection Owner
+		/// 	* Collection Admin
+		/// 	* Token Owner
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * token_id.
+		///
+		/// * property_keys: a vector of keys of the properties to be deleted.
 		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
 		#[transactional]
 		pub fn delete_token_properties(
@@ -675,6 +751,18 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))
 		}
 
+		/// Add or change token property permissions of a collection.
+		///
+		/// # Permissions
+		///
+		/// * Collection Owner
+		/// * Collection Admin
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.
 		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]
 		#[transactional]
 		pub fn set_token_property_permissions(
@@ -689,6 +777,22 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))
 		}
 
+		/// Create multiple items inside a collection with explicitly specified initial parameters.
+		/// 
+		/// # Permissions
+		///
+		/// * Collection Owner
+		/// * Collection Admin
+		/// * Anyone if
+		///     * Allow List is enabled, and
+		///     * Address is added to allow list, and
+		///     * MintPermission is enabled (see SetMintPermission method)
+		///
+		/// # Arguments
+		///
+		/// * collection_id: ID of the collection.
+		///
+		/// * data: explicit item creation data. 
 		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
 		#[transactional]
 		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
@@ -698,11 +802,11 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))
 		}
 
-		/// Set transfers_enabled value for particular collection
+		/// Set transfers_enabled value for particular collection.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
+		/// * Collection Owner
 		///
 		/// # Arguments
 		///
@@ -723,13 +827,13 @@
 			target_collection.save()
 		}
 
-		/// Destroys a concrete instance of NFT.
+		/// Destroy a concrete instance of NFT.
 		///
 		/// # Permissions
 		///
-		/// * Collection Owner.
-		/// * Collection Admin.
-		/// * Current NFT Owner.
+		/// * Collection Owner
+		/// * Collection Admin
+		/// * Current NFT Owner
 		///
 		/// # Arguments
 		///
@@ -752,7 +856,7 @@
 			Ok(post_info)
 		}
 
-		/// Destroys a concrete instance of NFT on behalf of the owner
+		/// Destroy a concrete instance of NFT on behalf of the owner.
 		/// See also: [`approve`]
 		///
 		/// # Permissions
@@ -835,6 +939,7 @@
 		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
 		///
 		/// # Permissions
+		/// 
 		/// * Collection Owner
 		/// * Collection Admin
 		/// * Current NFT owner
@@ -860,6 +965,18 @@
 			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
 		}
 
+		/// Set specific limits of a collection. Empty, or None fields mean chain default.
+		///.
+		/// # Permissions
+		/// 
+		/// * Collection Owner
+		/// * Collection Admin
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * new_limit: The new limits of the collection. They will overwrite the current ones.
 		#[weight = <SelfWeightOf<T>>::set_collection_limits()]
 		#[transactional]
 		pub fn set_collection_limits(
@@ -882,12 +999,24 @@
 			target_collection.save()
 		}
 
+		/// Set specific permissions of a collection. Empty, or None fields mean chain default.
+		///
+		/// # Permissions
+		/// 
+		/// * Collection Owner
+		/// * Collection Admin
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * new_permission: The new permissions of the collection. They will overwrite the current ones.
 		#[weight = <SelfWeightOf<T>>::set_collection_limits()]
 		#[transactional]
 		pub fn set_collection_permissions(
 			origin,
 			collection_id: CollectionId,
-			new_limit: CollectionPermissions,
+			new_permission: CollectionPermissions,
 		) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -895,7 +1024,7 @@
 			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)?;
+			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(
 				collection_id
@@ -904,6 +1033,19 @@
 			target_collection.save()
 		}
 
+		/// Re-partition a refungible token, while owning all of its parts.
+		///
+		/// # Permissions
+		/// 
+		/// * Token Owner (must own every part)
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		/// 
+		/// * token: the ID of the RFT. 
+		///
+		/// * amount: The new number of parts into which the token shall be partitioned.
 		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
 		#[transactional]
 		pub fn repartition(
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -197,7 +197,6 @@
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub enum CollectionMode {
 	NFT,
-	// decimal points
 	Fungible(DecimalPoints),
 	ReFungible,
 }
@@ -252,12 +251,14 @@
 pub enum SponsorshipState<AccountId> {
 	/// The fees are applied to the transaction sender
 	Disabled,
+	/// Pending confirmation from a sponsor-to-be
 	Unconfirmed(AccountId),
 	/// Transactions are sponsored by specified account
 	Confirmed(AccountId),
 }
 
 impl<AccountId> SponsorshipState<AccountId> {
+	/// Get the acting sponsor account, if present
 	pub fn sponsor(&self) -> Option<&AccountId> {
 		match self {
 			Self::Confirmed(sponsor) => Some(sponsor),
@@ -265,6 +266,7 @@
 		}
 	}
 
+	/// Get the sponsor account currently pending confirmation, if present
 	pub fn pending_sponsor(&self) -> Option<&AccountId> {
 		match self {
 			Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),
@@ -272,6 +274,7 @@
 		}
 	}
 
+	/// Is sponsorship set and acting
 	pub fn confirmed(&self) -> bool {
 		matches!(self, Self::Confirmed(_))
 	}
@@ -283,7 +286,7 @@
 	}
 }
 
-/// Used in storage
+/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version)
 #[struct_versioning::versioned(version = 2, upper)]
 #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
 pub struct Collection<AccountId> {
@@ -324,7 +327,7 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
-/// Used in RPC calls
+/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version)
 #[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct RpcCollection<AccountId> {
@@ -362,12 +365,15 @@
 
 pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
 
-/// All fields are wrapped in `Option`s, where None means chain default
+/// Limits and restrictions of a collection.
+/// All fields are wrapped in `Option`s, where None means chain default.
 // When adding/removing fields from this struct - don't forget to also update clamp_limits
 #[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct CollectionLimits {
+	/// Maximum number of owned tokens per account
 	pub account_token_ownership_limit: Option<u32>,
+	/// Maximum size of data of a sponsored transaction
 	pub sponsored_data_size: Option<u32>,
 
 	/// FIXME should we delete this or repurpose it?
@@ -375,13 +381,18 @@
 	/// Some(v) - setVariableMetadata is sponsored
 	///           if there is v block between txs
 	pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,
+	/// Maximum amount of tokens inside the collection
 	pub token_limit: Option<u32>,
 
-	// Timeouts for item types in passed blocks
+	/// Timeout for sponsoring a token transfer in passed blocks
 	pub sponsor_transfer_timeout: Option<u32>,
+	/// Timeout for sponsoring an approval in passed blocks
 	pub sponsor_approve_timeout: Option<u32>,
+	/// Can a token be transferred by the owner
 	pub owner_can_transfer: Option<bool>,
+	/// Can a token be burned by the owner
 	pub owner_can_destroy: Option<bool>,
+	/// Can a token be transferred at all
 	pub transfers_enabled: Option<bool>,
 }
 
@@ -509,6 +520,7 @@
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub enum SponsoringRateLimit {
 	SponsoringDisabled,
+	/// Once per how many blocks can sponsorship of a transaction type occur
 	Blocks(u32),
 }
 
@@ -516,6 +528,7 @@
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 #[derivative(Debug)]
 pub struct CreateNftData {
+	/// Key-value pairs used to describe the token as metadata
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub properties: CollectionPropertiesVec,
@@ -524,6 +537,7 @@
 #[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct CreateFungibleData {
+	/// Number of fungible tokens minted
 	pub value: u128,
 }
 
@@ -534,6 +548,7 @@
 	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
+	/// Number of pieces the RFT is split into
 	pub pieces: u128,
 }
 
@@ -553,6 +568,7 @@
 	ReFungible(CreateReFungibleData),
 }
 
+/// Explicit NFT creation data with meta parameters
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug)]
 pub struct CreateNftExData<CrossAccountId> {
@@ -561,6 +577,7 @@
 	pub owner: CrossAccountId,
 }
 
+/// Explicit RFT creation data with meta parameters
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
 pub struct CreateRefungibleExData<CrossAccountId> {
@@ -570,6 +587,7 @@
 	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
 }
 
+/// Explicit item creation data with meta parameters, namely the owner
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
 pub enum CreateItemExData<CrossAccountId> {
@@ -617,6 +635,7 @@
 	}
 }
 
+/// Token's address, dictated by its collection and token IDs
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 // todo possibly rename to be used generally as an address pair
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -43,13 +43,13 @@
     accountTokens: fun('Get tokens owned by account', [collectionParam, crossAccountParam()], 'Vec<u32>'),
     collectionTokens: fun('Get tokens contained in collection', [collectionParam], 'Vec<u32>'),
 
-    lastTokenId: fun('Get last token id', [collectionParam], 'u32'),
+    lastTokenId: fun('Get last token ID created in a collection', [collectionParam], 'u32'),
     totalSupply: fun('Get amount of unique collection tokens', [collectionParam], 'u32'),
-    accountBalance: fun('Get amount of different user tokens', [collectionParam, crossAccountParam()], 'u32'),
-    balance: fun('Get amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
+    accountBalance: fun('Get owned amount of any user tokens', [collectionParam, crossAccountParam()], 'u32'),
+    balance: fun('Get owned amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
     allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
     tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
-    topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
+    topmostTokenOwner: fun('Get token owner, in case of nested token - find the parent recursively', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
     tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
     constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
     variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
@@ -74,7 +74,7 @@
       'UpDataStructsTokenData',
     ),
     tokenExists: fun('Check if token exists', [collectionParam, tokenParam], 'bool'),
-    collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
+    collectionById: fun('Get collection by specified ID', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
     collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
     allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
     nextSponsored: fun('Get number of blocks when sponsored transaction is available', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),