git.delta.rocks / unique-network / refs/commits / 5e5dd9227b0d

difftreelog

source

pallets/nonfungible/src/lib.rs29.0 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31	PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32	AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37	eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859#[struct_versioning::versioned(version = 2, upper)]60#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]61pub struct ItemData<CrossAccountId> {62	#[version(..2)]63	pub const_data: BoundedVec<u8, CustomDataLimit>,6465	#[version(..2)]66	pub variable_data: BoundedVec<u8, CustomDataLimit>,6768	pub owner: CrossAccountId,69}7071#[frame_support::pallet]72pub mod pallet {73	use super::*;74	use frame_support::{75		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,76	};77	use frame_system::pallet_prelude::*;78	use up_data_structs::{CollectionId, TokenId};79	use super::weights::WeightInfo;8081	#[pallet::error]82	pub enum Error<T> {83		/// Not Nonfungible item data used to mint in Nonfungible collection.84		NotNonfungibleDataUsedToMintFungibleCollectionToken,85		/// Used amount > 1 with NFT86		NonfungibleItemsHaveNoAmount,87		/// Unable to burn NFT with children88		CantBurnNftWithChildren,89	}9091	#[pallet::config]92	pub trait Config:93		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config94	{95		type WeightInfo: WeightInfo;96	}9798	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);99100	#[pallet::pallet]101	#[pallet::storage_version(STORAGE_VERSION)]102	#[pallet::generate_store(pub(super) trait Store)]103	pub struct Pallet<T>(_);104105	#[pallet::storage]106	pub type TokensMinted<T: Config> =107		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;108	#[pallet::storage]109	pub type TokensBurnt<T: Config> =110		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112	#[pallet::storage]113	pub type TokenData<T: Config> = StorageNMap<114		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),115		Value = ItemData<T::CrossAccountId>,116		QueryKind = OptionQuery,117	>;118119	#[pallet::storage]120	#[pallet::getter(fn token_properties)]121	pub type TokenProperties<T: Config> = StorageNMap<122		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),123		Value = Properties,124		QueryKind = ValueQuery,125		OnEmpty = up_data_structs::TokenProperties,126	>;127128	#[pallet::storage]129	#[pallet::getter(fn token_aux_property)]130	pub type TokenAuxProperties<T: Config> = StorageNMap<131		Key = (132			Key<Twox64Concat, CollectionId>,133			Key<Twox64Concat, TokenId>,134			Key<Twox64Concat, PropertyScope>,135			Key<Twox64Concat, PropertyKey>,136		),137		Value = AuxPropertyValue,138		QueryKind = OptionQuery,139	>;140141	/// Used to enumerate tokens owned by account142	#[pallet::storage]143	pub type Owned<T: Config> = StorageNMap<144		Key = (145			Key<Twox64Concat, CollectionId>,146			Key<Blake2_128Concat, T::CrossAccountId>,147			Key<Twox64Concat, TokenId>,148		),149		Value = bool,150		QueryKind = ValueQuery,151	>;152153	/// Used to enumerate token's children154	#[pallet::storage]155	#[pallet::getter(fn token_children)]156	pub type TokenChildren<T: Config> = StorageNMap<157		Key = (158			Key<Twox64Concat, CollectionId>,159			Key<Twox64Concat, TokenId>,160			Key<Twox64Concat, (CollectionId, TokenId)>,161		),162		Value = bool,163		QueryKind = ValueQuery,164	>;165166	#[pallet::storage]167	pub type AccountBalance<T: Config> = StorageNMap<168		Key = (169			Key<Twox64Concat, CollectionId>,170			Key<Blake2_128Concat, T::CrossAccountId>,171		),172		Value = u32,173		QueryKind = ValueQuery,174	>;175176	#[pallet::storage]177	pub type Allowance<T: Config> = StorageNMap<178		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),179		Value = T::CrossAccountId,180		QueryKind = OptionQuery,181	>;182183	#[pallet::hooks]184	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {185		fn on_runtime_upgrade() -> Weight {186			StorageVersion::new(1).put::<Pallet<T>>();187188			0189		}190	}191}192193pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);194impl<T: Config> NonfungibleHandle<T> {195	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {196		Self(inner)197	}198	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {199		self.0200	}201	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {202		&mut self.0203	}204}205impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {206	fn recorder(&self) -> &SubstrateRecorder<T> {207		self.0.recorder()208	}209	fn into_recorder(self) -> SubstrateRecorder<T> {210		self.0.into_recorder()211	}212}213impl<T: Config> Deref for NonfungibleHandle<T> {214	type Target = pallet_common::CollectionHandle<T>;215216	fn deref(&self) -> &Self::Target {217		&self.0218	}219}220221impl<T: Config> Pallet<T> {222	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {223		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)224	}225	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {226		<TokenData<T>>::contains_key((collection.id, token))227	}228229	pub fn set_scoped_token_property(230		collection_id: CollectionId,231		token_id: TokenId,232		scope: PropertyScope,233		property: Property,234	) -> DispatchResult {235		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {236			properties.try_scoped_set(scope, property.key, property.value)237		})238		.map_err(<CommonError<T>>::from)?;239240		Ok(())241	}242243	pub fn set_scoped_token_properties(244		collection_id: CollectionId,245		token_id: TokenId,246		scope: PropertyScope,247		properties: impl Iterator<Item = Property>,248	) -> DispatchResult {249		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {250			stored_properties.try_scoped_set_from_iter(scope, properties)251		})252		.map_err(<CommonError<T>>::from)?;253254		Ok(())255	}256257	pub fn try_mutate_token_aux_property<R, E>(258		collection_id: CollectionId,259		token_id: TokenId,260		scope: PropertyScope,261		key: PropertyKey,262		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,263	) -> Result<R, E> {264		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)265	}266267	pub fn remove_token_aux_property(268		collection_id: CollectionId,269		token_id: TokenId,270		scope: PropertyScope,271		key: PropertyKey,272	) {273		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));274	}275276	pub fn iterate_token_aux_properties(277		collection_id: CollectionId,278		token_id: TokenId,279		scope: PropertyScope,280	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {281		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))282	}283284	pub fn current_token_id(collection_id: CollectionId) -> TokenId {285		TokenId(<TokensMinted<T>>::get(collection_id))286	}287}288289// unchecked calls skips any permission checks290impl<T: Config> Pallet<T> {291	pub fn init_collection(292		owner: T::CrossAccountId,293		data: CreateCollectionData<T::AccountId>,294		is_external: bool,295	) -> Result<CollectionId, DispatchError> {296		<PalletCommon<T>>::init_collection(owner, data, is_external)297	}298	pub fn destroy_collection(299		collection: NonfungibleHandle<T>,300		sender: &T::CrossAccountId,301	) -> DispatchResult {302		let id = collection.id;303304		if Self::collection_has_tokens(id) {305			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());306		}307308		// =========309310		PalletCommon::destroy_collection(collection.0, sender)?;311312		<TokenData<T>>::remove_prefix((id,), None);313		<TokenChildren<T>>::remove_prefix((id,), None);314		<Owned<T>>::remove_prefix((id,), None);315		<TokensMinted<T>>::remove(id);316		<TokensBurnt<T>>::remove(id);317		<Allowance<T>>::remove_prefix((id,), None);318		<AccountBalance<T>>::remove_prefix((id,), None);319		Ok(())320	}321322	pub fn burn(323		collection: &NonfungibleHandle<T>,324		sender: &T::CrossAccountId,325		token: TokenId,326	) -> DispatchResult {327		let token_data =328			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;329		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);330331		if collection.permissions.access() == AccessMode::AllowList {332			collection.check_allowlist(sender)?;333		}334335		if Self::token_has_children(collection.id, token) {336			return Err(<Error<T>>::CantBurnNftWithChildren.into());337		}338339		let burnt = <TokensBurnt<T>>::get(collection.id)340			.checked_add(1)341			.ok_or(ArithmeticError::Overflow)?;342343		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))344			.checked_sub(1)345			.ok_or(ArithmeticError::Overflow)?;346347		// =========348349		if balance == 0 {350			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));351		} else {352			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);353		}354355		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);356357		<Owned<T>>::remove((collection.id, &token_data.owner, token));358		<TokensBurnt<T>>::insert(collection.id, burnt);359		<TokenData<T>>::remove((collection.id, token));360		<TokenProperties<T>>::remove((collection.id, token));361		<TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);362		let old_spender = <Allowance<T>>::take((collection.id, token));363364		if let Some(old_spender) = old_spender {365			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(366				collection.id,367				token,368				token_data.owner.clone(),369				old_spender,370				0,371			));372		}373374		<PalletEvm<T>>::deposit_log(375			ERC721Events::Transfer {376				from: *token_data.owner.as_eth(),377				to: H160::default(),378				token_id: token.into(),379			}380			.to_log(collection_id_to_address(collection.id)),381		);382		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(383			collection.id,384			token,385			token_data.owner,386			1,387		));388		Ok(())389	}390391	#[transactional]392	pub fn burn_recursively(393		collection: &NonfungibleHandle<T>,394		sender: &T::CrossAccountId,395		token: TokenId,396		self_budget: &dyn Budget,397		breadth_budget: &dyn Budget,398	) -> DispatchResultWithPostInfo {399		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);400401		let current_token_account =402			T::CrossTokenAddressMapping::token_to_address(collection.id, token);403404		let mut weight = 0 as Weight;405406		// This method is transactional, if user in fact doesn't have permissions to remove token -407		// tokens removed here will be restored after rejected transaction408		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {409			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);410			let PostDispatchInfo { actual_weight, .. } =411				<PalletStructure<T>>::burn_item_recursively(412					current_token_account.clone(),413					collection,414					token,415					self_budget,416					breadth_budget,417				)?;418			if let Some(actual_weight) = actual_weight {419				weight = weight.saturating_add(actual_weight);420			}421		}422423		Self::burn(collection, sender, token)?;424		DispatchResultWithPostInfo::Ok(PostDispatchInfo {425			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),426			pays_fee: Pays::Yes,427		})428	}429430	#[transactional]431	fn modify_token_properties(432		collection: &NonfungibleHandle<T>,433		sender: &T::CrossAccountId,434		token_id: TokenId,435		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,436		is_token_create: bool,437		nesting_budget: &dyn Budget,438	) -> DispatchResult {439		let mut collection_admin_status = None;440		let mut token_owner_result = None;441442		let mut is_collection_admin =443			|| *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));444445		let mut is_token_owner = || {446			*token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {447				let is_owned = <PalletStructure<T>>::check_indirectly_owned(448					sender.clone(),449					collection.id,450					token_id,451					None,452					nesting_budget,453				)?;454455				Ok(is_owned)456			})457		};458459		for (key, value) in properties {460			let permission = <PalletCommon<T>>::property_permissions(collection.id)461				.get(&key)462				.cloned()463				.unwrap_or_else(PropertyPermission::none);464465			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))466				.get(&key)467				.is_some();468469			match permission {470				PropertyPermission { mutable: false, .. } if is_property_exists => {471					return Err(<CommonError<T>>::NoPermission.into());472				}473474				PropertyPermission {475					collection_admin,476					token_owner,477					..478				} => {479					//TODO: investigate threats during public minting.480					if is_token_create && (collection_admin || token_owner) && value.is_some() {481						// Pass482					} else if collection_admin && is_collection_admin() {483						// Pass484					} else if token_owner && is_token_owner()? {485						// Pass486					} else {487						fail!(<CommonError<T>>::NoPermission);488					}489				}490			}491492			match value {493				Some(value) => {494					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {495						properties.try_set(key.clone(), value)496					})497					.map_err(<CommonError<T>>::from)?;498499					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(500						collection.id,501						token_id,502						key,503					));504				}505				None => {506					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {507						properties.remove(&key)508					})509					.map_err(<CommonError<T>>::from)?;510511					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(512						collection.id,513						token_id,514						key,515					));516				}517			}518		}519520		Ok(())521	}522523	pub fn set_token_properties(524		collection: &NonfungibleHandle<T>,525		sender: &T::CrossAccountId,526		token_id: TokenId,527		properties: impl Iterator<Item = Property>,528		is_token_create: bool,529		nesting_budget: &dyn Budget,530	) -> DispatchResult {531		Self::modify_token_properties(532			collection,533			sender,534			token_id,535			properties.map(|p| (p.key, Some(p.value))),536			is_token_create,537			nesting_budget,538		)539	}540541	pub fn set_token_property(542		collection: &NonfungibleHandle<T>,543		sender: &T::CrossAccountId,544		token_id: TokenId,545		property: Property,546		nesting_budget: &dyn Budget,547	) -> DispatchResult {548		let is_token_create = false;549550		Self::set_token_properties(551			collection,552			sender,553			token_id,554			[property].into_iter(),555			is_token_create,556			nesting_budget,557		)558	}559560	pub fn delete_token_properties(561		collection: &NonfungibleHandle<T>,562		sender: &T::CrossAccountId,563		token_id: TokenId,564		property_keys: impl Iterator<Item = PropertyKey>,565		nesting_budget: &dyn Budget,566	) -> DispatchResult {567		let is_token_create = false;568569		Self::modify_token_properties(570			collection,571			sender,572			token_id,573			property_keys.into_iter().map(|key| (key, None)),574			is_token_create,575			nesting_budget,576		)577	}578579	pub fn delete_token_property(580		collection: &NonfungibleHandle<T>,581		sender: &T::CrossAccountId,582		token_id: TokenId,583		property_key: PropertyKey,584		nesting_budget: &dyn Budget,585	) -> DispatchResult {586		Self::delete_token_properties(587			collection,588			sender,589			token_id,590			[property_key].into_iter(),591			nesting_budget,592		)593	}594595	pub fn set_collection_properties(596		collection: &NonfungibleHandle<T>,597		sender: &T::CrossAccountId,598		properties: Vec<Property>,599	) -> DispatchResult {600		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)601	}602603	pub fn delete_collection_properties(604		collection: &CollectionHandle<T>,605		sender: &T::CrossAccountId,606		property_keys: Vec<PropertyKey>,607	) -> DispatchResult {608		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)609	}610611	pub fn set_token_property_permissions(612		collection: &CollectionHandle<T>,613		sender: &T::CrossAccountId,614		property_permissions: Vec<PropertyKeyPermission>,615	) -> DispatchResult {616		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)617	}618619	pub fn set_property_permission(620		collection: &CollectionHandle<T>,621		sender: &T::CrossAccountId,622		permission: PropertyKeyPermission,623	) -> DispatchResult {624		<PalletCommon<T>>::set_property_permission(collection, sender, permission)625	}626627	pub fn transfer(628		collection: &NonfungibleHandle<T>,629		from: &T::CrossAccountId,630		to: &T::CrossAccountId,631		token: TokenId,632		nesting_budget: &dyn Budget,633	) -> DispatchResult {634		ensure!(635			collection.limits.transfers_enabled(),636			<CommonError<T>>::TransferNotAllowed637		);638639		let token_data =640			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;641		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);642643		if collection.permissions.access() == AccessMode::AllowList {644			collection.check_allowlist(from)?;645			collection.check_allowlist(to)?;646		}647		<PalletCommon<T>>::ensure_correct_receiver(to)?;648649		let balance_from = <AccountBalance<T>>::get((collection.id, from))650			.checked_sub(1)651			.ok_or(<CommonError<T>>::TokenValueTooLow)?;652		let balance_to = if from != to {653			let balance_to = <AccountBalance<T>>::get((collection.id, to))654				.checked_add(1)655				.ok_or(ArithmeticError::Overflow)?;656657			ensure!(658				balance_to < collection.limits.account_token_ownership_limit(),659				<CommonError<T>>::AccountTokenLimitExceeded,660			);661662			Some(balance_to)663		} else {664			None665		};666667		<PalletStructure<T>>::nest_if_sent_to_token(668			from.clone(),669			to,670			collection.id,671			token,672			nesting_budget,673		)?;674675		// =========676677		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);678679		<TokenData<T>>::insert(680			(collection.id, token),681			ItemData {682				owner: to.clone(),683				..token_data684			},685		);686687		if let Some(balance_to) = balance_to {688			// from != to689			if balance_from == 0 {690				<AccountBalance<T>>::remove((collection.id, from));691			} else {692				<AccountBalance<T>>::insert((collection.id, from), balance_from);693			}694			<AccountBalance<T>>::insert((collection.id, to), balance_to);695			<Owned<T>>::remove((collection.id, from, token));696			<Owned<T>>::insert((collection.id, to, token), true);697		}698		Self::set_allowance_unchecked(collection, from, token, None, true);699700		<PalletEvm<T>>::deposit_log(701			ERC721Events::Transfer {702				from: *from.as_eth(),703				to: *to.as_eth(),704				token_id: token.into(),705			}706			.to_log(collection_id_to_address(collection.id)),707		);708		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(709			collection.id,710			token,711			from.clone(),712			to.clone(),713			1,714		));715		Ok(())716	}717718	pub fn create_multiple_items(719		collection: &NonfungibleHandle<T>,720		sender: &T::CrossAccountId,721		data: Vec<CreateItemData<T>>,722		nesting_budget: &dyn Budget,723	) -> DispatchResult {724		if !collection.is_owner_or_admin(sender) {725			ensure!(726				collection.permissions.mint_mode(),727				<CommonError<T>>::PublicMintingNotAllowed728			);729			collection.check_allowlist(sender)?;730731			for item in data.iter() {732				collection.check_allowlist(&item.owner)?;733			}734		}735736		for data in data.iter() {737			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;738		}739740		let first_token = <TokensMinted<T>>::get(collection.id);741		let tokens_minted = first_token742			.checked_add(data.len() as u32)743			.ok_or(ArithmeticError::Overflow)?;744		ensure!(745			tokens_minted <= collection.limits.token_limit(),746			<CommonError<T>>::CollectionTokenLimitExceeded747		);748749		let mut balances = BTreeMap::new();750		for data in &data {751			let balance = balances752				.entry(&data.owner)753				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));754			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;755756			ensure!(757				*balance <= collection.limits.account_token_ownership_limit(),758				<CommonError<T>>::AccountTokenLimitExceeded,759			);760		}761762		for (i, data) in data.iter().enumerate() {763			let token = TokenId(first_token + i as u32 + 1);764765			<PalletStructure<T>>::check_nesting(766				sender.clone(),767				&data.owner,768				collection.id,769				token,770				nesting_budget,771			)?;772		}773774		// =========775776		with_transaction(|| {777			for (i, data) in data.iter().enumerate() {778				let token = first_token + i as u32 + 1;779780				<TokenData<T>>::insert(781					(collection.id, token),782					ItemData {783						// const_data: data.const_data.clone(),784						owner: data.owner.clone(),785					},786				);787788				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(789					&data.owner,790					collection.id,791					TokenId(token),792				);793794				if let Err(e) = Self::set_token_properties(795					collection,796					sender,797					TokenId(token),798					data.properties.clone().into_iter(),799					true,800					nesting_budget,801				) {802					return TransactionOutcome::Rollback(Err(e));803				}804			}805			TransactionOutcome::Commit(Ok(()))806		})?;807808		<TokensMinted<T>>::insert(collection.id, tokens_minted);809		for (account, balance) in balances {810			<AccountBalance<T>>::insert((collection.id, account), balance);811		}812		for (i, data) in data.into_iter().enumerate() {813			let token = first_token + i as u32 + 1;814			<Owned<T>>::insert((collection.id, &data.owner, token), true);815816			<PalletEvm<T>>::deposit_log(817				ERC721Events::Transfer {818					from: H160::default(),819					to: *data.owner.as_eth(),820					token_id: token.into(),821				}822				.to_log(collection_id_to_address(collection.id)),823			);824			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(825				collection.id,826				TokenId(token),827				data.owner.clone(),828				1,829			));830		}831		Ok(())832	}833834	pub fn set_allowance_unchecked(835		collection: &NonfungibleHandle<T>,836		sender: &T::CrossAccountId,837		token: TokenId,838		spender: Option<&T::CrossAccountId>,839		assume_implicit_eth: bool,840	) {841		if let Some(spender) = spender {842			let old_spender = <Allowance<T>>::get((collection.id, token));843			<Allowance<T>>::insert((collection.id, token), spender);844			// In ERC721 there is only one possible approved user of token, so we set845			// approved user to spender846			<PalletEvm<T>>::deposit_log(847				ERC721Events::Approval {848					owner: *sender.as_eth(),849					approved: *spender.as_eth(),850					token_id: token.into(),851				}852				.to_log(collection_id_to_address(collection.id)),853			);854			// In Unique chain, any token can have any amount of approved users, so we need to855			// set allowance of old owner to 0, and allowance of new owner to 1856			if old_spender.as_ref() != Some(spender) {857				if let Some(old_owner) = old_spender {858					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(859						collection.id,860						token,861						sender.clone(),862						old_owner,863						0,864					));865				}866				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(867					collection.id,868					token,869					sender.clone(),870					spender.clone(),871					1,872				));873			}874		} else {875			let old_spender = <Allowance<T>>::take((collection.id, token));876			if !assume_implicit_eth {877				// In ERC721 there is only one possible approved user of token, so we set878				// approved user to zero address879				<PalletEvm<T>>::deposit_log(880					ERC721Events::Approval {881						owner: *sender.as_eth(),882						approved: H160::default(),883						token_id: token.into(),884					}885					.to_log(collection_id_to_address(collection.id)),886				);887			}888			// In Unique chain, any token can have any amount of approved users, so we need to889			// set allowance of old owner to 0890			if let Some(old_spender) = old_spender {891				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(892					collection.id,893					token,894					sender.clone(),895					old_spender,896					0,897				));898			}899		}900	}901902	pub fn set_allowance(903		collection: &NonfungibleHandle<T>,904		sender: &T::CrossAccountId,905		token: TokenId,906		spender: Option<&T::CrossAccountId>,907	) -> DispatchResult {908		if collection.permissions.access() == AccessMode::AllowList {909			collection.check_allowlist(sender)?;910			if let Some(spender) = spender {911				collection.check_allowlist(spender)?;912			}913		}914915		if let Some(spender) = spender {916			<PalletCommon<T>>::ensure_correct_receiver(spender)?;917		}918919		let token_data =920			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;921		if &token_data.owner != sender {922			ensure!(923				collection.ignores_owned_amount(sender),924				<CommonError<T>>::CantApproveMoreThanOwned925			);926		}927928		// =========929930		Self::set_allowance_unchecked(collection, sender, token, spender, false);931		Ok(())932	}933934	fn check_allowed(935		collection: &NonfungibleHandle<T>,936		spender: &T::CrossAccountId,937		from: &T::CrossAccountId,938		token: TokenId,939		nesting_budget: &dyn Budget,940	) -> DispatchResult {941		if spender.conv_eq(from) {942			return Ok(());943		}944		if collection.permissions.access() == AccessMode::AllowList {945			// `from`, `to` checked in [`transfer`]946			collection.check_allowlist(spender)?;947		}948949		if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {950			return Ok(());951		}952953		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {954			ensure!(955				<PalletStructure<T>>::check_indirectly_owned(956					spender.clone(),957					source.0,958					source.1,959					None,960					nesting_budget961				)?,962				<CommonError<T>>::ApprovedValueTooLow,963			);964			return Ok(());965		}966		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {967			return Ok(());968		}969		ensure!(970			collection.ignores_allowance(spender),971			<CommonError<T>>::ApprovedValueTooLow972		);973		Ok(())974	}975976	pub fn transfer_from(977		collection: &NonfungibleHandle<T>,978		spender: &T::CrossAccountId,979		from: &T::CrossAccountId,980		to: &T::CrossAccountId,981		token: TokenId,982		nesting_budget: &dyn Budget,983	) -> DispatchResult {984		Self::check_allowed(collection, spender, from, token, nesting_budget)?;985986		// =========987988		// Allowance is reset in [`transfer`]989		Self::transfer(collection, from, to, token, nesting_budget)990	}991992	pub fn burn_from(993		collection: &NonfungibleHandle<T>,994		spender: &T::CrossAccountId,995		from: &T::CrossAccountId,996		token: TokenId,997		nesting_budget: &dyn Budget,998	) -> DispatchResult {999		Self::check_allowed(collection, spender, from, token, nesting_budget)?;10001001		// =========10021003		Self::burn(collection, from, token)1004	}10051006	pub fn check_nesting(1007		handle: &NonfungibleHandle<T>,1008		sender: T::CrossAccountId,1009		from: (CollectionId, TokenId),1010		under: TokenId,1011		nesting_budget: &dyn Budget,1012	) -> DispatchResult {1013		let nesting = handle.permissions.nesting();10141015		#[cfg(not(feature = "runtime-benchmarks"))]1016		let permissive = false;1017		#[cfg(feature = "runtime-benchmarks")]1018		let permissive = nesting.permissive;10191020		if permissive {1021			// Pass1022		} else if nesting.token_owner1023			&& <PalletStructure<T>>::check_indirectly_owned(1024				sender.clone(),1025				handle.id,1026				under,1027				Some(from),1028				nesting_budget,1029			)? {1030			// Pass1031		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1032			// Pass1033		} else {1034			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1035		}10361037		if let Some(whitelist) = &nesting.restricted {1038			ensure!(1039				whitelist.contains(&from.0),1040				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1041			);1042		}1043		Ok(())1044	}10451046	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1047		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1048	}10491050	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1051		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1052	}10531054	fn collection_has_tokens(collection_id: CollectionId) -> bool {1055		<TokenData<T>>::iter_prefix((collection_id,))1056			.next()1057			.is_some()1058	}10591060	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1061		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1062			.next()1063			.is_some()1064	}10651066	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1067		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1068			.map(|((child_collection_id, child_id), _)| TokenChild {1069				collection: child_collection_id,1070				token: child_id,1071			})1072			.collect()1073	}10741075	/// Delegated to `create_multiple_items`1076	pub fn create_item(1077		collection: &NonfungibleHandle<T>,1078		sender: &T::CrossAccountId,1079		data: CreateItemData<T>,1080		nesting_budget: &dyn Budget,1081	) -> DispatchResult {1082		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1083	}1084}