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
before · pallets/common/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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkBoundedTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> DispatchResult {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///293		/// * mode: [CollectionMode] converted into u8.294		///295		/// * account_id: Collection owner.296		CollectionCreated(CollectionId, u8, T::AccountId),297298		/// New collection was destroyed299		///300		/// # Arguments301		///302		/// * collection_id: Globally unique identifier of collection.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: Id of the collection where item was created.310		///311		/// * item_id: Id of an item. Unique within the collection.312		///313		/// * recipient: Owner of newly created item314		///315		/// * amount: Always 1 for NFT316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: which user has destroyed its tokens327		///328		/// * amount: Always 1 for NFT329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred332		///333		/// * collection_id: Id of collection to which item is belong334		///335		/// * item_id: Id of an item336		///337		/// * sender: Original owner of item338		///339		/// * recipient: New owner of item340		///341		/// * amount: Always 1 for NFT342		Transfer(343			CollectionId,344			TokenId,345			T::CrossAccountId,346			T::CrossAccountId,347			u128,348		),349350		/// * collection_id351		///352		/// * item_id353		///354		/// * sender355		///356		/// * spender357		///358		/// * amount359		Approved(360			CollectionId,361			TokenId,362			T::CrossAccountId,363			T::CrossAccountId,364			u128,365		),366367		CollectionPropertySet(CollectionId, PropertyKey),368369		CollectionPropertyDeleted(CollectionId, PropertyKey),370371		TokenPropertySet(CollectionId, TokenId, PropertyKey),372373		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),374375		PropertyPermissionSet(CollectionId, PropertyKey),376	}377378	#[pallet::error]379	pub enum Error<T> {380		/// This collection does not exist.381		CollectionNotFound,382		/// Sender parameter and item owner must be equal.383		MustBeTokenOwner,384		/// No permission to perform action385		NoPermission,386		/// Destroying only empty collections is allowed387		CantDestroyNotEmptyCollection,388		/// Collection is not in mint mode.389		PublicMintingNotAllowed,390		/// Address is not in allow list.391		AddressNotInAllowlist,392393		/// Collection name can not be longer than 63 char.394		CollectionNameLimitExceeded,395		/// Collection description can not be longer than 255 char.396		CollectionDescriptionLimitExceeded,397		/// Token prefix can not be longer than 15 char.398		CollectionTokenPrefixLimitExceeded,399		/// Total collections bound exceeded.400		TotalCollectionsLimitExceeded,401		/// Exceeded max admin count402		CollectionAdminCountExceeded,403		/// Collection limit bounds per collection exceeded404		CollectionLimitBoundsExceeded,405		/// Tried to enable permissions which are only permitted to be disabled406		OwnerPermissionsCantBeReverted,407		/// Collection settings not allowing items transferring408		TransferNotAllowed,409		/// Account token limit exceeded per collection410		AccountTokenLimitExceeded,411		/// Collection token limit exceeded412		CollectionTokenLimitExceeded,413		/// Metadata flag frozen414		MetadataFlagFrozen,415416		/// Item not exists.417		TokenNotFound,418		/// Item balance not enough.419		TokenValueTooLow,420		/// Requested value more than approved.421		ApprovedValueTooLow,422		/// Tried to approve more than owned423		CantApproveMoreThanOwned,424425		/// Can't transfer tokens to ethereum zero address426		AddressIsZero,427		/// Target collection doesn't supports this operation428		UnsupportedOperation,429430		/// Not sufficient funds to perform action431		NotSufficientFounds,432433		/// User not passed nesting rule434		UserIsNotAllowedToNest,435		/// Only tokens from specific collections may nest tokens under this436		SourceCollectionIsNotAllowedToNest,437438		/// Tried to store more data than allowed in collection field439		CollectionFieldSizeExceeded,440441		/// Tried to store more property data than allowed442		NoSpaceForProperty,443444		/// Tried to store more property keys than allowed445		PropertyLimitReached,446447		/// Property key is too long448		PropertyKeyIsTooLong,449450		/// Only ASCII letters, digits, and '_', '-' are allowed451		InvalidCharacterInPropertyKey,452453		/// Empty property keys are forbidden454		EmptyPropertyKey,455456		/// Tried to access an external collection with an internal API457		CollectionIsExternal,458459		/// Tried to access an internal collection with an external API460		CollectionIsInternal,461	}462463	#[pallet::storage]464	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;465	#[pallet::storage]466	pub type DestroyedCollectionCount<T> =467		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;468469	/// Collection info470	#[pallet::storage]471	pub type CollectionById<T> = StorageMap<472		Hasher = Blake2_128Concat,473		Key = CollectionId,474		Value = Collection<<T as frame_system::Config>::AccountId>,475		QueryKind = OptionQuery,476	>;477478	/// Collection properties479	#[pallet::storage]480	#[pallet::getter(fn collection_properties)]481	pub type CollectionProperties<T> = StorageMap<482		Hasher = Blake2_128Concat,483		Key = CollectionId,484		Value = Properties,485		QueryKind = ValueQuery,486		OnEmpty = up_data_structs::CollectionProperties,487	>;488489	#[pallet::storage]490	#[pallet::getter(fn property_permissions)]491	pub type CollectionPropertyPermissions<T> = StorageMap<492		Hasher = Blake2_128Concat,493		Key = CollectionId,494		Value = PropertiesPermissionMap,495		QueryKind = ValueQuery,496	>;497498	#[pallet::storage]499	pub type AdminAmount<T> = StorageMap<500		Hasher = Blake2_128Concat,501		Key = CollectionId,502		Value = u32,503		QueryKind = ValueQuery,504	>;505506	/// List of collection admins507	#[pallet::storage]508	pub type IsAdmin<T: Config> = StorageNMap<509		Key = (510			Key<Blake2_128Concat, CollectionId>,511			Key<Blake2_128Concat, T::CrossAccountId>,512		),513		Value = bool,514		QueryKind = ValueQuery,515	>;516517	/// Allowlisted collection users518	#[pallet::storage]519	pub type Allowlist<T: Config> = StorageNMap<520		Key = (521			Key<Blake2_128Concat, CollectionId>,522			Key<Blake2_128Concat, T::CrossAccountId>,523		),524		Value = bool,525		QueryKind = ValueQuery,526	>;527528	/// Not used by code, exists only to provide some types to metadata529	#[pallet::storage]530	pub type DummyStorageValue<T: Config> = StorageValue<531		Value = (532			CollectionStats,533			CollectionId,534			TokenId,535			TokenChild,536			PhantomType<(537				TokenData<T::CrossAccountId>,538				RpcCollection<T::AccountId>,539				// RMRK540				RmrkCollectionInfo<T::AccountId>,541				RmrkInstanceInfo<T::AccountId>,542				RmrkResourceInfo,543				RmrkPropertyInfo,544				RmrkBaseInfo<T::AccountId>,545				RmrkPartType,546				RmrkBoundedTheme,547				RmrkNftChild,548			)>,549		),550		QueryKind = OptionQuery,551	>;552553	#[pallet::hooks]554	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {555		fn on_runtime_upgrade() -> Weight {556			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {557				use up_data_structs::{CollectionVersion1, CollectionVersion2};558				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {559					let mut props = Vec::new();560					if !v.offchain_schema.is_empty() {561						props.push(Property {562							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),563							value: v564								.offchain_schema565								.clone()566								.into_inner()567								.try_into()568								.expect("offchain schema too big"),569						});570					}571					if !v.variable_on_chain_schema.is_empty() {572						props.push(Property {573							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),574							value: v575								.variable_on_chain_schema576								.clone()577								.into_inner()578								.try_into()579								.expect("offchain schema too big"),580						});581					}582					if !v.const_on_chain_schema.is_empty() {583						props.push(Property {584							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),585							value: v586								.const_on_chain_schema587								.clone()588								.into_inner()589								.try_into()590								.expect("offchain schema too big"),591						});592					}593					props.push(Property {594						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),595						value: match v.schema_version {596							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),597							SchemaVersion::Unique => b"Unique".as_slice(),598						}599						.to_vec()600						.try_into()601						.unwrap(),602					});603					Self::set_scoped_collection_properties(604						id,605						PropertyScope::None,606						props.into_iter(),607					)608					.expect("existing data larger than properties");609					let mut new = CollectionVersion2::from(v.clone());610					new.permissions.access = Some(v.access);611					new.permissions.mint_mode = Some(v.mint_mode);612					Some(new)613				});614			}615616			0617		}618	}619}620621impl<T: Config> Pallet<T> {622	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens623	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {624		ensure!(625			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,626			<Error<T>>::AddressIsZero627		);628		Ok(())629	}630	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {631		<IsAdmin<T>>::iter_prefix((collection,))632			.map(|(a, _)| a)633			.collect()634	}635	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {636		<Allowlist<T>>::iter_prefix((collection,))637			.map(|(a, _)| a)638			.collect()639	}640	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {641		<Allowlist<T>>::get((collection, user))642	}643	pub fn collection_stats() -> CollectionStats {644		let created = <CreatedCollectionCount<T>>::get();645		let destroyed = <DestroyedCollectionCount<T>>::get();646		CollectionStats {647			created: created.0,648			destroyed: destroyed.0,649			alive: created.0 - destroyed.0,650		}651	}652653	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {654		let collection = <CollectionById<T>>::get(collection);655		if collection.is_none() {656			return None;657		}658659		let collection = collection.unwrap();660		let limits = collection.limits;661		let effective_limits = CollectionLimits {662			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),663			sponsored_data_size: Some(limits.sponsored_data_size()),664			sponsored_data_rate_limit: Some(665				limits666					.sponsored_data_rate_limit667					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),668			),669			token_limit: Some(limits.token_limit()),670			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(671				match collection.mode {672					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,673					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,674					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,675				},676			)),677			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),678			owner_can_transfer: Some(limits.owner_can_transfer()),679			owner_can_destroy: Some(limits.owner_can_destroy()),680			transfers_enabled: Some(limits.transfers_enabled()),681		};682683		Some(effective_limits)684	}685686	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {687		let Collection {688			name,689			description,690			owner,691			mode,692			token_prefix,693			sponsorship,694			limits,695			permissions,696			external_collection,697		} = <CollectionById<T>>::get(collection)?;698699		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)700			.into_iter()701			.map(|(key, permission)| PropertyKeyPermission { key, permission })702			.collect();703704		let properties = <CollectionProperties<T>>::get(collection)705			.into_iter()706			.map(|(key, value)| Property { key, value })707			.collect();708709		let permissions = CollectionPermissions {710			access: Some(permissions.access()),711			mint_mode: Some(permissions.mint_mode()),712			nesting: Some(permissions.nesting().clone()),713		};714715		Some(RpcCollection {716			name: name.into_inner(),717			description: description.into_inner(),718			owner,719			mode,720			token_prefix: token_prefix.into_inner(),721			sponsorship,722			limits,723			permissions,724			token_property_permissions,725			properties,726			read_only: external_collection,727		})728	}729}730731macro_rules! limit_default {732	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{733		$(734			if let Some($new) = $new.$field {735				let $old = $old.$field($($arg)?);736				let _ = $new;737				let _ = $old;738				$check739			} else {740				$new.$field = $old.$field741			}742		)*743	}};744}745macro_rules! limit_default_clone {746	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{747		$(748			if let Some($new) = $new.$field.clone() {749				let $old = $old.$field($($arg)?);750				let _ = $new;751				let _ = $old;752				$check753			} else {754				$new.$field = $old.$field.clone()755			}756		)*757	}};758}759760impl<T: Config> Pallet<T> {761	pub fn init_collection(762		owner: T::CrossAccountId,763		data: CreateCollectionData<T::AccountId>,764		is_external: bool,765	) -> Result<CollectionId, DispatchError> {766		{767			ensure!(768				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,769				Error::<T>::CollectionTokenPrefixLimitExceeded770			);771		}772773		let created_count = <CreatedCollectionCount<T>>::get()774			.0775			.checked_add(1)776			.ok_or(ArithmeticError::Overflow)?;777		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;778		let id = CollectionId(created_count);779780		// bound Total number of collections781		ensure!(782			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,783			<Error<T>>::TotalCollectionsLimitExceeded784		);785786		// =========787788		let collection = Collection {789			owner: owner.as_sub().clone(),790			name: data.name,791			mode: data.mode.clone(),792			description: data.description,793			token_prefix: data.token_prefix,794			sponsorship: data795				.pending_sponsor796				.map(SponsorshipState::Unconfirmed)797				.unwrap_or_default(),798			limits: data799				.limits800				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))801				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,802			permissions: data803				.permissions804				.map(|permissions| {805					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)806				})807				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,808			external_collection: is_external,809		};810811		let mut collection_properties = up_data_structs::CollectionProperties::get();812		collection_properties813			.try_set_from_iter(data.properties.into_iter())814			.map_err(<Error<T>>::from)?;815816		CollectionProperties::<T>::insert(id, collection_properties);817818		let mut token_props_permissions = PropertiesPermissionMap::new();819		token_props_permissions820			.try_set_from_iter(data.token_property_permissions.into_iter())821			.map_err(<Error<T>>::from)?;822823		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);824825		// Take a (non-refundable) deposit of collection creation826		{827			let mut imbalance =828				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();829			imbalance.subsume(830				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(831					&T::TreasuryAccountId::get(),832					T::CollectionCreationPrice::get(),833				),834			);835			<T as Config>::Currency::settle(836				&owner.as_sub(),837				imbalance,838				WithdrawReasons::TRANSFER,839				ExistenceRequirement::KeepAlive,840			)841			.map_err(|_| Error::<T>::NotSufficientFounds)?;842		}843844		<CreatedCollectionCount<T>>::put(created_count);845		<Pallet<T>>::deposit_event(Event::CollectionCreated(846			id,847			data.mode.id(),848			owner.as_sub().clone(),849		));850		<PalletEvm<T>>::deposit_log(851			erc::CollectionHelpersEvents::CollectionCreated {852				owner: *owner.as_eth(),853				collection_id: eth::collection_id_to_address(id),854			}855			.to_log(T::ContractAddress::get()),856		);857		<CollectionById<T>>::insert(id, collection);858		Ok(id)859	}860861	pub fn destroy_collection(862		collection: CollectionHandle<T>,863		sender: &T::CrossAccountId,864	) -> DispatchResult {865		ensure!(866			collection.limits.owner_can_destroy(),867			<Error<T>>::NoPermission,868		);869		collection.check_is_owner(sender)?;870871		let destroyed_collections = <DestroyedCollectionCount<T>>::get()872			.0873			.checked_add(1)874			.ok_or(ArithmeticError::Overflow)?;875876		// =========877878		<DestroyedCollectionCount<T>>::put(destroyed_collections);879		<CollectionById<T>>::remove(collection.id);880		<AdminAmount<T>>::remove(collection.id);881		<IsAdmin<T>>::remove_prefix((collection.id,), None);882		<Allowlist<T>>::remove_prefix((collection.id,), None);883		<CollectionProperties<T>>::remove(collection.id);884885		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));886		Ok(())887	}888889	pub fn set_collection_property(890		collection: &CollectionHandle<T>,891		sender: &T::CrossAccountId,892		property: Property,893	) -> DispatchResult {894		collection.check_is_owner_or_admin(sender)?;895896		CollectionProperties::<T>::try_mutate(collection.id, |properties| {897			let property = property.clone();898			properties.try_set(property.key, property.value)899		})900		.map_err(<Error<T>>::from)?;901902		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));903904		Ok(())905	}906907	pub fn set_scoped_collection_property(908		collection_id: CollectionId,909		scope: PropertyScope,910		property: Property,911	) -> DispatchResult {912		CollectionProperties::<T>::try_mutate(collection_id, |properties| {913			properties.try_scoped_set(scope, property.key, property.value)914		})915		.map_err(<Error<T>>::from)?;916917		Ok(())918	}919920	pub fn set_scoped_collection_properties(921		collection_id: CollectionId,922		scope: PropertyScope,923		properties: impl Iterator<Item = Property>,924	) -> DispatchResult {925		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {926			stored_properties.try_scoped_set_from_iter(scope, properties)927		})928		.map_err(<Error<T>>::from)?;929930		Ok(())931	}932933	#[transactional]934	pub fn set_collection_properties(935		collection: &CollectionHandle<T>,936		sender: &T::CrossAccountId,937		properties: Vec<Property>,938	) -> DispatchResult {939		for property in properties {940			Self::set_collection_property(collection, sender, property)?;941		}942943		Ok(())944	}945946	pub fn delete_collection_property(947		collection: &CollectionHandle<T>,948		sender: &T::CrossAccountId,949		property_key: PropertyKey,950	) -> DispatchResult {951		collection.check_is_owner_or_admin(sender)?;952953		CollectionProperties::<T>::try_mutate(collection.id, |properties| {954			properties.remove(&property_key)955		})956		.map_err(<Error<T>>::from)?;957958		Self::deposit_event(Event::CollectionPropertyDeleted(959			collection.id,960			property_key,961		));962963		Ok(())964	}965966	#[transactional]967	pub fn delete_collection_properties(968		collection: &CollectionHandle<T>,969		sender: &T::CrossAccountId,970		property_keys: Vec<PropertyKey>,971	) -> DispatchResult {972		for key in property_keys {973			Self::delete_collection_property(collection, sender, key)?;974		}975976		Ok(())977	}978979	// For migrations980	pub fn set_property_permission_unchecked(981		collection: CollectionId,982		property_permission: PropertyKeyPermission,983	) -> DispatchResult {984		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {985			permissions.try_set(property_permission.key, property_permission.permission)986		})987		.map_err(<Error<T>>::from)?;988		Ok(())989	}990991	pub fn set_property_permission(992		collection: &CollectionHandle<T>,993		sender: &T::CrossAccountId,994		property_permission: PropertyKeyPermission,995	) -> DispatchResult {996		collection.check_is_owner_or_admin(sender)?;997998		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);999		let current_permission = all_permissions.get(&property_permission.key);1000		if matches![1001			current_permission,1002			Some(PropertyPermission { mutable: false, .. })1003		] {1004			return Err(<Error<T>>::NoPermission.into());1005		}10061007		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1008			let property_permission = property_permission.clone();1009			permissions.try_set(property_permission.key, property_permission.permission)1010		})1011		.map_err(<Error<T>>::from)?;10121013		Self::deposit_event(Event::PropertyPermissionSet(1014			collection.id,1015			property_permission.key,1016		));10171018		Ok(())1019	}10201021	#[transactional]1022	pub fn set_token_property_permissions(1023		collection: &CollectionHandle<T>,1024		sender: &T::CrossAccountId,1025		property_permissions: Vec<PropertyKeyPermission>,1026	) -> DispatchResult {1027		for prop_pemission in property_permissions {1028			Self::set_property_permission(collection, sender, prop_pemission)?;1029		}10301031		Ok(())1032	}10331034	pub fn get_collection_property(1035		collection_id: CollectionId,1036		key: &PropertyKey,1037	) -> Option<PropertyValue> {1038		Self::collection_properties(collection_id).get(key).cloned()1039	}10401041	pub fn bytes_keys_to_property_keys(1042		keys: Vec<Vec<u8>>,1043	) -> Result<Vec<PropertyKey>, DispatchError> {1044		keys.into_iter()1045			.map(|key| -> Result<PropertyKey, DispatchError> {1046				key.try_into()1047					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1048			})1049			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1050	}10511052	pub fn filter_collection_properties(1053		collection_id: CollectionId,1054		keys: Option<Vec<PropertyKey>>,1055	) -> Result<Vec<Property>, DispatchError> {1056		let properties = Self::collection_properties(collection_id);10571058		let properties = keys1059			.map(|keys| {1060				keys.into_iter()1061					.filter_map(|key| {1062						properties.get(&key).map(|value| Property {1063							key,1064							value: value.clone(),1065						})1066					})1067					.collect()1068			})1069			.unwrap_or_else(|| {1070				properties1071					.into_iter()1072					.map(|(key, value)| Property { key, value })1073					.collect()1074			});10751076		Ok(properties)1077	}10781079	pub fn filter_property_permissions(1080		collection_id: CollectionId,1081		keys: Option<Vec<PropertyKey>>,1082	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1083		let permissions = Self::property_permissions(collection_id);10841085		let key_permissions = keys1086			.map(|keys| {1087				keys.into_iter()1088					.filter_map(|key| {1089						permissions1090							.get(&key)1091							.map(|permission| PropertyKeyPermission {1092								key,1093								permission: permission.clone(),1094							})1095					})1096					.collect()1097			})1098			.unwrap_or_else(|| {1099				permissions1100					.into_iter()1101					.map(|(key, permission)| PropertyKeyPermission { key, permission })1102					.collect()1103			});11041105		Ok(key_permissions)1106	}11071108	pub fn toggle_allowlist(1109		collection: &CollectionHandle<T>,1110		sender: &T::CrossAccountId,1111		user: &T::CrossAccountId,1112		allowed: bool,1113	) -> DispatchResult {1114		collection.check_is_owner_or_admin(sender)?;11151116		// =========11171118		if allowed {1119			<Allowlist<T>>::insert((collection.id, user), true);1120		} else {1121			<Allowlist<T>>::remove((collection.id, user));1122		}11231124		Ok(())1125	}11261127	pub fn toggle_admin(1128		collection: &CollectionHandle<T>,1129		sender: &T::CrossAccountId,1130		user: &T::CrossAccountId,1131		admin: bool,1132	) -> DispatchResult {1133		collection.check_is_owner(sender)?;11341135		let was_admin = <IsAdmin<T>>::get((collection.id, user));1136		if was_admin == admin {1137			return Ok(());1138		}1139		let amount = <AdminAmount<T>>::get(collection.id);11401141		if admin {1142			let amount = amount1143				.checked_add(1)1144				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1145			ensure!(1146				amount <= Self::collection_admins_limit(),1147				<Error<T>>::CollectionAdminCountExceeded,1148			);11491150			// =========11511152			<AdminAmount<T>>::insert(collection.id, amount);1153			<IsAdmin<T>>::insert((collection.id, user), true);1154		} else {1155			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1156			<IsAdmin<T>>::remove((collection.id, user));1157		}11581159		Ok(())1160	}11611162	pub fn clamp_limits(1163		mode: CollectionMode,1164		old_limit: &CollectionLimits,1165		mut new_limit: CollectionLimits,1166	) -> Result<CollectionLimits, DispatchError> {1167		let limits = old_limit;1168		limit_default!(old_limit, new_limit,1169			account_token_ownership_limit => ensure!(1170				new_limit <= MAX_TOKEN_OWNERSHIP,1171				<Error<T>>::CollectionLimitBoundsExceeded,1172			),1173			sponsored_data_size => ensure!(1174				new_limit <= CUSTOM_DATA_LIMIT,1175				<Error<T>>::CollectionLimitBoundsExceeded,1176			),11771178			sponsored_data_rate_limit => {},1179			token_limit => ensure!(1180				old_limit >= new_limit && new_limit > 0,1181				<Error<T>>::CollectionTokenLimitExceeded1182			),11831184			sponsor_transfer_timeout(match mode {1185				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1186				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1187				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188			}) => ensure!(1189				new_limit <= MAX_SPONSOR_TIMEOUT,1190				<Error<T>>::CollectionLimitBoundsExceeded,1191			),1192			sponsor_approve_timeout => {},1193			owner_can_transfer => ensure!(1194				!limits.owner_can_transfer_instaled() ||1195				old_limit || !new_limit,1196				<Error<T>>::OwnerPermissionsCantBeReverted,1197			),1198			owner_can_destroy => ensure!(1199				old_limit || !new_limit,1200				<Error<T>>::OwnerPermissionsCantBeReverted,1201			),1202			transfers_enabled => {},1203		);1204		Ok(new_limit)1205	}12061207	pub fn clamp_permissions(1208		_mode: CollectionMode,1209		old_limit: &CollectionPermissions,1210		mut new_limit: CollectionPermissions,1211	) -> Result<CollectionPermissions, DispatchError> {1212		limit_default_clone!(old_limit, new_limit,1213			access => {},1214			mint_mode => {},1215			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1216		);1217		Ok(new_limit)1218	}1219}12201221#[macro_export]1222macro_rules! unsupported {1223	() => {1224		Err(<Error<T>>::UnsupportedOperation.into())1225	};1226}12271228/// Worst cases1229pub trait CommonWeightInfo<CrossAccountId> {1230	fn create_item() -> Weight;1231	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1232	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1233	fn burn_item() -> Weight;1234	fn set_collection_properties(amount: u32) -> Weight;1235	fn delete_collection_properties(amount: u32) -> Weight;1236	fn set_token_properties(amount: u32) -> Weight;1237	fn delete_token_properties(amount: u32) -> Weight;1238	fn set_token_property_permissions(amount: u32) -> Weight;1239	fn transfer() -> Weight;1240	fn approve() -> Weight;1241	fn transfer_from() -> Weight;1242	fn burn_from() -> Weight;12431244	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1245	/// whole users's balance1246	///1247	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1248	fn burn_recursively_self_raw() -> Weight;1249	/// Cost of iterating over `amount` children while burning, without counting child burning itself1250	///1251	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1252	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12531254	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1255		Self::burn_recursively_self_raw()1256			.saturating_mul(max_selfs.max(1) as u64)1257			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1258	}1259}12601261pub trait RefungibleExtensionsWeightInfo {1262	fn repartition() -> Weight;1263}12641265pub trait CommonCollectionOperations<T: Config> {1266	fn create_item(1267		&self,1268		sender: T::CrossAccountId,1269		to: T::CrossAccountId,1270		data: CreateItemData,1271		nesting_budget: &dyn Budget,1272	) -> DispatchResultWithPostInfo;1273	fn create_multiple_items(1274		&self,1275		sender: T::CrossAccountId,1276		to: T::CrossAccountId,1277		data: Vec<CreateItemData>,1278		nesting_budget: &dyn Budget,1279	) -> DispatchResultWithPostInfo;1280	fn create_multiple_items_ex(1281		&self,1282		sender: T::CrossAccountId,1283		data: CreateItemExData<T::CrossAccountId>,1284		nesting_budget: &dyn Budget,1285	) -> DispatchResultWithPostInfo;1286	fn burn_item(1287		&self,1288		sender: T::CrossAccountId,1289		token: TokenId,1290		amount: u128,1291	) -> DispatchResultWithPostInfo;1292	fn burn_item_recursively(1293		&self,1294		sender: T::CrossAccountId,1295		token: TokenId,1296		self_budget: &dyn Budget,1297		breadth_budget: &dyn Budget,1298	) -> DispatchResultWithPostInfo;1299	fn set_collection_properties(1300		&self,1301		sender: T::CrossAccountId,1302		properties: Vec<Property>,1303	) -> DispatchResultWithPostInfo;1304	fn delete_collection_properties(1305		&self,1306		sender: &T::CrossAccountId,1307		property_keys: Vec<PropertyKey>,1308	) -> DispatchResultWithPostInfo;1309	fn set_token_properties(1310		&self,1311		sender: T::CrossAccountId,1312		token_id: TokenId,1313		property: Vec<Property>,1314		nesting_budget: &dyn Budget,1315	) -> DispatchResultWithPostInfo;1316	fn delete_token_properties(1317		&self,1318		sender: T::CrossAccountId,1319		token_id: TokenId,1320		property_keys: Vec<PropertyKey>,1321		nesting_budget: &dyn Budget,1322	) -> DispatchResultWithPostInfo;1323	fn set_token_property_permissions(1324		&self,1325		sender: &T::CrossAccountId,1326		property_permissions: Vec<PropertyKeyPermission>,1327	) -> DispatchResultWithPostInfo;1328	fn transfer(1329		&self,1330		sender: T::CrossAccountId,1331		to: T::CrossAccountId,1332		token: TokenId,1333		amount: u128,1334		nesting_budget: &dyn Budget,1335	) -> DispatchResultWithPostInfo;1336	fn approve(1337		&self,1338		sender: T::CrossAccountId,1339		spender: T::CrossAccountId,1340		token: TokenId,1341		amount: u128,1342	) -> DispatchResultWithPostInfo;1343	fn transfer_from(1344		&self,1345		sender: T::CrossAccountId,1346		from: T::CrossAccountId,1347		to: T::CrossAccountId,1348		token: TokenId,1349		amount: u128,1350		nesting_budget: &dyn Budget,1351	) -> DispatchResultWithPostInfo;1352	fn burn_from(1353		&self,1354		sender: T::CrossAccountId,1355		from: T::CrossAccountId,1356		token: TokenId,1357		amount: u128,1358		nesting_budget: &dyn Budget,1359	) -> DispatchResultWithPostInfo;13601361	fn check_nesting(1362		&self,1363		sender: T::CrossAccountId,1364		from: (CollectionId, TokenId),1365		under: TokenId,1366		nesting_budget: &dyn Budget,1367	) -> DispatchResult;13681369	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13701371	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13721373	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1374	fn collection_tokens(&self) -> Vec<TokenId>;1375	fn token_exists(&self, token: TokenId) -> bool;1376	fn last_token_id(&self) -> TokenId;13771378	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1379	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1380	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1381	/// Amount of unique collection tokens1382	fn total_supply(&self) -> u32;1383	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1384	fn account_balance(&self, account: T::CrossAccountId) -> u32;1385	/// Amount of specific token account have (Applicable to fungible/refungible)1386	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1387	/// Amount of token pieces1388	fn total_pieces(&self, token: TokenId) -> Option<u128>;1389	fn allowance(1390		&self,1391		sender: T::CrossAccountId,1392		spender: T::CrossAccountId,1393		token: TokenId,1394	) -> u128;1395	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1396}13971398pub trait RefungibleExtensions<T>1399where1400	T: Config,1401{1402	fn repartition(1403		&self,1404		owner: &T::CrossAccountId,1405		token: TokenId,1406		amount: u128,1407	) -> DispatchResultWithPostInfo;1408}14091410// Flexible enough for implementing CommonCollectionOperations1411pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1412	let post_info = PostDispatchInfo {1413		actual_weight: Some(weight),1414		pays_fee: Pays::Yes,1415	};1416	match res {1417		Ok(()) => Ok(post_info),1418		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1419	}1420}14211422impl<T: Config> From<PropertiesError> for Error<T> {1423	fn from(error: PropertiesError) -> Self {1424		match error {1425			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1426			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1427			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1428			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1429			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1430		}1431	}1432}
after · pallets/common/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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkBoundedTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> DispatchResult {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///293		/// * mode: [CollectionMode] converted into u8.294		///295		/// * account_id: Collection owner.296		CollectionCreated(CollectionId, u8, T::AccountId),297298		/// New collection was destroyed299		///300		/// # Arguments301		///302		/// * collection_id: Globally unique identifier of collection that has been destroyed.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: ID of the collection where the item was created.310		///311		/// * item_id: ID of the item. Unique within the collection.312		///313		/// * recipient: Owner of the newly created item.314		///315		/// * amount: The amount of tokens that were created (always 1 for NFT).316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id: Identifier of the collection to which the burned NFT belonged.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: Which user has destroyed their tokens.327		///328		/// * amount: The amount of tokens that were destroyed (always 1 for NFT).329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred.332		/// 333		/// # Arguments334		///335		/// * collection_id: ID of the collection to which the item belongs.336		///337		/// * item_id: ID of the item trasnferred.338		///339		/// * sender: Original owner of the item.340		///341		/// * recipient: New owner of the item.342		///343		/// * amount: The amount of tokens that were transferred (always 1 for NFT).344		Transfer(345			CollectionId,346			TokenId,347			T::CrossAccountId,348			T::CrossAccountId,349			u128,350		),351352		/// Sponsoring allowance was approved.353		/// 354		/// # Arguments355		/// 356		/// * collection_id357		///358		/// * item_id359		///360		/// * sender361		///362		/// * spender363		///364		/// * amount365		Approved(366			CollectionId,367			TokenId,368			T::CrossAccountId,369			T::CrossAccountId,370			u128,371		),372373		/// Collection property was added or edited.374		/// 375		/// # Arguments376		/// 377		/// * collection_id: ID of the collection, whose property was just set.378		/// 379		/// * property_key: Key of the property that was just set.380		CollectionPropertySet(CollectionId, PropertyKey),381382		/// Collection property was deleted.383		/// 384		/// # Arguments385		/// 386		/// * collection_id: ID of the collection, whose property was just deleted.387		/// 388		/// * property_key: Key of the property that was just deleted.389		CollectionPropertyDeleted(CollectionId, PropertyKey),390391		/// Item property was added or edited.392		/// 393		/// # Arguments394		/// 395		/// * collection_id: ID of the collection, whose token's property was just set.396		/// 397		/// * item_id: ID of the item, whose property was just set.398		/// 399		/// * property_key: Key of the property that was just set.400		TokenPropertySet(CollectionId, TokenId, PropertyKey),401402		/// Item property was deleted.403		/// 404		/// # Arguments405		/// 406		/// * collection_id: ID of the collection, whose token's property was just deleted.407		/// 408		/// * item_id: ID of the item, whose property was just deleted.409		/// 410		/// * property_key: Key of the property that was just deleted.411		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413		/// Token property permission was added or updated for a collection.414		/// 415		/// # Arguments416		/// 417		/// * collection_id: ID of the collection, whose permissions were just set/updated.418		/// 419		/// * property_key: Key of the property of the set/updated permission.420		PropertyPermissionSet(CollectionId, PropertyKey),421	}422423	#[pallet::error]424	pub enum Error<T> {425		/// This collection does not exist.426		CollectionNotFound,427		/// Sender parameter and item owner must be equal.428		MustBeTokenOwner,429		/// No permission to perform action430		NoPermission,431		/// Destroying only empty collections is allowed432		CantDestroyNotEmptyCollection,433		/// Collection is not in mint mode.434		PublicMintingNotAllowed,435		/// Address is not in allow list.436		AddressNotInAllowlist,437438		/// Collection name can not be longer than 63 char.439		CollectionNameLimitExceeded,440		/// Collection description can not be longer than 255 char.441		CollectionDescriptionLimitExceeded,442		/// Token prefix can not be longer than 15 char.443		CollectionTokenPrefixLimitExceeded,444		/// Total collections bound exceeded.445		TotalCollectionsLimitExceeded,446		/// Exceeded max admin count447		CollectionAdminCountExceeded,448		/// Collection limit bounds per collection exceeded449		CollectionLimitBoundsExceeded,450		/// Tried to enable permissions which are only permitted to be disabled451		OwnerPermissionsCantBeReverted,452		/// Collection settings not allowing items transferring453		TransferNotAllowed,454		/// Account token limit exceeded per collection455		AccountTokenLimitExceeded,456		/// Collection token limit exceeded457		CollectionTokenLimitExceeded,458		/// Metadata flag frozen459		MetadataFlagFrozen,460461		/// Item does not exist462		TokenNotFound,463		/// Item is balance not enough464		TokenValueTooLow,465		/// Requested value is more than the approved466		ApprovedValueTooLow,467		/// Tried to approve more than owned468		CantApproveMoreThanOwned,469470		/// Can't transfer tokens to ethereum zero address471		AddressIsZero,472		/// Target collection doesn't support this operation473		UnsupportedOperation,474475		/// Insufficient funds to perform an action476		NotSufficientFounds,477478		/// User does not satisfy the nesting rule479		UserIsNotAllowedToNest,480		/// Only tokens from specific collections may nest tokens under this one481		SourceCollectionIsNotAllowedToNest,482483		/// Tried to store more data than allowed in collection field484		CollectionFieldSizeExceeded,485486		/// Tried to store more property data than allowed487		NoSpaceForProperty,488489		/// Tried to store more property keys than allowed490		PropertyLimitReached,491492		/// Property key is too long493		PropertyKeyIsTooLong,494495		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed496		InvalidCharacterInPropertyKey,497498		/// Empty property keys are forbidden499		EmptyPropertyKey,500501		/// Tried to access an external collection with an internal API502		CollectionIsExternal,503504		/// Tried to access an internal collection with an external API505		CollectionIsInternal,506	}507508	/// The number of created collections. Essentially contains the last collection ID.509	#[pallet::storage]510	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512	/// The number of destroyed collections513	#[pallet::storage]514	pub type DestroyedCollectionCount<T> =515		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517	/// Collection info518	#[pallet::storage]519	pub type CollectionById<T> = StorageMap<520		Hasher = Blake2_128Concat,521		Key = CollectionId,522		Value = Collection<<T as frame_system::Config>::AccountId>,523		QueryKind = OptionQuery,524	>;525526	/// Collection properties527	#[pallet::storage]528	#[pallet::getter(fn collection_properties)]529	pub type CollectionProperties<T> = StorageMap<530		Hasher = Blake2_128Concat,531		Key = CollectionId,532		Value = Properties,533		QueryKind = ValueQuery,534		OnEmpty = up_data_structs::CollectionProperties,535	>;536537	/// Token permissions of a collection538	#[pallet::storage]539	#[pallet::getter(fn property_permissions)]540	pub type CollectionPropertyPermissions<T> = StorageMap<541		Hasher = Blake2_128Concat,542		Key = CollectionId,543		Value = PropertiesPermissionMap,544		QueryKind = ValueQuery,545	>;546547	/// Amount of collection admins548	#[pallet::storage]549	pub type AdminAmount<T> = StorageMap<550		Hasher = Blake2_128Concat,551		Key = CollectionId,552		Value = u32,553		QueryKind = ValueQuery,554	>;555556	/// List of collection admins557	#[pallet::storage]558	pub type IsAdmin<T: Config> = StorageNMap<559		Key = (560			Key<Blake2_128Concat, CollectionId>,561			Key<Blake2_128Concat, T::CrossAccountId>,562		),563		Value = bool,564		QueryKind = ValueQuery,565	>;566567	/// Allowlisted collection users568	#[pallet::storage]569	pub type Allowlist<T: Config> = StorageNMap<570		Key = (571			Key<Blake2_128Concat, CollectionId>,572			Key<Blake2_128Concat, T::CrossAccountId>,573		),574		Value = bool,575		QueryKind = ValueQuery,576	>;577578	/// Not used by code, exists only to provide some types to metadata579	#[pallet::storage]580	pub type DummyStorageValue<T: Config> = StorageValue<581		Value = (582			CollectionStats,583			CollectionId,584			TokenId,585			TokenChild,586			PhantomType<(587				TokenData<T::CrossAccountId>,588				RpcCollection<T::AccountId>,589				// RMRK590				RmrkCollectionInfo<T::AccountId>,591				RmrkInstanceInfo<T::AccountId>,592				RmrkResourceInfo,593				RmrkPropertyInfo,594				RmrkBaseInfo<T::AccountId>,595				RmrkPartType,596				RmrkBoundedTheme,597				RmrkNftChild,598			)>,599		),600		QueryKind = OptionQuery,601	>;602603	#[pallet::hooks]604	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605		fn on_runtime_upgrade() -> Weight {606			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607				use up_data_structs::{CollectionVersion1, CollectionVersion2};608				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609					let mut props = Vec::new();610					if !v.offchain_schema.is_empty() {611						props.push(Property {612							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613							value: v614								.offchain_schema615								.clone()616								.into_inner()617								.try_into()618								.expect("offchain schema too big"),619						});620					}621					if !v.variable_on_chain_schema.is_empty() {622						props.push(Property {623							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624							value: v625								.variable_on_chain_schema626								.clone()627								.into_inner()628								.try_into()629								.expect("offchain schema too big"),630						});631					}632					if !v.const_on_chain_schema.is_empty() {633						props.push(Property {634							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635							value: v636								.const_on_chain_schema637								.clone()638								.into_inner()639								.try_into()640								.expect("offchain schema too big"),641						});642					}643					props.push(Property {644						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645						value: match v.schema_version {646							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647							SchemaVersion::Unique => b"Unique".as_slice(),648						}649						.to_vec()650						.try_into()651						.unwrap(),652					});653					Self::set_scoped_collection_properties(654						id,655						PropertyScope::None,656						props.into_iter(),657					)658					.expect("existing data larger than properties");659					let mut new = CollectionVersion2::from(v.clone());660					new.permissions.access = Some(v.access);661					new.permissions.mint_mode = Some(v.mint_mode);662					Some(new)663				});664			}665666			0667		}668	}669}670671impl<T: Config> Pallet<T> {672	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens673	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674		ensure!(675			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676			<Error<T>>::AddressIsZero677		);678		Ok(())679	}680	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681		<IsAdmin<T>>::iter_prefix((collection,))682			.map(|(a, _)| a)683			.collect()684	}685	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686		<Allowlist<T>>::iter_prefix((collection,))687			.map(|(a, _)| a)688			.collect()689	}690	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691		<Allowlist<T>>::get((collection, user))692	}693	pub fn collection_stats() -> CollectionStats {694		let created = <CreatedCollectionCount<T>>::get();695		let destroyed = <DestroyedCollectionCount<T>>::get();696		CollectionStats {697			created: created.0,698			destroyed: destroyed.0,699			alive: created.0 - destroyed.0,700		}701	}702703	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704		let collection = <CollectionById<T>>::get(collection);705		if collection.is_none() {706			return None;707		}708709		let collection = collection.unwrap();710		let limits = collection.limits;711		let effective_limits = CollectionLimits {712			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713			sponsored_data_size: Some(limits.sponsored_data_size()),714			sponsored_data_rate_limit: Some(715				limits716					.sponsored_data_rate_limit717					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),718			),719			token_limit: Some(limits.token_limit()),720			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721				match collection.mode {722					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725				},726			)),727			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728			owner_can_transfer: Some(limits.owner_can_transfer()),729			owner_can_destroy: Some(limits.owner_can_destroy()),730			transfers_enabled: Some(limits.transfers_enabled()),731		};732733		Some(effective_limits)734	}735736	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737		let Collection {738			name,739			description,740			owner,741			mode,742			token_prefix,743			sponsorship,744			limits,745			permissions,746			external_collection,747		} = <CollectionById<T>>::get(collection)?;748749		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750			.into_iter()751			.map(|(key, permission)| PropertyKeyPermission { key, permission })752			.collect();753754		let properties = <CollectionProperties<T>>::get(collection)755			.into_iter()756			.map(|(key, value)| Property { key, value })757			.collect();758759		let permissions = CollectionPermissions {760			access: Some(permissions.access()),761			mint_mode: Some(permissions.mint_mode()),762			nesting: Some(permissions.nesting().clone()),763		};764765		Some(RpcCollection {766			name: name.into_inner(),767			description: description.into_inner(),768			owner,769			mode,770			token_prefix: token_prefix.into_inner(),771			sponsorship,772			limits,773			permissions,774			token_property_permissions,775			properties,776			read_only: external_collection,777		})778	}779}780781macro_rules! limit_default {782	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783		$(784			if let Some($new) = $new.$field {785				let $old = $old.$field($($arg)?);786				let _ = $new;787				let _ = $old;788				$check789			} else {790				$new.$field = $old.$field791			}792		)*793	}};794}795macro_rules! limit_default_clone {796	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797		$(798			if let Some($new) = $new.$field.clone() {799				let $old = $old.$field($($arg)?);800				let _ = $new;801				let _ = $old;802				$check803			} else {804				$new.$field = $old.$field.clone()805			}806		)*807	}};808}809810impl<T: Config> Pallet<T> {811	pub fn init_collection(812		owner: T::CrossAccountId,813		data: CreateCollectionData<T::AccountId>,814		is_external: bool,815	) -> Result<CollectionId, DispatchError> {816		{817			ensure!(818				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819				Error::<T>::CollectionTokenPrefixLimitExceeded820			);821		}822823		let created_count = <CreatedCollectionCount<T>>::get()824			.0825			.checked_add(1)826			.ok_or(ArithmeticError::Overflow)?;827		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828		let id = CollectionId(created_count);829830		// bound Total number of collections831		ensure!(832			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833			<Error<T>>::TotalCollectionsLimitExceeded834		);835836		// =========837838		let collection = Collection {839			owner: owner.as_sub().clone(),840			name: data.name,841			mode: data.mode.clone(),842			description: data.description,843			token_prefix: data.token_prefix,844			sponsorship: data845				.pending_sponsor846				.map(SponsorshipState::Unconfirmed)847				.unwrap_or_default(),848			limits: data849				.limits850				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,852			permissions: data853				.permissions854				.map(|permissions| {855					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856				})857				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858			external_collection: is_external,859		};860861		let mut collection_properties = up_data_structs::CollectionProperties::get();862		collection_properties863			.try_set_from_iter(data.properties.into_iter())864			.map_err(<Error<T>>::from)?;865866		CollectionProperties::<T>::insert(id, collection_properties);867868		let mut token_props_permissions = PropertiesPermissionMap::new();869		token_props_permissions870			.try_set_from_iter(data.token_property_permissions.into_iter())871			.map_err(<Error<T>>::from)?;872873		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875		// Take a (non-refundable) deposit of collection creation876		{877			let mut imbalance =878				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879			imbalance.subsume(880				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881					&T::TreasuryAccountId::get(),882					T::CollectionCreationPrice::get(),883				),884			);885			<T as Config>::Currency::settle(886				&owner.as_sub(),887				imbalance,888				WithdrawReasons::TRANSFER,889				ExistenceRequirement::KeepAlive,890			)891			.map_err(|_| Error::<T>::NotSufficientFounds)?;892		}893894		<CreatedCollectionCount<T>>::put(created_count);895		<Pallet<T>>::deposit_event(Event::CollectionCreated(896			id,897			data.mode.id(),898			owner.as_sub().clone(),899		));900		<PalletEvm<T>>::deposit_log(901			erc::CollectionHelpersEvents::CollectionCreated {902				owner: *owner.as_eth(),903				collection_id: eth::collection_id_to_address(id),904			}905			.to_log(T::ContractAddress::get()),906		);907		<CollectionById<T>>::insert(id, collection);908		Ok(id)909	}910911	pub fn destroy_collection(912		collection: CollectionHandle<T>,913		sender: &T::CrossAccountId,914	) -> DispatchResult {915		ensure!(916			collection.limits.owner_can_destroy(),917			<Error<T>>::NoPermission,918		);919		collection.check_is_owner(sender)?;920921		let destroyed_collections = <DestroyedCollectionCount<T>>::get()922			.0923			.checked_add(1)924			.ok_or(ArithmeticError::Overflow)?;925926		// =========927928		<DestroyedCollectionCount<T>>::put(destroyed_collections);929		<CollectionById<T>>::remove(collection.id);930		<AdminAmount<T>>::remove(collection.id);931		<IsAdmin<T>>::remove_prefix((collection.id,), None);932		<Allowlist<T>>::remove_prefix((collection.id,), None);933		<CollectionProperties<T>>::remove(collection.id);934935		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936		Ok(())937	}938939	pub fn set_collection_property(940		collection: &CollectionHandle<T>,941		sender: &T::CrossAccountId,942		property: Property,943	) -> DispatchResult {944		collection.check_is_owner_or_admin(sender)?;945946		CollectionProperties::<T>::try_mutate(collection.id, |properties| {947			let property = property.clone();948			properties.try_set(property.key, property.value)949		})950		.map_err(<Error<T>>::from)?;951952		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954		Ok(())955	}956957	pub fn set_scoped_collection_property(958		collection_id: CollectionId,959		scope: PropertyScope,960		property: Property,961	) -> DispatchResult {962		CollectionProperties::<T>::try_mutate(collection_id, |properties| {963			properties.try_scoped_set(scope, property.key, property.value)964		})965		.map_err(<Error<T>>::from)?;966967		Ok(())968	}969970	pub fn set_scoped_collection_properties(971		collection_id: CollectionId,972		scope: PropertyScope,973		properties: impl Iterator<Item = Property>,974	) -> DispatchResult {975		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976			stored_properties.try_scoped_set_from_iter(scope, properties)977		})978		.map_err(<Error<T>>::from)?;979980		Ok(())981	}982983	#[transactional]984	pub fn set_collection_properties(985		collection: &CollectionHandle<T>,986		sender: &T::CrossAccountId,987		properties: Vec<Property>,988	) -> DispatchResult {989		for property in properties {990			Self::set_collection_property(collection, sender, property)?;991		}992993		Ok(())994	}995996	pub fn delete_collection_property(997		collection: &CollectionHandle<T>,998		sender: &T::CrossAccountId,999		property_key: PropertyKey,1000	) -> DispatchResult {1001		collection.check_is_owner_or_admin(sender)?;10021003		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004			properties.remove(&property_key)1005		})1006		.map_err(<Error<T>>::from)?;10071008		Self::deposit_event(Event::CollectionPropertyDeleted(1009			collection.id,1010			property_key,1011		));10121013		Ok(())1014	}10151016	#[transactional]1017	pub fn delete_collection_properties(1018		collection: &CollectionHandle<T>,1019		sender: &T::CrossAccountId,1020		property_keys: Vec<PropertyKey>,1021	) -> DispatchResult {1022		for key in property_keys {1023			Self::delete_collection_property(collection, sender, key)?;1024		}10251026		Ok(())1027	}10281029	// For migrations1030	pub fn set_property_permission_unchecked(1031		collection: CollectionId,1032		property_permission: PropertyKeyPermission,1033	) -> DispatchResult {1034		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035			permissions.try_set(property_permission.key, property_permission.permission)1036		})1037		.map_err(<Error<T>>::from)?;1038		Ok(())1039	}10401041	pub fn set_property_permission(1042		collection: &CollectionHandle<T>,1043		sender: &T::CrossAccountId,1044		property_permission: PropertyKeyPermission,1045	) -> DispatchResult {1046		collection.check_is_owner_or_admin(sender)?;10471048		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049		let current_permission = all_permissions.get(&property_permission.key);1050		if matches![1051			current_permission,1052			Some(PropertyPermission { mutable: false, .. })1053		] {1054			return Err(<Error<T>>::NoPermission.into());1055		}10561057		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058			let property_permission = property_permission.clone();1059			permissions.try_set(property_permission.key, property_permission.permission)1060		})1061		.map_err(<Error<T>>::from)?;10621063		Self::deposit_event(Event::PropertyPermissionSet(1064			collection.id,1065			property_permission.key,1066		));10671068		Ok(())1069	}10701071	#[transactional]1072	pub fn set_token_property_permissions(1073		collection: &CollectionHandle<T>,1074		sender: &T::CrossAccountId,1075		property_permissions: Vec<PropertyKeyPermission>,1076	) -> DispatchResult {1077		for prop_pemission in property_permissions {1078			Self::set_property_permission(collection, sender, prop_pemission)?;1079		}10801081		Ok(())1082	}10831084	pub fn get_collection_property(1085		collection_id: CollectionId,1086		key: &PropertyKey,1087	) -> Option<PropertyValue> {1088		Self::collection_properties(collection_id).get(key).cloned()1089	}10901091	pub fn bytes_keys_to_property_keys(1092		keys: Vec<Vec<u8>>,1093	) -> Result<Vec<PropertyKey>, DispatchError> {1094		keys.into_iter()1095			.map(|key| -> Result<PropertyKey, DispatchError> {1096				key.try_into()1097					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098			})1099			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1100	}11011102	pub fn filter_collection_properties(1103		collection_id: CollectionId,1104		keys: Option<Vec<PropertyKey>>,1105	) -> Result<Vec<Property>, DispatchError> {1106		let properties = Self::collection_properties(collection_id);11071108		let properties = keys1109			.map(|keys| {1110				keys.into_iter()1111					.filter_map(|key| {1112						properties.get(&key).map(|value| Property {1113							key,1114							value: value.clone(),1115						})1116					})1117					.collect()1118			})1119			.unwrap_or_else(|| {1120				properties1121					.into_iter()1122					.map(|(key, value)| Property { key, value })1123					.collect()1124			});11251126		Ok(properties)1127	}11281129	pub fn filter_property_permissions(1130		collection_id: CollectionId,1131		keys: Option<Vec<PropertyKey>>,1132	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133		let permissions = Self::property_permissions(collection_id);11341135		let key_permissions = keys1136			.map(|keys| {1137				keys.into_iter()1138					.filter_map(|key| {1139						permissions1140							.get(&key)1141							.map(|permission| PropertyKeyPermission {1142								key,1143								permission: permission.clone(),1144							})1145					})1146					.collect()1147			})1148			.unwrap_or_else(|| {1149				permissions1150					.into_iter()1151					.map(|(key, permission)| PropertyKeyPermission { key, permission })1152					.collect()1153			});11541155		Ok(key_permissions)1156	}11571158	pub fn toggle_allowlist(1159		collection: &CollectionHandle<T>,1160		sender: &T::CrossAccountId,1161		user: &T::CrossAccountId,1162		allowed: bool,1163	) -> DispatchResult {1164		collection.check_is_owner_or_admin(sender)?;11651166		// =========11671168		if allowed {1169			<Allowlist<T>>::insert((collection.id, user), true);1170		} else {1171			<Allowlist<T>>::remove((collection.id, user));1172		}11731174		Ok(())1175	}11761177	pub fn toggle_admin(1178		collection: &CollectionHandle<T>,1179		sender: &T::CrossAccountId,1180		user: &T::CrossAccountId,1181		admin: bool,1182	) -> DispatchResult {1183		collection.check_is_owner(sender)?;11841185		let was_admin = <IsAdmin<T>>::get((collection.id, user));1186		if was_admin == admin {1187			return Ok(());1188		}1189		let amount = <AdminAmount<T>>::get(collection.id);11901191		if admin {1192			let amount = amount1193				.checked_add(1)1194				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195			ensure!(1196				amount <= Self::collection_admins_limit(),1197				<Error<T>>::CollectionAdminCountExceeded,1198			);11991200			// =========12011202			<AdminAmount<T>>::insert(collection.id, amount);1203			<IsAdmin<T>>::insert((collection.id, user), true);1204		} else {1205			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206			<IsAdmin<T>>::remove((collection.id, user));1207		}12081209		Ok(())1210	}12111212	pub fn clamp_limits(1213		mode: CollectionMode,1214		old_limit: &CollectionLimits,1215		mut new_limit: CollectionLimits,1216	) -> Result<CollectionLimits, DispatchError> {1217		let limits = old_limit;1218		limit_default!(old_limit, new_limit,1219			account_token_ownership_limit => ensure!(1220				new_limit <= MAX_TOKEN_OWNERSHIP,1221				<Error<T>>::CollectionLimitBoundsExceeded,1222			),1223			sponsored_data_size => ensure!(1224				new_limit <= CUSTOM_DATA_LIMIT,1225				<Error<T>>::CollectionLimitBoundsExceeded,1226			),12271228			sponsored_data_rate_limit => {},1229			token_limit => ensure!(1230				old_limit >= new_limit && new_limit > 0,1231				<Error<T>>::CollectionTokenLimitExceeded1232			),12331234			sponsor_transfer_timeout(match mode {1235				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238			}) => ensure!(1239				new_limit <= MAX_SPONSOR_TIMEOUT,1240				<Error<T>>::CollectionLimitBoundsExceeded,1241			),1242			sponsor_approve_timeout => {},1243			owner_can_transfer => ensure!(1244				!limits.owner_can_transfer_instaled() ||1245				old_limit || !new_limit,1246				<Error<T>>::OwnerPermissionsCantBeReverted,1247			),1248			owner_can_destroy => ensure!(1249				old_limit || !new_limit,1250				<Error<T>>::OwnerPermissionsCantBeReverted,1251			),1252			transfers_enabled => {},1253		);1254		Ok(new_limit)1255	}12561257	pub fn clamp_permissions(1258		_mode: CollectionMode,1259		old_limit: &CollectionPermissions,1260		mut new_limit: CollectionPermissions,1261	) -> Result<CollectionPermissions, DispatchError> {1262		limit_default_clone!(old_limit, new_limit,1263			access => {},1264			mint_mode => {},1265			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1266		);1267		Ok(new_limit)1268	}1269}12701271#[macro_export]1272macro_rules! unsupported {1273	() => {1274		Err(<Error<T>>::UnsupportedOperation.into())1275	};1276}12771278/// Worst cases1279pub trait CommonWeightInfo<CrossAccountId> {1280	fn create_item() -> Weight;1281	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283	fn burn_item() -> Weight;1284	fn set_collection_properties(amount: u32) -> Weight;1285	fn delete_collection_properties(amount: u32) -> Weight;1286	fn set_token_properties(amount: u32) -> Weight;1287	fn delete_token_properties(amount: u32) -> Weight;1288	fn set_token_property_permissions(amount: u32) -> Weight;1289	fn transfer() -> Weight;1290	fn approve() -> Weight;1291	fn transfer_from() -> Weight;1292	fn burn_from() -> Weight;12931294	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1295	/// whole users's balance1296	///1297	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1298	fn burn_recursively_self_raw() -> Weight;1299	/// Cost of iterating over `amount` children while burning, without counting child burning itself1300	///1301	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1302	fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305		Self::burn_recursively_self_raw()1306			.saturating_mul(max_selfs.max(1) as u64)1307			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308	}1309}13101311pub trait RefungibleExtensionsWeightInfo {1312	fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316	fn create_item(1317		&self,1318		sender: T::CrossAccountId,1319		to: T::CrossAccountId,1320		data: CreateItemData,1321		nesting_budget: &dyn Budget,1322	) -> DispatchResultWithPostInfo;1323	fn create_multiple_items(1324		&self,1325		sender: T::CrossAccountId,1326		to: T::CrossAccountId,1327		data: Vec<CreateItemData>,1328		nesting_budget: &dyn Budget,1329	) -> DispatchResultWithPostInfo;1330	fn create_multiple_items_ex(1331		&self,1332		sender: T::CrossAccountId,1333		data: CreateItemExData<T::CrossAccountId>,1334		nesting_budget: &dyn Budget,1335	) -> DispatchResultWithPostInfo;1336	fn burn_item(1337		&self,1338		sender: T::CrossAccountId,1339		token: TokenId,1340		amount: u128,1341	) -> DispatchResultWithPostInfo;1342	fn burn_item_recursively(1343		&self,1344		sender: T::CrossAccountId,1345		token: TokenId,1346		self_budget: &dyn Budget,1347		breadth_budget: &dyn Budget,1348	) -> DispatchResultWithPostInfo;1349	fn set_collection_properties(1350		&self,1351		sender: T::CrossAccountId,1352		properties: Vec<Property>,1353	) -> DispatchResultWithPostInfo;1354	fn delete_collection_properties(1355		&self,1356		sender: &T::CrossAccountId,1357		property_keys: Vec<PropertyKey>,1358	) -> DispatchResultWithPostInfo;1359	fn set_token_properties(1360		&self,1361		sender: T::CrossAccountId,1362		token_id: TokenId,1363		property: Vec<Property>,1364		nesting_budget: &dyn Budget,1365	) -> DispatchResultWithPostInfo;1366	fn delete_token_properties(1367		&self,1368		sender: T::CrossAccountId,1369		token_id: TokenId,1370		property_keys: Vec<PropertyKey>,1371		nesting_budget: &dyn Budget,1372	) -> DispatchResultWithPostInfo;1373	fn set_token_property_permissions(1374		&self,1375		sender: &T::CrossAccountId,1376		property_permissions: Vec<PropertyKeyPermission>,1377	) -> DispatchResultWithPostInfo;1378	fn transfer(1379		&self,1380		sender: T::CrossAccountId,1381		to: T::CrossAccountId,1382		token: TokenId,1383		amount: u128,1384		nesting_budget: &dyn Budget,1385	) -> DispatchResultWithPostInfo;1386	fn approve(1387		&self,1388		sender: T::CrossAccountId,1389		spender: T::CrossAccountId,1390		token: TokenId,1391		amount: u128,1392	) -> DispatchResultWithPostInfo;1393	fn transfer_from(1394		&self,1395		sender: T::CrossAccountId,1396		from: T::CrossAccountId,1397		to: T::CrossAccountId,1398		token: TokenId,1399		amount: u128,1400		nesting_budget: &dyn Budget,1401	) -> DispatchResultWithPostInfo;1402	fn burn_from(1403		&self,1404		sender: T::CrossAccountId,1405		from: T::CrossAccountId,1406		token: TokenId,1407		amount: u128,1408		nesting_budget: &dyn Budget,1409	) -> DispatchResultWithPostInfo;14101411	fn check_nesting(1412		&self,1413		sender: T::CrossAccountId,1414		from: (CollectionId, TokenId),1415		under: TokenId,1416		nesting_budget: &dyn Budget,1417	) -> DispatchResult;14181419	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424	fn collection_tokens(&self) -> Vec<TokenId>;1425	fn token_exists(&self, token: TokenId) -> bool;1426	fn last_token_id(&self) -> TokenId;14271428	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431	/// Amount of unique collection tokens1432	fn total_supply(&self) -> u32;1433	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1434	fn account_balance(&self, account: T::CrossAccountId) -> u32;1435	/// Amount of specific token account have (Applicable to fungible/refungible)1436	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437	/// Amount of token pieces1438	fn total_pieces(&self, token: TokenId) -> Option<u128>;1439	fn allowance(1440		&self,1441		sender: T::CrossAccountId,1442		spender: T::CrossAccountId,1443		token: TokenId,1444	) -> u128;1445	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450	T: Config,1451{1452	fn repartition(1453		&self,1454		owner: &T::CrossAccountId,1455		token: TokenId,1456		amount: u128,1457	) -> DispatchResultWithPostInfo;1458}14591460// Flexible enough for implementing CommonCollectionOperations1461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462	let post_info = PostDispatchInfo {1463		actual_weight: Some(weight),1464		pays_fee: Pays::Yes,1465	};1466	match res {1467		Ok(()) => Ok(post_info),1468		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469	}1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473	fn from(error: PropertiesError) -> Self {1474		match error {1475			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480		}1481	}1482}
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
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -56,6 +56,8 @@
 pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
+/// Token data, stored independently from other data used to describe it.
+/// Notably contains the owner account address.
 #[struct_versioning::versioned(version = 2, upper)]
 #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
 pub struct ItemData<CrossAccountId> {
@@ -102,13 +104,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 burnt tokens 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>),
@@ -116,6 +122,7 @@
 		QueryKind = OptionQuery,
 	>;
 
+	/// Key-value pairs, describing the metadata of a token.
 	#[pallet::storage]
 	#[pallet::getter(fn token_properties)]
 	pub type TokenProperties<T: Config> = StorageNMap<
@@ -125,6 +132,7 @@
 		OnEmpty = up_data_structs::TokenProperties,
 	>;
 
+	/// Scoped, auxiliary properties of a token, primarily used for on-chain operations.
 	#[pallet::storage]
 	#[pallet::getter(fn token_aux_property)]
 	pub type TokenAuxProperties<T: Config> = StorageNMap<
@@ -138,7 +146,7 @@
 		QueryKind = OptionQuery,
 	>;
 
-	/// Used to enumerate tokens owned by account
+	/// Used to enumerate tokens owned by account.
 	#[pallet::storage]
 	pub type Owned<T: Config> = StorageNMap<
 		Key = (
@@ -150,7 +158,7 @@
 		QueryKind = ValueQuery,
 	>;
 
-	/// Used to enumerate token's children
+	/// Used to enumerate token's children.
 	#[pallet::storage]
 	#[pallet::getter(fn token_children)]
 	pub type TokenChildren<T: Config> = StorageNMap<
@@ -163,6 +171,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Amount of tokens owned in a collection.s
 	#[pallet::storage]
 	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
@@ -173,6 +182,7 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// todo doc
 	#[pallet::storage]
 	pub type Allowance<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -180,6 +190,7 @@
 		QueryKind = OptionQuery,
 	>;
 
+	/// Upgrade from the old schema to properties.
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
 		fn on_runtime_upgrade() -> Weight {
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>'),