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

difftreelog

cargo fmt

Daniel Shiposha2022-06-29parent: #b12e489.patch.diff
in: master

1 file changed

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	PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild, AuxPropertyValue,32};33use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};34use pallet_common::{35	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,36	eth::collection_id_to_address,37};38use pallet_structure::{Pallet as PalletStructure, Error as StructureError};39use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};40use sp_core::H160;41use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};42use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};43use core::ops::Deref;44use codec::{Encode, Decode, MaxEncodedLen};45use scale_info::TypeInfo;4647pub use pallet::*;48use weights::WeightInfo;49#[cfg(feature = "runtime-benchmarks")]50pub mod benchmarking;51pub mod common;52pub mod erc;53pub mod weights;5455pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;56pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5758#[struct_versioning::versioned(version = 2, upper)]59#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]60pub struct ItemData<CrossAccountId> {61	#[version(..2)]62	pub const_data: BoundedVec<u8, CustomDataLimit>,6364	#[version(..2)]65	pub variable_data: BoundedVec<u8, CustomDataLimit>,6667	pub owner: CrossAccountId,68}6970#[frame_support::pallet]71pub mod pallet {72	use super::*;73	use frame_support::{74		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,75	};76	use frame_system::pallet_prelude::*;77	use up_data_structs::{CollectionId, TokenId};78	use super::weights::WeightInfo;7980	#[pallet::error]81	pub enum Error<T> {82		/// Not Nonfungible item data used to mint in Nonfungible collection.83		NotNonfungibleDataUsedToMintFungibleCollectionToken,84		/// Used amount > 1 with NFT85		NonfungibleItemsHaveNoAmount,86		/// Unable to burn NFT with children87		CantBurnNftWithChildren,88	}8990	#[pallet::config]91	pub trait Config:92		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config93	{94		type WeightInfo: WeightInfo;95	}9697	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);9899	#[pallet::pallet]100	#[pallet::storage_version(STORAGE_VERSION)]101	#[pallet::generate_store(pub(super) trait Store)]102	pub struct Pallet<T>(_);103104	#[pallet::storage]105	pub type TokensMinted<T: Config> =106		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;107	#[pallet::storage]108	pub type TokensBurnt<T: Config> =109		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;110111	#[pallet::storage]112	pub type TokenData<T: Config> = StorageNMap<113		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),114		Value = ItemData<T::CrossAccountId>,115		QueryKind = OptionQuery,116	>;117118	#[pallet::storage]119	#[pallet::getter(fn token_properties)]120	pub type TokenProperties<T: Config> = StorageNMap<121		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),122		Value = Properties,123		QueryKind = ValueQuery,124		OnEmpty = up_data_structs::TokenProperties,125	>;126127	#[pallet::storage]128	#[pallet::getter(fn token_aux_property)]129	pub type TokenAuxProperties<T: Config> = StorageNMap<130		Key = (131			Key<Twox64Concat, CollectionId>,132			Key<Twox64Concat, TokenId>,133			Key<Twox64Concat, PropertyScope>,134			Key<Twox64Concat, PropertyKey>,135		),136		Value = AuxPropertyValue,137		QueryKind = OptionQuery,138	>;139140	/// Used to enumerate tokens owned by account141	#[pallet::storage]142	pub type Owned<T: Config> = StorageNMap<143		Key = (144			Key<Twox64Concat, CollectionId>,145			Key<Blake2_128Concat, T::CrossAccountId>,146			Key<Twox64Concat, TokenId>,147		),148		Value = bool,149		QueryKind = ValueQuery,150	>;151152	/// Used to enumerate token's children153	#[pallet::storage]154	#[pallet::getter(fn token_children)]155	pub type TokenChildren<T: Config> = StorageNMap<156		Key = (157			Key<Twox64Concat, CollectionId>,158			Key<Twox64Concat, TokenId>,159			Key<Twox64Concat, (CollectionId, TokenId)>,160		),161		Value = bool,162		QueryKind = ValueQuery,163	>;164165	#[pallet::storage]166	pub type AccountBalance<T: Config> = StorageNMap<167		Key = (168			Key<Twox64Concat, CollectionId>,169			Key<Blake2_128Concat, T::CrossAccountId>,170		),171		Value = u32,172		QueryKind = ValueQuery,173	>;174175	#[pallet::storage]176	pub type Allowance<T: Config> = StorageNMap<177		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),178		Value = T::CrossAccountId,179		QueryKind = OptionQuery,180	>;181182	#[pallet::hooks]183	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {184		fn on_runtime_upgrade() -> Weight {185			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {186				let mut had_consts = BTreeSet::new();187				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(188					|(collection, token), v| {189						let mut props = vec![];190						if !v.const_data.is_empty() {191							props.push(Property {192								key: b"_old_constData".to_vec().try_into().unwrap(),193								value: v194									.const_data195									.clone()196									.into_inner()197									.try_into()198									.expect("const too long"),199							});200							had_consts.insert(collection);201						}202						if !v.variable_data.is_empty() {203							props.push(Property {204								key: b"_old_variableData".to_vec().try_into().unwrap(),205								value: v206									.variable_data207									.clone()208									.into_inner()209									.try_into()210									.expect("variable too long"),211							})212						}213						if !props.is_empty() {214							Self::set_scoped_token_properties(215								collection,216								token,217								PropertyScope::None,218								props.into_iter(),219							)220							.expect("existing token data exceeds property storage");221						}222						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))223					},224				);225				for collection in had_consts {226					<PalletCommon<T>>::set_property_permission_unchecked(227						collection,228						PropertyKeyPermission {229							key: b"_old_constData".to_vec().try_into().unwrap(),230							permission: PropertyPermission {231								mutable: false,232								collection_admin: true,233								token_owner: false,234							},235						},236					)237					.expect("failed to configure permission");238				}239			}240241			0242		}243	}244}245246pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);247impl<T: Config> NonfungibleHandle<T> {248	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {249		Self(inner)250	}251	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {252		self.0253	}254	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {255		&mut self.0256	}257}258impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {259	fn recorder(&self) -> &SubstrateRecorder<T> {260		self.0.recorder()261	}262	fn into_recorder(self) -> SubstrateRecorder<T> {263		self.0.into_recorder()264	}265}266impl<T: Config> Deref for NonfungibleHandle<T> {267	type Target = pallet_common::CollectionHandle<T>;268269	fn deref(&self) -> &Self::Target {270		&self.0271	}272}273274impl<T: Config> Pallet<T> {275	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {276		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)277	}278	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {279		<TokenData<T>>::contains_key((collection.id, token))280	}281282	pub fn set_scoped_token_property(283		collection_id: CollectionId,284		token_id: TokenId,285		scope: PropertyScope,286		property: Property,287	) -> DispatchResult {288		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {289			properties.try_scoped_set(scope, property.key, property.value)290		})291		.map_err(<CommonError<T>>::from)?;292293		Ok(())294	}295296	pub fn set_scoped_token_properties(297		collection_id: CollectionId,298		token_id: TokenId,299		scope: PropertyScope,300		properties: impl Iterator<Item = Property>,301	) -> DispatchResult {302		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {303			stored_properties.try_scoped_set_from_iter(scope, properties)304		})305		.map_err(<CommonError<T>>::from)?;306307		Ok(())308	}309310	pub fn try_mutate_token_aux_property<R, E>(311		collection_id: CollectionId,312		token_id: TokenId,313		scope: PropertyScope,314		key: PropertyKey,315		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,316	) -> Result<R, E> {317		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)318	}319320	pub fn remove_token_aux_property(321		collection_id: CollectionId,322		token_id: TokenId,323		scope: PropertyScope,324		key: PropertyKey,325	) {326		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));327	}328329	pub fn iterate_token_aux_properties(330		collection_id: CollectionId,331		token_id: TokenId,332		scope: PropertyScope,333	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {334		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))335	}336337	pub fn current_token_id(collection_id: CollectionId) -> TokenId {338		TokenId(<TokensMinted<T>>::get(collection_id))339	}340}341342// unchecked calls skips any permission checks343impl<T: Config> Pallet<T> {344	pub fn init_collection(345		owner: T::CrossAccountId,346		data: CreateCollectionData<T::AccountId>,347		is_external: bool,348	) -> Result<CollectionId, DispatchError> {349		<PalletCommon<T>>::init_collection(owner, data, is_external)350	}351	pub fn destroy_collection(352		collection: NonfungibleHandle<T>,353		sender: &T::CrossAccountId,354	) -> DispatchResult {355		let id = collection.id;356357		if Self::collection_has_tokens(id) {358			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());359		}360361		// =========362363		PalletCommon::destroy_collection(collection.0, sender)?;364365		<TokenData<T>>::remove_prefix((id,), None);366		<TokenChildren<T>>::remove_prefix((id,), None);367		<Owned<T>>::remove_prefix((id,), None);368		<TokensMinted<T>>::remove(id);369		<TokensBurnt<T>>::remove(id);370		<Allowance<T>>::remove_prefix((id,), None);371		<AccountBalance<T>>::remove_prefix((id,), None);372		Ok(())373	}374375	pub fn burn(376		collection: &NonfungibleHandle<T>,377		sender: &T::CrossAccountId,378		token: TokenId,379	) -> DispatchResult {380		let token_data =381			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;382		ensure!(383			&token_data.owner == sender,384			<CommonError<T>>::NoPermission385		);386387		if collection.permissions.access() == AccessMode::AllowList {388			collection.check_allowlist(sender)?;389		}390391		if Self::token_has_children(collection.id, token) {392			return Err(<Error<T>>::CantBurnNftWithChildren.into());393		}394395		let burnt = <TokensBurnt<T>>::get(collection.id)396			.checked_add(1)397			.ok_or(ArithmeticError::Overflow)?;398399		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))400			.checked_sub(1)401			.ok_or(ArithmeticError::Overflow)?;402403		// =========404405		if balance == 0 {406			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));407		} else {408			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);409		}410411		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);412413		<Owned<T>>::remove((collection.id, &token_data.owner, token));414		<TokensBurnt<T>>::insert(collection.id, burnt);415		<TokenData<T>>::remove((collection.id, token));416		<TokenProperties<T>>::remove((collection.id, token));417		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);418		let old_spender = <Allowance<T>>::take((collection.id, token));419420		if let Some(old_spender) = old_spender {421			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(422				collection.id,423				token,424				token_data.owner.clone(),425				old_spender,426				0,427			));428		}429430		<PalletEvm<T>>::deposit_log(431			ERC721Events::Transfer {432				from: *token_data.owner.as_eth(),433				to: H160::default(),434				token_id: token.into(),435			}436			.to_log(collection_id_to_address(collection.id)),437		);438		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(439			collection.id,440			token,441			token_data.owner,442			1,443		));444		Ok(())445	}446447	#[transactional]448	pub fn burn_recursively(449		collection: &NonfungibleHandle<T>,450		sender: &T::CrossAccountId,451		token: TokenId,452		self_budget: &dyn Budget,453		breadth_budget: &dyn Budget,454	) -> DispatchResultWithPostInfo {455		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);456457		let current_token_account =458			T::CrossTokenAddressMapping::token_to_address(collection.id, token);459460		let mut weight = 0 as Weight;461462		// This method is transactional, if user in fact doesn't have permissions to remove token -463		// tokens removed here will be restored after rejected transaction464		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {465			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);466			let PostDispatchInfo { actual_weight, .. } =467				<PalletStructure<T>>::burn_item_recursively(468					current_token_account.clone(),469					collection,470					token,471					self_budget,472					breadth_budget,473				)?;474			if let Some(actual_weight) = actual_weight {475				weight = weight.saturating_add(actual_weight);476			}477		}478479		Self::burn(collection, sender, token)?;480		DispatchResultWithPostInfo::Ok(PostDispatchInfo {481			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),482			pays_fee: Pays::Yes,483		})484	}485486	pub fn set_token_property(487		collection: &NonfungibleHandle<T>,488		sender: &T::CrossAccountId,489		token_id: TokenId,490		property: Property,491		is_token_create: bool,492	) -> DispatchResult {493		Self::check_token_change_permission(494			collection,495			sender,496			token_id,497			&property.key,498			is_token_create,499		)?;500501		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {502			let property = property.clone();503			properties.try_set(property.key, property.value)504		})505		.map_err(<CommonError<T>>::from)?;506507		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(508			collection.id,509			token_id,510			property.key,511		));512513		Ok(())514	}515516	#[transactional]517	pub fn set_token_properties(518		collection: &NonfungibleHandle<T>,519		sender: &T::CrossAccountId,520		token_id: TokenId,521		properties: Vec<Property>,522		is_token_create: bool,523	) -> DispatchResult {524		for property in properties {525			Self::set_token_property(collection, sender, token_id, property, is_token_create)?;526		}527528		Ok(())529	}530531	pub fn delete_token_property(532		collection: &NonfungibleHandle<T>,533		sender: &T::CrossAccountId,534		token_id: TokenId,535		property_key: PropertyKey,536	) -> DispatchResult {537		Self::check_token_change_permission(collection, sender, token_id, &property_key, false)?;538539		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {540			properties.remove(&property_key)541		})542		.map_err(<CommonError<T>>::from)?;543544		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(545			collection.id,546			token_id,547			property_key,548		));549550		Ok(())551	}552553	fn check_token_change_permission(554		collection: &NonfungibleHandle<T>,555		sender: &T::CrossAccountId,556		token_id: TokenId,557		property_key: &PropertyKey,558		is_token_create: bool,559	) -> DispatchResult {560		let permission = <PalletCommon<T>>::property_permissions(collection.id)561			.get(property_key)562			.cloned()563			.unwrap_or_else(PropertyPermission::none);564565		let token_data = <TokenData<T>>::get((collection.id, token_id))566			.ok_or(<CommonError<T>>::TokenNotFound)?;567568		let check_token_owner = || -> DispatchResult {569			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);570			Ok(())571		};572573		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))574			.get(property_key)575			.is_some();576577		match permission {578			PropertyPermission { mutable: false, .. } if is_property_exists => {579				Err(<CommonError<T>>::NoPermission.into())580			}581582			PropertyPermission {583				collection_admin,584				token_owner,585				..586			} => {587				//TODO: investigate threats during public minting.588				if is_token_create && (collection_admin || token_owner) {589					return Ok(());590				}591592				let mut check_result = Err(<CommonError<T>>::NoPermission.into());593594				if collection_admin {595					check_result = collection.check_is_owner_or_admin(sender);596				}597598				if token_owner {599					check_result.or_else(|_| check_token_owner())600				} else {601					check_result602				}603			}604		}605	}606607	#[transactional]608	pub fn delete_token_properties(609		collection: &NonfungibleHandle<T>,610		sender: &T::CrossAccountId,611		token_id: TokenId,612		property_keys: Vec<PropertyKey>,613	) -> DispatchResult {614		for key in property_keys {615			Self::delete_token_property(collection, sender, token_id, key)?;616		}617618		Ok(())619	}620621	pub fn set_collection_properties(622		collection: &NonfungibleHandle<T>,623		sender: &T::CrossAccountId,624		properties: Vec<Property>,625	) -> DispatchResult {626		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)627	}628629	pub fn delete_collection_properties(630		collection: &CollectionHandle<T>,631		sender: &T::CrossAccountId,632		property_keys: Vec<PropertyKey>,633	) -> DispatchResult {634		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)635	}636637	pub fn set_token_property_permissions(638		collection: &CollectionHandle<T>,639		sender: &T::CrossAccountId,640		property_permissions: Vec<PropertyKeyPermission>,641	) -> DispatchResult {642		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)643	}644645	pub fn set_property_permission(646		collection: &CollectionHandle<T>,647		sender: &T::CrossAccountId,648		permission: PropertyKeyPermission,649	) -> DispatchResult {650		<PalletCommon<T>>::set_property_permission(collection, sender, permission)651	}652653	pub fn transfer(654		collection: &NonfungibleHandle<T>,655		from: &T::CrossAccountId,656		to: &T::CrossAccountId,657		token: TokenId,658		nesting_budget: &dyn Budget,659	) -> DispatchResult {660		ensure!(661			collection.limits.transfers_enabled(),662			<CommonError<T>>::TransferNotAllowed663		);664665		let token_data =666			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;667		ensure!(668			&token_data.owner == from,669			<CommonError<T>>::NoPermission670		);671672		if collection.permissions.access() == AccessMode::AllowList {673			collection.check_allowlist(from)?;674			collection.check_allowlist(to)?;675		}676		<PalletCommon<T>>::ensure_correct_receiver(to)?;677678		let balance_from = <AccountBalance<T>>::get((collection.id, from))679			.checked_sub(1)680			.ok_or(<CommonError<T>>::TokenValueTooLow)?;681		let balance_to = if from != to {682			let balance_to = <AccountBalance<T>>::get((collection.id, to))683				.checked_add(1)684				.ok_or(ArithmeticError::Overflow)?;685686			ensure!(687				balance_to < collection.limits.account_token_ownership_limit(),688				<CommonError<T>>::AccountTokenLimitExceeded,689			);690691			Some(balance_to)692		} else {693			None694		};695696		<PalletStructure<T>>::nest_if_sent_to_token(697			from.clone(),698			to,699			collection.id,700			token,701			nesting_budget,702		)?;703704		// =========705706		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);707708		<TokenData<T>>::insert(709			(collection.id, token),710			ItemData {711				owner: to.clone(),712				..token_data713			},714		);715716		if let Some(balance_to) = balance_to {717			// from != to718			if balance_from == 0 {719				<AccountBalance<T>>::remove((collection.id, from));720			} else {721				<AccountBalance<T>>::insert((collection.id, from), balance_from);722			}723			<AccountBalance<T>>::insert((collection.id, to), balance_to);724			<Owned<T>>::remove((collection.id, from, token));725			<Owned<T>>::insert((collection.id, to, token), true);726		}727		Self::set_allowance_unchecked(collection, from, token, None, true);728729		<PalletEvm<T>>::deposit_log(730			ERC721Events::Transfer {731				from: *from.as_eth(),732				to: *to.as_eth(),733				token_id: token.into(),734			}735			.to_log(collection_id_to_address(collection.id)),736		);737		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(738			collection.id,739			token,740			from.clone(),741			to.clone(),742			1,743		));744		Ok(())745	}746747	pub fn create_multiple_items(748		collection: &NonfungibleHandle<T>,749		sender: &T::CrossAccountId,750		data: Vec<CreateItemData<T>>,751		nesting_budget: &dyn Budget,752	) -> DispatchResult {753		if !collection.is_owner_or_admin(sender) {754			ensure!(755				collection.permissions.mint_mode(),756				<CommonError<T>>::PublicMintingNotAllowed757			);758			collection.check_allowlist(sender)?;759760			for item in data.iter() {761				collection.check_allowlist(&item.owner)?;762			}763		}764765		for data in data.iter() {766			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;767		}768769		let first_token = <TokensMinted<T>>::get(collection.id);770		let tokens_minted = first_token771			.checked_add(data.len() as u32)772			.ok_or(ArithmeticError::Overflow)?;773		ensure!(774			tokens_minted <= collection.limits.token_limit(),775			<CommonError<T>>::CollectionTokenLimitExceeded776		);777778		let mut balances = BTreeMap::new();779		for data in &data {780			let balance = balances781				.entry(&data.owner)782				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));783			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;784785			ensure!(786				*balance <= collection.limits.account_token_ownership_limit(),787				<CommonError<T>>::AccountTokenLimitExceeded,788			);789		}790791		for (i, data) in data.iter().enumerate() {792			let token = TokenId(first_token + i as u32 + 1);793794			<PalletStructure<T>>::check_nesting(795				sender.clone(),796				&data.owner,797				collection.id,798				token,799				nesting_budget,800			)?;801		}802803		// =========804805		with_transaction(|| {806			for (i, data) in data.iter().enumerate() {807				let token = first_token + i as u32 + 1;808809				<TokenData<T>>::insert(810					(collection.id, token),811					ItemData {812						// const_data: data.const_data.clone(),813						owner: data.owner.clone(),814					},815				);816817				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(818					&data.owner,819					collection.id,820					TokenId(token),821				);822823				if let Err(e) = Self::set_token_properties(824					collection,825					sender,826					TokenId(token),827					data.properties.clone().into_inner(),828					true,829				) {830					return TransactionOutcome::Rollback(Err(e));831				}832			}833			TransactionOutcome::Commit(Ok(()))834		})?;835836		<TokensMinted<T>>::insert(collection.id, tokens_minted);837		for (account, balance) in balances {838			<AccountBalance<T>>::insert((collection.id, account), balance);839		}840		for (i, data) in data.into_iter().enumerate() {841			let token = first_token + i as u32 + 1;842			<Owned<T>>::insert((collection.id, &data.owner, token), true);843844			<PalletEvm<T>>::deposit_log(845				ERC721Events::Transfer {846					from: H160::default(),847					to: *data.owner.as_eth(),848					token_id: token.into(),849				}850				.to_log(collection_id_to_address(collection.id)),851			);852			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(853				collection.id,854				TokenId(token),855				data.owner.clone(),856				1,857			));858		}859		Ok(())860	}861862	pub fn set_allowance_unchecked(863		collection: &NonfungibleHandle<T>,864		sender: &T::CrossAccountId,865		token: TokenId,866		spender: Option<&T::CrossAccountId>,867		assume_implicit_eth: bool,868	) {869		if let Some(spender) = spender {870			let old_spender = <Allowance<T>>::get((collection.id, token));871			<Allowance<T>>::insert((collection.id, token), spender);872			// In ERC721 there is only one possible approved user of token, so we set873			// approved user to spender874			<PalletEvm<T>>::deposit_log(875				ERC721Events::Approval {876					owner: *sender.as_eth(),877					approved: *spender.as_eth(),878					token_id: token.into(),879				}880				.to_log(collection_id_to_address(collection.id)),881			);882			// In Unique chain, any token can have any amount of approved users, so we need to883			// set allowance of old owner to 0, and allowance of new owner to 1884			if old_spender.as_ref() != Some(spender) {885				if let Some(old_owner) = old_spender {886					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(887						collection.id,888						token,889						sender.clone(),890						old_owner,891						0,892					));893				}894				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(895					collection.id,896					token,897					sender.clone(),898					spender.clone(),899					1,900				));901			}902		} else {903			let old_spender = <Allowance<T>>::take((collection.id, token));904			if !assume_implicit_eth {905				// In ERC721 there is only one possible approved user of token, so we set906				// approved user to zero address907				<PalletEvm<T>>::deposit_log(908					ERC721Events::Approval {909						owner: *sender.as_eth(),910						approved: H160::default(),911						token_id: token.into(),912					}913					.to_log(collection_id_to_address(collection.id)),914				);915			}916			// In Unique chain, any token can have any amount of approved users, so we need to917			// set allowance of old owner to 0918			if let Some(old_spender) = old_spender {919				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(920					collection.id,921					token,922					sender.clone(),923					old_spender,924					0,925				));926			}927		}928	}929930	pub fn set_allowance(931		collection: &NonfungibleHandle<T>,932		sender: &T::CrossAccountId,933		token: TokenId,934		spender: Option<&T::CrossAccountId>,935	) -> DispatchResult {936		if collection.permissions.access() == AccessMode::AllowList {937			collection.check_allowlist(sender)?;938			if let Some(spender) = spender {939				collection.check_allowlist(spender)?;940			}941		}942943		if let Some(spender) = spender {944			<PalletCommon<T>>::ensure_correct_receiver(spender)?;945		}946947		let token_data =948			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;949		if &token_data.owner != sender {950			ensure!(951				collection.ignores_owned_amount(sender),952				<CommonError<T>>::CantApproveMoreThanOwned953			);954		}955956		// =========957958		Self::set_allowance_unchecked(collection, sender, token, spender, false);959		Ok(())960	}961962	fn check_allowed(963		collection: &NonfungibleHandle<T>,964		spender: &T::CrossAccountId,965		from: &T::CrossAccountId,966		token: TokenId,967		nesting_budget: &dyn Budget,968	) -> DispatchResult {969		if spender.conv_eq(from) {970			return Ok(());971		}972		if collection.permissions.access() == AccessMode::AllowList {973			// `from`, `to` checked in [`transfer`]974			collection.check_allowlist(spender)?;975		}976977		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {978			return Ok(());979		}980981		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {982			ensure!(983				<PalletStructure<T>>::check_indirectly_owned(984					spender.clone(),985					source.0,986					source.1,987					None,988					nesting_budget989				)?,990				<CommonError<T>>::ApprovedValueTooLow,991			);992			return Ok(());993		}994		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {995			return Ok(());996		}997		ensure!(998			collection.ignores_allowance(spender),999			<CommonError<T>>::ApprovedValueTooLow1000		);1001		Ok(())1002	}10031004	pub fn transfer_from(1005		collection: &NonfungibleHandle<T>,1006		spender: &T::CrossAccountId,1007		from: &T::CrossAccountId,1008		to: &T::CrossAccountId,1009		token: TokenId,1010		nesting_budget: &dyn Budget,1011	) -> DispatchResult {1012		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10131014		// =========10151016		// Allowance is reset in [`transfer`]1017		Self::transfer(collection, from, to, token, nesting_budget)1018	}10191020	pub fn burn_from(1021		collection: &NonfungibleHandle<T>,1022		spender: &T::CrossAccountId,1023		from: &T::CrossAccountId,1024		token: TokenId,1025		nesting_budget: &dyn Budget,1026	) -> DispatchResult {1027		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10281029		// =========10301031		Self::burn(collection, from, token)1032	}10331034	pub fn check_nesting(1035		handle: &NonfungibleHandle<T>,1036		sender: T::CrossAccountId,1037		from: (CollectionId, TokenId),1038		under: TokenId,1039		nesting_budget: &dyn Budget,1040	) -> DispatchResult {1041		let nesting = handle.permissions.nesting();10421043		#[cfg(not(feature = "runtime-benchmarks"))]1044		let permissive = false;1045		#[cfg(feature = "runtime-benchmarks")]1046		let permissive = nesting.permissive;10471048		if permissive {1049			// Pass1050		} else if nesting.token_owner1051			&& <PalletStructure<T>>::check_indirectly_owned(1052				sender.clone(),1053				handle.id,1054				under,1055				Some(from),1056				nesting_budget,1057			)? {1058			// Pass1059		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1060			// Pass1061		} else {1062			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1063		}10641065		if let Some(whitelist) = &nesting.restricted {1066			ensure!(1067				whitelist.contains(&from.0),1068				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1069			);1070		}1071		Ok(())1072	}10731074	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1075		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1076	}10771078	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1079		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1080	}10811082	fn collection_has_tokens(collection_id: CollectionId) -> bool {1083		<TokenData<T>>::iter_prefix((collection_id,))1084			.next()1085			.is_some()1086	}10871088	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1089		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1090			.next()1091			.is_some()1092	}10931094	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1095		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1096			.map(|((child_collection_id, child_id), _)| TokenChild {1097				collection: child_collection_id,1098				token: child_id,1099			})1100			.collect()1101	}11021103	/// Delegated to `create_multiple_items`1104	pub fn create_item(1105		collection: &NonfungibleHandle<T>,1106		sender: &T::CrossAccountId,1107		data: CreateItemData<T>,1108		nesting_budget: &dyn Budget,1109	) -> DispatchResult {1110		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1111	}1112}