git.delta.rocks / unique-network / refs/commits / 9b4da675e61d

difftreelog

doc: minor fixes + typos

Farhad Hakimov2022-07-11parent: #6035f8d.patch.diff
in: master

7 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -50,6 +50,7 @@
 		account: CrossAccountId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenId>>;
+
 	/// Get tokens contained in collection
 	#[method(name = "unique_collectionTokens")]
 	fn collection_tokens(
@@ -57,6 +58,7 @@
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenId>>;
+
 	/// Check if token exists
 	#[method(name = "unique_tokenExists")]
 	fn token_exists(
@@ -65,6 +67,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<bool>;
+
 	/// Get token owner
 	#[method(name = "unique_tokenOwner")]
 	fn token_owner(
@@ -73,6 +76,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(
@@ -81,6 +85,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CrossAccountId>>;
+
 	/// Get tokens nested directly into the token
 	#[method(name = "unique_tokenChildren")]
 	fn token_children(
@@ -89,6 +94,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<TokenChild>>;
+
 	/// Get collection properties
 	#[method(name = "unique_collectionProperties")]
 	fn collection_properties(
@@ -97,6 +103,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<Property>>;
+
 	/// Get token properties
 	#[method(name = "unique_tokenProperties")]
 	fn token_properties(
@@ -106,6 +113,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<Property>>;
+
 	/// Get property permissions
 	#[method(name = "unique_propertyPermissions")]
 	fn property_permissions(
@@ -114,6 +122,7 @@
 		keys: Option<Vec<String>>,
 		at: Option<BlockHash>,
 	) -> Result<Vec<PropertyKeyPermission>>;
+
 	/// Get token data
 	#[method(name = "unique_tokenData")]
 	fn token_data(
@@ -123,9 +132,11 @@
 		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(
@@ -134,6 +145,7 @@
 		account: CrossAccountId,
 		at: Option<BlockHash>,
 	) -> Result<u32>;
+
 	/// Get owned amount of specific account token
 	#[method(name = "unique_balance")]
 	fn balance(
@@ -143,6 +155,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<String>;
+
 	/// Get allowed amount
 	#[method(name = "unique_allowance")]
 	fn allowance(
@@ -153,6 +166,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<String>;
+
 	/// Get admin list
 	#[method(name = "unique_adminlist")]
 	fn adminlist(
@@ -160,6 +174,7 @@
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<CrossAccountId>>;
+
 	/// Get allowlist
 	#[method(name = "unique_allowlist")]
 	fn allowlist(
@@ -167,6 +182,7 @@
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<CrossAccountId>>;
+
 	/// Check if user is allowed to use collection
 	#[method(name = "unique_allowed")]
 	fn allowed(
@@ -175,9 +191,11 @@
 		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(
@@ -185,9 +203,11 @@
 		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(
@@ -197,6 +217,7 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<u64>>;
+
 	/// Get effective collection limits
 	#[method(name = "unique_effectiveCollectionLimits")]
 	fn effective_collection_limits(
@@ -204,6 +225,7 @@
 		collection_id: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CollectionLimits>>;
+
 	/// Get total pieces of token
 	#[method(name = "unique_totalPieces")]
 	fn total_pieces(
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -334,7 +334,7 @@
 		///
 		/// * collection_id: ID of the collection to which the item belongs.
 		///
-		/// * item_id: ID of the item trasnferred.
+		/// * item_id: ID of the item transferred.
 		///
 		/// * sender: Original owner of the item.
 		///
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -58,13 +58,13 @@
 	pub enum Error<T> {
 		/// Not Fungible item data used to mint in Fungible collection.
 		NotFungibleDataUsedToMintFungibleCollectionToken,
-		/// Not default id passed as TokenId argument
+		/// Not default id passed as TokenId argument.
 		FungibleItemsHaveNoId,
-		/// Tried to set data for fungible item
+		/// Tried to set data for fungible item.
 		FungibleItemsDontHaveData,
-		/// Fungible token does not support nested
+		/// Fungible token does not support nesting.
 		FungibleDisallowsNesting,
-		/// Setting item properties is not allowed
+		/// Setting item properties is not allowed.
 		SettingPropertiesNotAllowed,
 	}
 
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31	PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32	AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37	eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859/// Token data, stored independently from other data used to describe it.60/// Notably contains the owner account address.61#[struct_versioning::versioned(version = 2, upper)]62#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]63pub struct ItemData<CrossAccountId> {64	#[version(..2)]65	pub const_data: BoundedVec<u8, CustomDataLimit>,6667	#[version(..2)]68	pub variable_data: BoundedVec<u8, CustomDataLimit>,6970	pub owner: CrossAccountId,71}7273#[frame_support::pallet]74pub mod pallet {75	use super::*;76	use frame_support::{77		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,78	};79	use frame_system::pallet_prelude::*;80	use up_data_structs::{CollectionId, TokenId};81	use super::weights::WeightInfo;8283	#[pallet::error]84	pub enum Error<T> {85		/// Not Nonfungible item data used to mint in Nonfungible collection.86		NotNonfungibleDataUsedToMintFungibleCollectionToken,87		/// Used amount > 1 with NFT88		NonfungibleItemsHaveNoAmount,89		/// Unable to burn NFT with children90		CantBurnNftWithChildren,91	}9293	#[pallet::config]94	pub trait Config:95		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config96	{97		type WeightInfo: WeightInfo;98	}99100	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);101102	#[pallet::pallet]103	#[pallet::storage_version(STORAGE_VERSION)]104	#[pallet::generate_store(pub(super) trait Store)]105	pub struct Pallet<T>(_);106107	/// Total amount of minted tokens in a collection.108	#[pallet::storage]109	pub type TokensMinted<T: Config> =110		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112	/// Amount of burnt tokens in a collection.113	#[pallet::storage]114	pub type TokensBurnt<T: Config> =115		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;116117	/// Token data, used to partially describe a token.118	#[pallet::storage]119	pub type TokenData<T: Config> = StorageNMap<120		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),121		Value = ItemData<T::CrossAccountId>,122		QueryKind = OptionQuery,123	>;124125	/// Key-value pairs, describing the metadata of a token.126	#[pallet::storage]127	#[pallet::getter(fn token_properties)]128	pub type TokenProperties<T: Config> = StorageNMap<129		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),130		Value = Properties,131		QueryKind = ValueQuery,132		OnEmpty = up_data_structs::TokenProperties,133	>;134135	/// Scoped, auxiliary properties of a token, primarily used for on-chain operations.136	#[pallet::storage]137	#[pallet::getter(fn token_aux_property)]138	pub type TokenAuxProperties<T: Config> = StorageNMap<139		Key = (140			Key<Twox64Concat, CollectionId>,141			Key<Twox64Concat, TokenId>,142			Key<Twox64Concat, PropertyScope>,143			Key<Twox64Concat, PropertyKey>,144		),145		Value = AuxPropertyValue,146		QueryKind = OptionQuery,147	>;148149	/// Used to enumerate tokens owned by account.150	#[pallet::storage]151	pub type Owned<T: Config> = StorageNMap<152		Key = (153			Key<Twox64Concat, CollectionId>,154			Key<Blake2_128Concat, T::CrossAccountId>,155			Key<Twox64Concat, TokenId>,156		),157		Value = bool,158		QueryKind = ValueQuery,159	>;160161	/// Used to enumerate token's children.162	#[pallet::storage]163	#[pallet::getter(fn token_children)]164	pub type TokenChildren<T: Config> = StorageNMap<165		Key = (166			Key<Twox64Concat, CollectionId>,167			Key<Twox64Concat, TokenId>,168			Key<Twox64Concat, (CollectionId, TokenId)>,169		),170		Value = bool,171		QueryKind = ValueQuery,172	>;173174	/// Amount of tokens owned in a collection.s175	#[pallet::storage]176	pub type AccountBalance<T: Config> = StorageNMap<177		Key = (178			Key<Twox64Concat, CollectionId>,179			Key<Blake2_128Concat, T::CrossAccountId>,180		),181		Value = u32,182		QueryKind = ValueQuery,183	>;184185	/// todo doc186	#[pallet::storage]187	pub type Allowance<T: Config> = StorageNMap<188		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),189		Value = T::CrossAccountId,190		QueryKind = OptionQuery,191	>;192193	/// Upgrade from the old schema to properties.194	#[pallet::hooks]195	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {196		fn on_runtime_upgrade() -> Weight {197			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {198				let mut had_consts = BTreeSet::new();199				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(200					|(collection, token), v| {201						let mut props = vec![];202						if !v.const_data.is_empty() {203							props.push(Property {204								key: b"_old_constData".to_vec().try_into().unwrap(),205								value: v206									.const_data207									.clone()208									.into_inner()209									.try_into()210									.expect("const too long"),211							});212							had_consts.insert(collection);213						}214						if !v.variable_data.is_empty() {215							props.push(Property {216								key: b"_old_variableData".to_vec().try_into().unwrap(),217								value: v218									.variable_data219									.clone()220									.into_inner()221									.try_into()222									.expect("variable too long"),223							})224						}225						if !props.is_empty() {226							Self::set_scoped_token_properties(227								collection,228								token,229								PropertyScope::None,230								props.into_iter(),231							)232							.expect("existing token data exceeds property storage");233						}234						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))235					},236				);237				for collection in had_consts {238					<PalletCommon<T>>::set_property_permission_unchecked(239						collection,240						PropertyKeyPermission {241							key: b"_old_constData".to_vec().try_into().unwrap(),242							permission: PropertyPermission {243								mutable: false,244								collection_admin: true,245								token_owner: false,246							},247						},248					)249					.expect("failed to configure permission");250				}251			}252253			0254		}255	}256}257258pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);259impl<T: Config> NonfungibleHandle<T> {260	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {261		Self(inner)262	}263	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {264		self.0265	}266	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {267		&mut self.0268	}269}270impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {271	fn recorder(&self) -> &SubstrateRecorder<T> {272		self.0.recorder()273	}274	fn into_recorder(self) -> SubstrateRecorder<T> {275		self.0.into_recorder()276	}277}278impl<T: Config> Deref for NonfungibleHandle<T> {279	type Target = pallet_common::CollectionHandle<T>;280281	fn deref(&self) -> &Self::Target {282		&self.0283	}284}285286impl<T: Config> Pallet<T> {287	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {288		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289	}290	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {291		<TokenData<T>>::contains_key((collection.id, token))292	}293294	pub fn set_scoped_token_property(295		collection_id: CollectionId,296		token_id: TokenId,297		scope: PropertyScope,298		property: Property,299	) -> DispatchResult {300		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {301			properties.try_scoped_set(scope, property.key, property.value)302		})303		.map_err(<CommonError<T>>::from)?;304305		Ok(())306	}307308	pub fn set_scoped_token_properties(309		collection_id: CollectionId,310		token_id: TokenId,311		scope: PropertyScope,312		properties: impl Iterator<Item = Property>,313	) -> DispatchResult {314		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {315			stored_properties.try_scoped_set_from_iter(scope, properties)316		})317		.map_err(<CommonError<T>>::from)?;318319		Ok(())320	}321322	pub fn try_mutate_token_aux_property<R, E>(323		collection_id: CollectionId,324		token_id: TokenId,325		scope: PropertyScope,326		key: PropertyKey,327		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,328	) -> Result<R, E> {329		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)330	}331332	pub fn remove_token_aux_property(333		collection_id: CollectionId,334		token_id: TokenId,335		scope: PropertyScope,336		key: PropertyKey,337	) {338		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));339	}340341	pub fn iterate_token_aux_properties(342		collection_id: CollectionId,343		token_id: TokenId,344		scope: PropertyScope,345	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {346		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))347	}348349	pub fn current_token_id(collection_id: CollectionId) -> TokenId {350		TokenId(<TokensMinted<T>>::get(collection_id))351	}352}353354// unchecked calls skips any permission checks355impl<T: Config> Pallet<T> {356	pub fn init_collection(357		owner: T::CrossAccountId,358		data: CreateCollectionData<T::AccountId>,359		is_external: bool,360	) -> Result<CollectionId, DispatchError> {361		<PalletCommon<T>>::init_collection(owner, data, is_external)362	}363	pub fn destroy_collection(364		collection: NonfungibleHandle<T>,365		sender: &T::CrossAccountId,366	) -> DispatchResult {367		let id = collection.id;368369		if Self::collection_has_tokens(id) {370			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());371		}372373		// =========374375		PalletCommon::destroy_collection(collection.0, sender)?;376377		<TokenData<T>>::remove_prefix((id,), None);378		<TokenChildren<T>>::remove_prefix((id,), None);379		<Owned<T>>::remove_prefix((id,), None);380		<TokensMinted<T>>::remove(id);381		<TokensBurnt<T>>::remove(id);382		<Allowance<T>>::remove_prefix((id,), None);383		<AccountBalance<T>>::remove_prefix((id,), None);384		Ok(())385	}386387	pub fn burn(388		collection: &NonfungibleHandle<T>,389		sender: &T::CrossAccountId,390		token: TokenId,391	) -> DispatchResult {392		let token_data =393			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;394		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);395396		if collection.permissions.access() == AccessMode::AllowList {397			collection.check_allowlist(sender)?;398		}399400		if Self::token_has_children(collection.id, token) {401			return Err(<Error<T>>::CantBurnNftWithChildren.into());402		}403404		let burnt = <TokensBurnt<T>>::get(collection.id)405			.checked_add(1)406			.ok_or(ArithmeticError::Overflow)?;407408		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))409			.checked_sub(1)410			.ok_or(ArithmeticError::Overflow)?;411412		// =========413414		if balance == 0 {415			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));416		} else {417			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);418		}419420		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);421422		<Owned<T>>::remove((collection.id, &token_data.owner, token));423		<TokensBurnt<T>>::insert(collection.id, burnt);424		<TokenData<T>>::remove((collection.id, token));425		<TokenProperties<T>>::remove((collection.id, token));426		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);427		let old_spender = <Allowance<T>>::take((collection.id, token));428429		if let Some(old_spender) = old_spender {430			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(431				collection.id,432				token,433				token_data.owner.clone(),434				old_spender,435				0,436			));437		}438439		<PalletEvm<T>>::deposit_log(440			ERC721Events::Transfer {441				from: *token_data.owner.as_eth(),442				to: H160::default(),443				token_id: token.into(),444			}445			.to_log(collection_id_to_address(collection.id)),446		);447		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(448			collection.id,449			token,450			token_data.owner,451			1,452		));453		Ok(())454	}455456	#[transactional]457	pub fn burn_recursively(458		collection: &NonfungibleHandle<T>,459		sender: &T::CrossAccountId,460		token: TokenId,461		self_budget: &dyn Budget,462		breadth_budget: &dyn Budget,463	) -> DispatchResultWithPostInfo {464		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);465466		let current_token_account =467			T::CrossTokenAddressMapping::token_to_address(collection.id, token);468469		let mut weight = 0 as Weight;470471		// This method is transactional, if user in fact doesn't have permissions to remove token -472		// tokens removed here will be restored after rejected transaction473		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {474			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);475			let PostDispatchInfo { actual_weight, .. } =476				<PalletStructure<T>>::burn_item_recursively(477					current_token_account.clone(),478					collection,479					token,480					self_budget,481					breadth_budget,482				)?;483			if let Some(actual_weight) = actual_weight {484				weight = weight.saturating_add(actual_weight);485			}486		}487488		Self::burn(collection, sender, token)?;489		DispatchResultWithPostInfo::Ok(PostDispatchInfo {490			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),491			pays_fee: Pays::Yes,492		})493	}494495	#[transactional]496	fn modify_token_properties(497		collection: &NonfungibleHandle<T>,498		sender: &T::CrossAccountId,499		token_id: TokenId,500		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,501		is_token_create: bool,502		nesting_budget: &dyn Budget,503	) -> DispatchResult {504		let mut collection_admin_status = None;505		let mut token_owner_result = None;506507		let mut is_collection_admin =508			|| *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));509510		let mut is_token_owner = || {511			*token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {512				let is_owned = <PalletStructure<T>>::check_indirectly_owned(513					sender.clone(),514					collection.id,515					token_id,516					None,517					nesting_budget,518				)?;519520				Ok(is_owned)521			})522		};523524		for (key, value) in properties {525			let permission = <PalletCommon<T>>::property_permissions(collection.id)526				.get(&key)527				.cloned()528				.unwrap_or_else(PropertyPermission::none);529530			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))531				.get(&key)532				.is_some();533534			match permission {535				PropertyPermission { mutable: false, .. } if is_property_exists => {536					return Err(<CommonError<T>>::NoPermission.into());537				}538539				PropertyPermission {540					collection_admin,541					token_owner,542					..543				} => {544					//TODO: investigate threats during public minting.545					if is_token_create && (collection_admin || token_owner) && value.is_some() {546						// Pass547					} else if collection_admin && is_collection_admin() {548						// Pass549					} else if token_owner && is_token_owner()? {550						// Pass551					} else {552						fail!(<CommonError<T>>::NoPermission);553					}554				}555			}556557			match value {558				Some(value) => {559					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {560						properties.try_set(key.clone(), value)561					})562					.map_err(<CommonError<T>>::from)?;563564					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(565						collection.id,566						token_id,567						key,568					));569				}570				None => {571					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {572						properties.remove(&key)573					})574					.map_err(<CommonError<T>>::from)?;575576					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(577						collection.id,578						token_id,579						key,580					));581				}582			}583		}584585		Ok(())586	}587588	pub fn set_token_properties(589		collection: &NonfungibleHandle<T>,590		sender: &T::CrossAccountId,591		token_id: TokenId,592		properties: impl Iterator<Item = Property>,593		is_token_create: bool,594		nesting_budget: &dyn Budget,595	) -> DispatchResult {596		Self::modify_token_properties(597			collection,598			sender,599			token_id,600			properties.map(|p| (p.key, Some(p.value))),601			is_token_create,602			nesting_budget,603		)604	}605606	pub fn set_token_property(607		collection: &NonfungibleHandle<T>,608		sender: &T::CrossAccountId,609		token_id: TokenId,610		property: Property,611		nesting_budget: &dyn Budget,612	) -> DispatchResult {613		let is_token_create = false;614615		Self::set_token_properties(616			collection,617			sender,618			token_id,619			[property].into_iter(),620			is_token_create,621			nesting_budget,622		)623	}624625	pub fn delete_token_properties(626		collection: &NonfungibleHandle<T>,627		sender: &T::CrossAccountId,628		token_id: TokenId,629		property_keys: impl Iterator<Item = PropertyKey>,630		nesting_budget: &dyn Budget,631	) -> DispatchResult {632		let is_token_create = false;633634		Self::modify_token_properties(635			collection,636			sender,637			token_id,638			property_keys.into_iter().map(|key| (key, None)),639			is_token_create,640			nesting_budget,641		)642	}643644	pub fn delete_token_property(645		collection: &NonfungibleHandle<T>,646		sender: &T::CrossAccountId,647		token_id: TokenId,648		property_key: PropertyKey,649		nesting_budget: &dyn Budget,650	) -> DispatchResult {651		Self::delete_token_properties(652			collection,653			sender,654			token_id,655			[property_key].into_iter(),656			nesting_budget,657		)658	}659660	pub fn set_collection_properties(661		collection: &NonfungibleHandle<T>,662		sender: &T::CrossAccountId,663		properties: Vec<Property>,664	) -> DispatchResult {665		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)666	}667668	pub fn delete_collection_properties(669		collection: &CollectionHandle<T>,670		sender: &T::CrossAccountId,671		property_keys: Vec<PropertyKey>,672	) -> DispatchResult {673		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)674	}675676	pub fn set_token_property_permissions(677		collection: &CollectionHandle<T>,678		sender: &T::CrossAccountId,679		property_permissions: Vec<PropertyKeyPermission>,680	) -> DispatchResult {681		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)682	}683684	pub fn set_property_permission(685		collection: &CollectionHandle<T>,686		sender: &T::CrossAccountId,687		permission: PropertyKeyPermission,688	) -> DispatchResult {689		<PalletCommon<T>>::set_property_permission(collection, sender, permission)690	}691692	pub fn transfer(693		collection: &NonfungibleHandle<T>,694		from: &T::CrossAccountId,695		to: &T::CrossAccountId,696		token: TokenId,697		nesting_budget: &dyn Budget,698	) -> DispatchResult {699		ensure!(700			collection.limits.transfers_enabled(),701			<CommonError<T>>::TransferNotAllowed702		);703704		let token_data =705			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;706		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);707708		if collection.permissions.access() == AccessMode::AllowList {709			collection.check_allowlist(from)?;710			collection.check_allowlist(to)?;711		}712		<PalletCommon<T>>::ensure_correct_receiver(to)?;713714		let balance_from = <AccountBalance<T>>::get((collection.id, from))715			.checked_sub(1)716			.ok_or(<CommonError<T>>::TokenValueTooLow)?;717		let balance_to = if from != to {718			let balance_to = <AccountBalance<T>>::get((collection.id, to))719				.checked_add(1)720				.ok_or(ArithmeticError::Overflow)?;721722			ensure!(723				balance_to < collection.limits.account_token_ownership_limit(),724				<CommonError<T>>::AccountTokenLimitExceeded,725			);726727			Some(balance_to)728		} else {729			None730		};731732		<PalletStructure<T>>::nest_if_sent_to_token(733			from.clone(),734			to,735			collection.id,736			token,737			nesting_budget,738		)?;739740		// =========741742		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);743744		<TokenData<T>>::insert(745			(collection.id, token),746			ItemData {747				owner: to.clone(),748				..token_data749			},750		);751752		if let Some(balance_to) = balance_to {753			// from != to754			if balance_from == 0 {755				<AccountBalance<T>>::remove((collection.id, from));756			} else {757				<AccountBalance<T>>::insert((collection.id, from), balance_from);758			}759			<AccountBalance<T>>::insert((collection.id, to), balance_to);760			<Owned<T>>::remove((collection.id, from, token));761			<Owned<T>>::insert((collection.id, to, token), true);762		}763		Self::set_allowance_unchecked(collection, from, token, None, true);764765		<PalletEvm<T>>::deposit_log(766			ERC721Events::Transfer {767				from: *from.as_eth(),768				to: *to.as_eth(),769				token_id: token.into(),770			}771			.to_log(collection_id_to_address(collection.id)),772		);773		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774			collection.id,775			token,776			from.clone(),777			to.clone(),778			1,779		));780		Ok(())781	}782783	pub fn create_multiple_items(784		collection: &NonfungibleHandle<T>,785		sender: &T::CrossAccountId,786		data: Vec<CreateItemData<T>>,787		nesting_budget: &dyn Budget,788	) -> DispatchResult {789		if !collection.is_owner_or_admin(sender) {790			ensure!(791				collection.permissions.mint_mode(),792				<CommonError<T>>::PublicMintingNotAllowed793			);794			collection.check_allowlist(sender)?;795796			for item in data.iter() {797				collection.check_allowlist(&item.owner)?;798			}799		}800801		for data in data.iter() {802			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;803		}804805		let first_token = <TokensMinted<T>>::get(collection.id);806		let tokens_minted = first_token807			.checked_add(data.len() as u32)808			.ok_or(ArithmeticError::Overflow)?;809		ensure!(810			tokens_minted <= collection.limits.token_limit(),811			<CommonError<T>>::CollectionTokenLimitExceeded812		);813814		let mut balances = BTreeMap::new();815		for data in &data {816			let balance = balances817				.entry(&data.owner)818				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));819			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;820821			ensure!(822				*balance <= collection.limits.account_token_ownership_limit(),823				<CommonError<T>>::AccountTokenLimitExceeded,824			);825		}826827		for (i, data) in data.iter().enumerate() {828			let token = TokenId(first_token + i as u32 + 1);829830			<PalletStructure<T>>::check_nesting(831				sender.clone(),832				&data.owner,833				collection.id,834				token,835				nesting_budget,836			)?;837		}838839		// =========840841		with_transaction(|| {842			for (i, data) in data.iter().enumerate() {843				let token = first_token + i as u32 + 1;844845				<TokenData<T>>::insert(846					(collection.id, token),847					ItemData {848						// const_data: data.const_data.clone(),849						owner: data.owner.clone(),850					},851				);852853				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(854					&data.owner,855					collection.id,856					TokenId(token),857				);858859				if let Err(e) = Self::set_token_properties(860					collection,861					sender,862					TokenId(token),863					data.properties.clone().into_iter(),864					true,865					nesting_budget,866				) {867					return TransactionOutcome::Rollback(Err(e));868				}869			}870			TransactionOutcome::Commit(Ok(()))871		})?;872873		<TokensMinted<T>>::insert(collection.id, tokens_minted);874		for (account, balance) in balances {875			<AccountBalance<T>>::insert((collection.id, account), balance);876		}877		for (i, data) in data.into_iter().enumerate() {878			let token = first_token + i as u32 + 1;879			<Owned<T>>::insert((collection.id, &data.owner, token), true);880881			<PalletEvm<T>>::deposit_log(882				ERC721Events::Transfer {883					from: H160::default(),884					to: *data.owner.as_eth(),885					token_id: token.into(),886				}887				.to_log(collection_id_to_address(collection.id)),888			);889			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(890				collection.id,891				TokenId(token),892				data.owner.clone(),893				1,894			));895		}896		Ok(())897	}898899	pub fn set_allowance_unchecked(900		collection: &NonfungibleHandle<T>,901		sender: &T::CrossAccountId,902		token: TokenId,903		spender: Option<&T::CrossAccountId>,904		assume_implicit_eth: bool,905	) {906		if let Some(spender) = spender {907			let old_spender = <Allowance<T>>::get((collection.id, token));908			<Allowance<T>>::insert((collection.id, token), spender);909			// In ERC721 there is only one possible approved user of token, so we set910			// approved user to spender911			<PalletEvm<T>>::deposit_log(912				ERC721Events::Approval {913					owner: *sender.as_eth(),914					approved: *spender.as_eth(),915					token_id: token.into(),916				}917				.to_log(collection_id_to_address(collection.id)),918			);919			// In Unique chain, any token can have any amount of approved users, so we need to920			// set allowance of old owner to 0, and allowance of new owner to 1921			if old_spender.as_ref() != Some(spender) {922				if let Some(old_owner) = old_spender {923					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(924						collection.id,925						token,926						sender.clone(),927						old_owner,928						0,929					));930				}931				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(932					collection.id,933					token,934					sender.clone(),935					spender.clone(),936					1,937				));938			}939		} else {940			let old_spender = <Allowance<T>>::take((collection.id, token));941			if !assume_implicit_eth {942				// In ERC721 there is only one possible approved user of token, so we set943				// approved user to zero address944				<PalletEvm<T>>::deposit_log(945					ERC721Events::Approval {946						owner: *sender.as_eth(),947						approved: H160::default(),948						token_id: token.into(),949					}950					.to_log(collection_id_to_address(collection.id)),951				);952			}953			// In Unique chain, any token can have any amount of approved users, so we need to954			// set allowance of old owner to 0955			if let Some(old_spender) = old_spender {956				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(957					collection.id,958					token,959					sender.clone(),960					old_spender,961					0,962				));963			}964		}965	}966967	pub fn set_allowance(968		collection: &NonfungibleHandle<T>,969		sender: &T::CrossAccountId,970		token: TokenId,971		spender: Option<&T::CrossAccountId>,972	) -> DispatchResult {973		if collection.permissions.access() == AccessMode::AllowList {974			collection.check_allowlist(sender)?;975			if let Some(spender) = spender {976				collection.check_allowlist(spender)?;977			}978		}979980		if let Some(spender) = spender {981			<PalletCommon<T>>::ensure_correct_receiver(spender)?;982		}983984		let token_data =985			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;986		if &token_data.owner != sender {987			ensure!(988				collection.ignores_owned_amount(sender),989				<CommonError<T>>::CantApproveMoreThanOwned990			);991		}992993		// =========994995		Self::set_allowance_unchecked(collection, sender, token, spender, false);996		Ok(())997	}998999	fn check_allowed(1000		collection: &NonfungibleHandle<T>,1001		spender: &T::CrossAccountId,1002		from: &T::CrossAccountId,1003		token: TokenId,1004		nesting_budget: &dyn Budget,1005	) -> DispatchResult {1006		if spender.conv_eq(from) {1007			return Ok(());1008		}1009		if collection.permissions.access() == AccessMode::AllowList {1010			// `from`, `to` checked in [`transfer`]1011			collection.check_allowlist(spender)?;1012		}10131014		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {1015			return Ok(());1016		}10171018		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1019			ensure!(1020				<PalletStructure<T>>::check_indirectly_owned(1021					spender.clone(),1022					source.0,1023					source.1,1024					None,1025					nesting_budget1026				)?,1027				<CommonError<T>>::ApprovedValueTooLow,1028			);1029			return Ok(());1030		}1031		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1032			return Ok(());1033		}1034		ensure!(1035			collection.ignores_allowance(spender),1036			<CommonError<T>>::ApprovedValueTooLow1037		);1038		Ok(())1039	}10401041	pub fn transfer_from(1042		collection: &NonfungibleHandle<T>,1043		spender: &T::CrossAccountId,1044		from: &T::CrossAccountId,1045		to: &T::CrossAccountId,1046		token: TokenId,1047		nesting_budget: &dyn Budget,1048	) -> DispatchResult {1049		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10501051		// =========10521053		// Allowance is reset in [`transfer`]1054		Self::transfer(collection, from, to, token, nesting_budget)1055	}10561057	pub fn burn_from(1058		collection: &NonfungibleHandle<T>,1059		spender: &T::CrossAccountId,1060		from: &T::CrossAccountId,1061		token: TokenId,1062		nesting_budget: &dyn Budget,1063	) -> DispatchResult {1064		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10651066		// =========10671068		Self::burn(collection, from, token)1069	}10701071	pub fn check_nesting(1072		handle: &NonfungibleHandle<T>,1073		sender: T::CrossAccountId,1074		from: (CollectionId, TokenId),1075		under: TokenId,1076		nesting_budget: &dyn Budget,1077	) -> DispatchResult {1078		let nesting = handle.permissions.nesting();10791080		#[cfg(not(feature = "runtime-benchmarks"))]1081		let permissive = false;1082		#[cfg(feature = "runtime-benchmarks")]1083		let permissive = nesting.permissive;10841085		if permissive {1086			// Pass1087		} else if nesting.token_owner1088			&& <PalletStructure<T>>::check_indirectly_owned(1089				sender.clone(),1090				handle.id,1091				under,1092				Some(from),1093				nesting_budget,1094			)? {1095			// Pass1096		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1097			// Pass1098		} else {1099			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1100		}11011102		if let Some(whitelist) = &nesting.restricted {1103			ensure!(1104				whitelist.contains(&from.0),1105				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1106			);1107		}1108		Ok(())1109	}11101111	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1112		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1113	}11141115	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1116		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1117	}11181119	fn collection_has_tokens(collection_id: CollectionId) -> bool {1120		<TokenData<T>>::iter_prefix((collection_id,))1121			.next()1122			.is_some()1123	}11241125	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1126		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1127			.next()1128			.is_some()1129	}11301131	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1132		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1133			.map(|((child_collection_id, child_id), _)| TokenChild {1134				collection: child_collection_id,1135				token: child_id,1136			})1137			.collect()1138	}11391140	/// Delegated to `create_multiple_items`1141	pub fn create_item(1142		collection: &NonfungibleHandle<T>,1143		sender: &T::CrossAccountId,1144		data: CreateItemData<T>,1145		nesting_budget: &dyn Budget,1146	) -> DispatchResult {1147		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1148	}1149}
after · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31	PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32	AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37	eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859/// Token data, stored independently from other data used to describe it.60/// Notably contains the owner account address.61#[struct_versioning::versioned(version = 2, upper)]62#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]63pub struct ItemData<CrossAccountId> {64	#[version(..2)]65	pub const_data: BoundedVec<u8, CustomDataLimit>,6667	#[version(..2)]68	pub variable_data: BoundedVec<u8, CustomDataLimit>,6970	pub owner: CrossAccountId,71}7273#[frame_support::pallet]74pub mod pallet {75	use super::*;76	use frame_support::{77		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,78	};79	use frame_system::pallet_prelude::*;80	use up_data_structs::{CollectionId, TokenId};81	use super::weights::WeightInfo;8283	#[pallet::error]84	pub enum Error<T> {85		/// Not Nonfungible item data used to mint in Nonfungible collection.86		NotNonfungibleDataUsedToMintFungibleCollectionToken,87		/// Used amount > 1 with NFT88		NonfungibleItemsHaveNoAmount,89		/// Unable to burn NFT with children90		CantBurnNftWithChildren,91	}9293	#[pallet::config]94	pub trait Config:95		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config96	{97		type WeightInfo: WeightInfo;98	}99100	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);101102	#[pallet::pallet]103	#[pallet::storage_version(STORAGE_VERSION)]104	#[pallet::generate_store(pub(super) trait Store)]105	pub struct Pallet<T>(_);106107	/// Total amount of minted tokens in a collection.108	#[pallet::storage]109	pub type TokensMinted<T: Config> =110		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112	/// Amount of burnt tokens in a collection.113	#[pallet::storage]114	pub type TokensBurnt<T: Config> =115		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;116117	/// Token data, used to partially describe a token.118	#[pallet::storage]119	pub type TokenData<T: Config> = StorageNMap<120		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),121		Value = ItemData<T::CrossAccountId>,122		QueryKind = OptionQuery,123	>;124125	/// Key-value pairs, describing the metadata of a token.126	#[pallet::storage]127	#[pallet::getter(fn token_properties)]128	pub type TokenProperties<T: Config> = StorageNMap<129		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),130		Value = Properties,131		QueryKind = ValueQuery,132		OnEmpty = up_data_structs::TokenProperties,133	>;134135	/// Scoped, auxiliary properties of a token, primarily used for on-chain operations.136	#[pallet::storage]137	#[pallet::getter(fn token_aux_property)]138	pub type TokenAuxProperties<T: Config> = StorageNMap<139		Key = (140			Key<Twox64Concat, CollectionId>,141			Key<Twox64Concat, TokenId>,142			Key<Twox64Concat, PropertyScope>,143			Key<Twox64Concat, PropertyKey>,144		),145		Value = AuxPropertyValue,146		QueryKind = OptionQuery,147	>;148149	/// Used to enumerate tokens owned by account.150	#[pallet::storage]151	pub type Owned<T: Config> = StorageNMap<152		Key = (153			Key<Twox64Concat, CollectionId>,154			Key<Blake2_128Concat, T::CrossAccountId>,155			Key<Twox64Concat, TokenId>,156		),157		Value = bool,158		QueryKind = ValueQuery,159	>;160161	/// Used to enumerate token's children.162	#[pallet::storage]163	#[pallet::getter(fn token_children)]164	pub type TokenChildren<T: Config> = StorageNMap<165		Key = (166			Key<Twox64Concat, CollectionId>,167			Key<Twox64Concat, TokenId>,168			Key<Twox64Concat, (CollectionId, TokenId)>,169		),170		Value = bool,171		QueryKind = ValueQuery,172	>;173174	/// Amount of tokens owned in a collection.175	#[pallet::storage]176	pub type AccountBalance<T: Config> = StorageNMap<177		Key = (178			Key<Twox64Concat, CollectionId>,179			Key<Blake2_128Concat, T::CrossAccountId>,180		),181		Value = u32,182		QueryKind = ValueQuery,183	>;184185	/// todo:doc186	#[pallet::storage]187	pub type Allowance<T: Config> = StorageNMap<188		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),189		Value = T::CrossAccountId,190		QueryKind = OptionQuery,191	>;192193	/// Upgrade from the old schema to properties.194	#[pallet::hooks]195	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {196		fn on_runtime_upgrade() -> Weight {197			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {198				let mut had_consts = BTreeSet::new();199				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(200					|(collection, token), v| {201						let mut props = vec![];202						if !v.const_data.is_empty() {203							props.push(Property {204								key: b"_old_constData".to_vec().try_into().unwrap(),205								value: v206									.const_data207									.clone()208									.into_inner()209									.try_into()210									.expect("const too long"),211							});212							had_consts.insert(collection);213						}214						if !v.variable_data.is_empty() {215							props.push(Property {216								key: b"_old_variableData".to_vec().try_into().unwrap(),217								value: v218									.variable_data219									.clone()220									.into_inner()221									.try_into()222									.expect("variable too long"),223							})224						}225						if !props.is_empty() {226							Self::set_scoped_token_properties(227								collection,228								token,229								PropertyScope::None,230								props.into_iter(),231							)232							.expect("existing token data exceeds property storage");233						}234						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))235					},236				);237				for collection in had_consts {238					<PalletCommon<T>>::set_property_permission_unchecked(239						collection,240						PropertyKeyPermission {241							key: b"_old_constData".to_vec().try_into().unwrap(),242							permission: PropertyPermission {243								mutable: false,244								collection_admin: true,245								token_owner: false,246							},247						},248					)249					.expect("failed to configure permission");250				}251			}252253			0254		}255	}256}257258pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);259impl<T: Config> NonfungibleHandle<T> {260	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {261		Self(inner)262	}263	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {264		self.0265	}266	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {267		&mut self.0268	}269}270impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {271	fn recorder(&self) -> &SubstrateRecorder<T> {272		self.0.recorder()273	}274	fn into_recorder(self) -> SubstrateRecorder<T> {275		self.0.into_recorder()276	}277}278impl<T: Config> Deref for NonfungibleHandle<T> {279	type Target = pallet_common::CollectionHandle<T>;280281	fn deref(&self) -> &Self::Target {282		&self.0283	}284}285286impl<T: Config> Pallet<T> {287	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {288		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289	}290	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {291		<TokenData<T>>::contains_key((collection.id, token))292	}293294	pub fn set_scoped_token_property(295		collection_id: CollectionId,296		token_id: TokenId,297		scope: PropertyScope,298		property: Property,299	) -> DispatchResult {300		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {301			properties.try_scoped_set(scope, property.key, property.value)302		})303		.map_err(<CommonError<T>>::from)?;304305		Ok(())306	}307308	pub fn set_scoped_token_properties(309		collection_id: CollectionId,310		token_id: TokenId,311		scope: PropertyScope,312		properties: impl Iterator<Item = Property>,313	) -> DispatchResult {314		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {315			stored_properties.try_scoped_set_from_iter(scope, properties)316		})317		.map_err(<CommonError<T>>::from)?;318319		Ok(())320	}321322	pub fn try_mutate_token_aux_property<R, E>(323		collection_id: CollectionId,324		token_id: TokenId,325		scope: PropertyScope,326		key: PropertyKey,327		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,328	) -> Result<R, E> {329		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)330	}331332	pub fn remove_token_aux_property(333		collection_id: CollectionId,334		token_id: TokenId,335		scope: PropertyScope,336		key: PropertyKey,337	) {338		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));339	}340341	pub fn iterate_token_aux_properties(342		collection_id: CollectionId,343		token_id: TokenId,344		scope: PropertyScope,345	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {346		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))347	}348349	pub fn current_token_id(collection_id: CollectionId) -> TokenId {350		TokenId(<TokensMinted<T>>::get(collection_id))351	}352}353354// unchecked calls skips any permission checks355impl<T: Config> Pallet<T> {356	pub fn init_collection(357		owner: T::CrossAccountId,358		data: CreateCollectionData<T::AccountId>,359		is_external: bool,360	) -> Result<CollectionId, DispatchError> {361		<PalletCommon<T>>::init_collection(owner, data, is_external)362	}363	pub fn destroy_collection(364		collection: NonfungibleHandle<T>,365		sender: &T::CrossAccountId,366	) -> DispatchResult {367		let id = collection.id;368369		if Self::collection_has_tokens(id) {370			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());371		}372373		// =========374375		PalletCommon::destroy_collection(collection.0, sender)?;376377		<TokenData<T>>::remove_prefix((id,), None);378		<TokenChildren<T>>::remove_prefix((id,), None);379		<Owned<T>>::remove_prefix((id,), None);380		<TokensMinted<T>>::remove(id);381		<TokensBurnt<T>>::remove(id);382		<Allowance<T>>::remove_prefix((id,), None);383		<AccountBalance<T>>::remove_prefix((id,), None);384		Ok(())385	}386387	pub fn burn(388		collection: &NonfungibleHandle<T>,389		sender: &T::CrossAccountId,390		token: TokenId,391	) -> DispatchResult {392		let token_data =393			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;394		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);395396		if collection.permissions.access() == AccessMode::AllowList {397			collection.check_allowlist(sender)?;398		}399400		if Self::token_has_children(collection.id, token) {401			return Err(<Error<T>>::CantBurnNftWithChildren.into());402		}403404		let burnt = <TokensBurnt<T>>::get(collection.id)405			.checked_add(1)406			.ok_or(ArithmeticError::Overflow)?;407408		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))409			.checked_sub(1)410			.ok_or(ArithmeticError::Overflow)?;411412		// =========413414		if balance == 0 {415			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));416		} else {417			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);418		}419420		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);421422		<Owned<T>>::remove((collection.id, &token_data.owner, token));423		<TokensBurnt<T>>::insert(collection.id, burnt);424		<TokenData<T>>::remove((collection.id, token));425		<TokenProperties<T>>::remove((collection.id, token));426		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);427		let old_spender = <Allowance<T>>::take((collection.id, token));428429		if let Some(old_spender) = old_spender {430			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(431				collection.id,432				token,433				token_data.owner.clone(),434				old_spender,435				0,436			));437		}438439		<PalletEvm<T>>::deposit_log(440			ERC721Events::Transfer {441				from: *token_data.owner.as_eth(),442				to: H160::default(),443				token_id: token.into(),444			}445			.to_log(collection_id_to_address(collection.id)),446		);447		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(448			collection.id,449			token,450			token_data.owner,451			1,452		));453		Ok(())454	}455456	#[transactional]457	pub fn burn_recursively(458		collection: &NonfungibleHandle<T>,459		sender: &T::CrossAccountId,460		token: TokenId,461		self_budget: &dyn Budget,462		breadth_budget: &dyn Budget,463	) -> DispatchResultWithPostInfo {464		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);465466		let current_token_account =467			T::CrossTokenAddressMapping::token_to_address(collection.id, token);468469		let mut weight = 0 as Weight;470471		// This method is transactional, if user in fact doesn't have permissions to remove token -472		// tokens removed here will be restored after rejected transaction473		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {474			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);475			let PostDispatchInfo { actual_weight, .. } =476				<PalletStructure<T>>::burn_item_recursively(477					current_token_account.clone(),478					collection,479					token,480					self_budget,481					breadth_budget,482				)?;483			if let Some(actual_weight) = actual_weight {484				weight = weight.saturating_add(actual_weight);485			}486		}487488		Self::burn(collection, sender, token)?;489		DispatchResultWithPostInfo::Ok(PostDispatchInfo {490			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),491			pays_fee: Pays::Yes,492		})493	}494495	#[transactional]496	fn modify_token_properties(497		collection: &NonfungibleHandle<T>,498		sender: &T::CrossAccountId,499		token_id: TokenId,500		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,501		is_token_create: bool,502		nesting_budget: &dyn Budget,503	) -> DispatchResult {504		let mut collection_admin_status = None;505		let mut token_owner_result = None;506507		let mut is_collection_admin =508			|| *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));509510		let mut is_token_owner = || {511			*token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {512				let is_owned = <PalletStructure<T>>::check_indirectly_owned(513					sender.clone(),514					collection.id,515					token_id,516					None,517					nesting_budget,518				)?;519520				Ok(is_owned)521			})522		};523524		for (key, value) in properties {525			let permission = <PalletCommon<T>>::property_permissions(collection.id)526				.get(&key)527				.cloned()528				.unwrap_or_else(PropertyPermission::none);529530			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))531				.get(&key)532				.is_some();533534			match permission {535				PropertyPermission { mutable: false, .. } if is_property_exists => {536					return Err(<CommonError<T>>::NoPermission.into());537				}538539				PropertyPermission {540					collection_admin,541					token_owner,542					..543				} => {544					//TODO: investigate threats during public minting.545					if is_token_create && (collection_admin || token_owner) && value.is_some() {546						// Pass547					} else if collection_admin && is_collection_admin() {548						// Pass549					} else if token_owner && is_token_owner()? {550						// Pass551					} else {552						fail!(<CommonError<T>>::NoPermission);553					}554				}555			}556557			match value {558				Some(value) => {559					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {560						properties.try_set(key.clone(), value)561					})562					.map_err(<CommonError<T>>::from)?;563564					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(565						collection.id,566						token_id,567						key,568					));569				}570				None => {571					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {572						properties.remove(&key)573					})574					.map_err(<CommonError<T>>::from)?;575576					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(577						collection.id,578						token_id,579						key,580					));581				}582			}583		}584585		Ok(())586	}587588	pub fn set_token_properties(589		collection: &NonfungibleHandle<T>,590		sender: &T::CrossAccountId,591		token_id: TokenId,592		properties: impl Iterator<Item = Property>,593		is_token_create: bool,594		nesting_budget: &dyn Budget,595	) -> DispatchResult {596		Self::modify_token_properties(597			collection,598			sender,599			token_id,600			properties.map(|p| (p.key, Some(p.value))),601			is_token_create,602			nesting_budget,603		)604	}605606	pub fn set_token_property(607		collection: &NonfungibleHandle<T>,608		sender: &T::CrossAccountId,609		token_id: TokenId,610		property: Property,611		nesting_budget: &dyn Budget,612	) -> DispatchResult {613		let is_token_create = false;614615		Self::set_token_properties(616			collection,617			sender,618			token_id,619			[property].into_iter(),620			is_token_create,621			nesting_budget,622		)623	}624625	pub fn delete_token_properties(626		collection: &NonfungibleHandle<T>,627		sender: &T::CrossAccountId,628		token_id: TokenId,629		property_keys: impl Iterator<Item = PropertyKey>,630		nesting_budget: &dyn Budget,631	) -> DispatchResult {632		let is_token_create = false;633634		Self::modify_token_properties(635			collection,636			sender,637			token_id,638			property_keys.into_iter().map(|key| (key, None)),639			is_token_create,640			nesting_budget,641		)642	}643644	pub fn delete_token_property(645		collection: &NonfungibleHandle<T>,646		sender: &T::CrossAccountId,647		token_id: TokenId,648		property_key: PropertyKey,649		nesting_budget: &dyn Budget,650	) -> DispatchResult {651		Self::delete_token_properties(652			collection,653			sender,654			token_id,655			[property_key].into_iter(),656			nesting_budget,657		)658	}659660	pub fn set_collection_properties(661		collection: &NonfungibleHandle<T>,662		sender: &T::CrossAccountId,663		properties: Vec<Property>,664	) -> DispatchResult {665		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)666	}667668	pub fn delete_collection_properties(669		collection: &CollectionHandle<T>,670		sender: &T::CrossAccountId,671		property_keys: Vec<PropertyKey>,672	) -> DispatchResult {673		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)674	}675676	pub fn set_token_property_permissions(677		collection: &CollectionHandle<T>,678		sender: &T::CrossAccountId,679		property_permissions: Vec<PropertyKeyPermission>,680	) -> DispatchResult {681		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)682	}683684	pub fn set_property_permission(685		collection: &CollectionHandle<T>,686		sender: &T::CrossAccountId,687		permission: PropertyKeyPermission,688	) -> DispatchResult {689		<PalletCommon<T>>::set_property_permission(collection, sender, permission)690	}691692	pub fn transfer(693		collection: &NonfungibleHandle<T>,694		from: &T::CrossAccountId,695		to: &T::CrossAccountId,696		token: TokenId,697		nesting_budget: &dyn Budget,698	) -> DispatchResult {699		ensure!(700			collection.limits.transfers_enabled(),701			<CommonError<T>>::TransferNotAllowed702		);703704		let token_data =705			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;706		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);707708		if collection.permissions.access() == AccessMode::AllowList {709			collection.check_allowlist(from)?;710			collection.check_allowlist(to)?;711		}712		<PalletCommon<T>>::ensure_correct_receiver(to)?;713714		let balance_from = <AccountBalance<T>>::get((collection.id, from))715			.checked_sub(1)716			.ok_or(<CommonError<T>>::TokenValueTooLow)?;717		let balance_to = if from != to {718			let balance_to = <AccountBalance<T>>::get((collection.id, to))719				.checked_add(1)720				.ok_or(ArithmeticError::Overflow)?;721722			ensure!(723				balance_to < collection.limits.account_token_ownership_limit(),724				<CommonError<T>>::AccountTokenLimitExceeded,725			);726727			Some(balance_to)728		} else {729			None730		};731732		<PalletStructure<T>>::nest_if_sent_to_token(733			from.clone(),734			to,735			collection.id,736			token,737			nesting_budget,738		)?;739740		// =========741742		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);743744		<TokenData<T>>::insert(745			(collection.id, token),746			ItemData {747				owner: to.clone(),748				..token_data749			},750		);751752		if let Some(balance_to) = balance_to {753			// from != to754			if balance_from == 0 {755				<AccountBalance<T>>::remove((collection.id, from));756			} else {757				<AccountBalance<T>>::insert((collection.id, from), balance_from);758			}759			<AccountBalance<T>>::insert((collection.id, to), balance_to);760			<Owned<T>>::remove((collection.id, from, token));761			<Owned<T>>::insert((collection.id, to, token), true);762		}763		Self::set_allowance_unchecked(collection, from, token, None, true);764765		<PalletEvm<T>>::deposit_log(766			ERC721Events::Transfer {767				from: *from.as_eth(),768				to: *to.as_eth(),769				token_id: token.into(),770			}771			.to_log(collection_id_to_address(collection.id)),772		);773		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774			collection.id,775			token,776			from.clone(),777			to.clone(),778			1,779		));780		Ok(())781	}782783	pub fn create_multiple_items(784		collection: &NonfungibleHandle<T>,785		sender: &T::CrossAccountId,786		data: Vec<CreateItemData<T>>,787		nesting_budget: &dyn Budget,788	) -> DispatchResult {789		if !collection.is_owner_or_admin(sender) {790			ensure!(791				collection.permissions.mint_mode(),792				<CommonError<T>>::PublicMintingNotAllowed793			);794			collection.check_allowlist(sender)?;795796			for item in data.iter() {797				collection.check_allowlist(&item.owner)?;798			}799		}800801		for data in data.iter() {802			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;803		}804805		let first_token = <TokensMinted<T>>::get(collection.id);806		let tokens_minted = first_token807			.checked_add(data.len() as u32)808			.ok_or(ArithmeticError::Overflow)?;809		ensure!(810			tokens_minted <= collection.limits.token_limit(),811			<CommonError<T>>::CollectionTokenLimitExceeded812		);813814		let mut balances = BTreeMap::new();815		for data in &data {816			let balance = balances817				.entry(&data.owner)818				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));819			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;820821			ensure!(822				*balance <= collection.limits.account_token_ownership_limit(),823				<CommonError<T>>::AccountTokenLimitExceeded,824			);825		}826827		for (i, data) in data.iter().enumerate() {828			let token = TokenId(first_token + i as u32 + 1);829830			<PalletStructure<T>>::check_nesting(831				sender.clone(),832				&data.owner,833				collection.id,834				token,835				nesting_budget,836			)?;837		}838839		// =========840841		with_transaction(|| {842			for (i, data) in data.iter().enumerate() {843				let token = first_token + i as u32 + 1;844845				<TokenData<T>>::insert(846					(collection.id, token),847					ItemData {848						// const_data: data.const_data.clone(),849						owner: data.owner.clone(),850					},851				);852853				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(854					&data.owner,855					collection.id,856					TokenId(token),857				);858859				if let Err(e) = Self::set_token_properties(860					collection,861					sender,862					TokenId(token),863					data.properties.clone().into_iter(),864					true,865					nesting_budget,866				) {867					return TransactionOutcome::Rollback(Err(e));868				}869			}870			TransactionOutcome::Commit(Ok(()))871		})?;872873		<TokensMinted<T>>::insert(collection.id, tokens_minted);874		for (account, balance) in balances {875			<AccountBalance<T>>::insert((collection.id, account), balance);876		}877		for (i, data) in data.into_iter().enumerate() {878			let token = first_token + i as u32 + 1;879			<Owned<T>>::insert((collection.id, &data.owner, token), true);880881			<PalletEvm<T>>::deposit_log(882				ERC721Events::Transfer {883					from: H160::default(),884					to: *data.owner.as_eth(),885					token_id: token.into(),886				}887				.to_log(collection_id_to_address(collection.id)),888			);889			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(890				collection.id,891				TokenId(token),892				data.owner.clone(),893				1,894			));895		}896		Ok(())897	}898899	pub fn set_allowance_unchecked(900		collection: &NonfungibleHandle<T>,901		sender: &T::CrossAccountId,902		token: TokenId,903		spender: Option<&T::CrossAccountId>,904		assume_implicit_eth: bool,905	) {906		if let Some(spender) = spender {907			let old_spender = <Allowance<T>>::get((collection.id, token));908			<Allowance<T>>::insert((collection.id, token), spender);909			// In ERC721 there is only one possible approved user of token, so we set910			// approved user to spender911			<PalletEvm<T>>::deposit_log(912				ERC721Events::Approval {913					owner: *sender.as_eth(),914					approved: *spender.as_eth(),915					token_id: token.into(),916				}917				.to_log(collection_id_to_address(collection.id)),918			);919			// In Unique chain, any token can have any amount of approved users, so we need to920			// set allowance of old owner to 0, and allowance of new owner to 1921			if old_spender.as_ref() != Some(spender) {922				if let Some(old_owner) = old_spender {923					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(924						collection.id,925						token,926						sender.clone(),927						old_owner,928						0,929					));930				}931				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(932					collection.id,933					token,934					sender.clone(),935					spender.clone(),936					1,937				));938			}939		} else {940			let old_spender = <Allowance<T>>::take((collection.id, token));941			if !assume_implicit_eth {942				// In ERC721 there is only one possible approved user of token, so we set943				// approved user to zero address944				<PalletEvm<T>>::deposit_log(945					ERC721Events::Approval {946						owner: *sender.as_eth(),947						approved: H160::default(),948						token_id: token.into(),949					}950					.to_log(collection_id_to_address(collection.id)),951				);952			}953			// In Unique chain, any token can have any amount of approved users, so we need to954			// set allowance of old owner to 0955			if let Some(old_spender) = old_spender {956				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(957					collection.id,958					token,959					sender.clone(),960					old_spender,961					0,962				));963			}964		}965	}966967	pub fn set_allowance(968		collection: &NonfungibleHandle<T>,969		sender: &T::CrossAccountId,970		token: TokenId,971		spender: Option<&T::CrossAccountId>,972	) -> DispatchResult {973		if collection.permissions.access() == AccessMode::AllowList {974			collection.check_allowlist(sender)?;975			if let Some(spender) = spender {976				collection.check_allowlist(spender)?;977			}978		}979980		if let Some(spender) = spender {981			<PalletCommon<T>>::ensure_correct_receiver(spender)?;982		}983984		let token_data =985			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;986		if &token_data.owner != sender {987			ensure!(988				collection.ignores_owned_amount(sender),989				<CommonError<T>>::CantApproveMoreThanOwned990			);991		}992993		// =========994995		Self::set_allowance_unchecked(collection, sender, token, spender, false);996		Ok(())997	}998999	fn check_allowed(1000		collection: &NonfungibleHandle<T>,1001		spender: &T::CrossAccountId,1002		from: &T::CrossAccountId,1003		token: TokenId,1004		nesting_budget: &dyn Budget,1005	) -> DispatchResult {1006		if spender.conv_eq(from) {1007			return Ok(());1008		}1009		if collection.permissions.access() == AccessMode::AllowList {1010			// `from`, `to` checked in [`transfer`]1011			collection.check_allowlist(spender)?;1012		}10131014		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {1015			return Ok(());1016		}10171018		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1019			ensure!(1020				<PalletStructure<T>>::check_indirectly_owned(1021					spender.clone(),1022					source.0,1023					source.1,1024					None,1025					nesting_budget1026				)?,1027				<CommonError<T>>::ApprovedValueTooLow,1028			);1029			return Ok(());1030		}1031		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1032			return Ok(());1033		}1034		ensure!(1035			collection.ignores_allowance(spender),1036			<CommonError<T>>::ApprovedValueTooLow1037		);1038		Ok(())1039	}10401041	pub fn transfer_from(1042		collection: &NonfungibleHandle<T>,1043		spender: &T::CrossAccountId,1044		from: &T::CrossAccountId,1045		to: &T::CrossAccountId,1046		token: TokenId,1047		nesting_budget: &dyn Budget,1048	) -> DispatchResult {1049		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10501051		// =========10521053		// Allowance is reset in [`transfer`]1054		Self::transfer(collection, from, to, token, nesting_budget)1055	}10561057	pub fn burn_from(1058		collection: &NonfungibleHandle<T>,1059		spender: &T::CrossAccountId,1060		from: &T::CrossAccountId,1061		token: TokenId,1062		nesting_budget: &dyn Budget,1063	) -> DispatchResult {1064		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10651066		// =========10671068		Self::burn(collection, from, token)1069	}10701071	pub fn check_nesting(1072		handle: &NonfungibleHandle<T>,1073		sender: T::CrossAccountId,1074		from: (CollectionId, TokenId),1075		under: TokenId,1076		nesting_budget: &dyn Budget,1077	) -> DispatchResult {1078		let nesting = handle.permissions.nesting();10791080		#[cfg(not(feature = "runtime-benchmarks"))]1081		let permissive = false;1082		#[cfg(feature = "runtime-benchmarks")]1083		let permissive = nesting.permissive;10841085		if permissive {1086			// Pass1087		} else if nesting.token_owner1088			&& <PalletStructure<T>>::check_indirectly_owned(1089				sender.clone(),1090				handle.id,1091				under,1092				Some(from),1093				nesting_budget,1094			)? {1095			// Pass1096		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1097			// Pass1098		} else {1099			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1100		}11011102		if let Some(whitelist) = &nesting.restricted {1103			ensure!(1104				whitelist.contains(&from.0),1105				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1106			);1107		}1108		Ok(())1109	}11101111	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1112		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1113	}11141115	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1116		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1117	}11181119	fn collection_has_tokens(collection_id: CollectionId) -> bool {1120		<TokenData<T>>::iter_prefix((collection_id,))1121			.next()1122			.is_some()1123	}11241125	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1126		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1127			.next()1128			.is_some()1129	}11301131	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1132		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1133			.map(|((child_collection_id, child_id), _)| TokenChild {1134				collection: child_collection_id,1135				token: child_id,1136			})1137			.collect()1138	}11391140	/// Delegated to `create_multiple_items`1141	pub fn create_item(1142		collection: &NonfungibleHandle<T>,1143		sender: &T::CrossAccountId,1144		data: CreateItemData<T>,1145		nesting_budget: &dyn Budget,1146	) -> DispatchResult {1147		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1148	}1149}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -64,13 +64,13 @@
 	pub enum Error<T> {
 		/// Not Refungible item data used to mint in Refungible collection.
 		NotRefungibleDataUsedToMintFungibleCollectionToken,
-		/// Maximum refungibility exceeded
+		/// Maximum refungibility exceeded.
 		WrongRefungiblePieces,
-		/// Refungible token can't be repartitioned by user who isn't owns all pieces
+		/// Refungible token can't be repartitioned by user who isn't owns all pieces.
 		RepartitionWhileNotOwningAllPieces,
-		/// Refungible token can't nest other tokens
+		/// Refungible token can't nest other tokens.
 		RefungibleDisallowsNesting,
-		/// Setting item properties is not allowed
+		/// Setting item properties is not allowed.
 		SettingPropertiesNotAllowed,
 	}
 
@@ -605,7 +605,6 @@
 		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/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -31,7 +31,7 @@
 		DepthLimit,
 		/// While iterating over children, reached the breadth limit.
 		BreadthLimit,
-		/// Couldn't find the token owner that is a token. Perhaps, it does not yet exist. todo:doc? rephrase?
+		/// Couldn't find the token owner that is itself a token.
 		TokenNotFound,
 	}
 
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -286,7 +286,7 @@
 	}
 }
 
-/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version)
+/// 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> {
@@ -327,7 +327,7 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
-/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version)
+/// 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> {
@@ -568,7 +568,7 @@
 	ReFungible(CreateReFungibleData),
 }
 
-/// Explicit NFT creation data with meta parameters
+/// Explicit NFT creation data with meta parameters.
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug)]
 pub struct CreateNftExData<CrossAccountId> {
@@ -577,7 +577,7 @@
 	pub owner: CrossAccountId,
 }
 
-/// Explicit RFT creation data with meta parameters
+/// 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> {
@@ -587,7 +587,7 @@
 	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
 }
 
-/// Explicit item creation data with meta parameters, namely the owner
+/// 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> {
@@ -635,7 +635,7 @@
 	}
 }
 
-/// Token's address, dictated by its collection and token IDs
+/// 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