git.delta.rocks / unique-network / refs/commits / a7c09a369ee4

difftreelog

Add appropriate errors for properties

Daniel Shiposha2022-05-05parent: #cc9cedd.patch.diff
in: master

3 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -365,6 +365,10 @@
 
 		/// Tried to store more data than allowed in collection field
 		CollectionFieldSizeExceeded,
+
+		NoSpaceForProperty,
+
+		PropertyLimitReached,
 	}
 
 	#[pallet::storage]
@@ -651,7 +655,10 @@
 
 		CollectionProperties::<T>::insert(
 			id,
-			Properties::from_collection_props_vec(data.properties)?,
+			Properties::from_collection_props_vec(data.properties)
+				.map_err(|e| -> Error::<T> {
+					e.into()
+				})?,
 		);
 
 		let token_props_permissions: PropertiesPermissionMap = data
@@ -660,7 +667,9 @@
 			.map(|property| (property.key, property.permission))
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
-			.map_err(|_| PropertiesError::PropertyLimitReached)?;
+			.map_err(|_| -> Error::<T> {
+				PropertiesError::PropertyLimitReached.into()
+			})?;
 
 		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);
 
@@ -744,6 +753,9 @@
 
 		CollectionProperties::<T>::try_mutate(collection.id, |properties| {
 			properties.try_set_property(property.clone())
+		})
+		.map_err(|e| -> Error::<T> {
+			e.into()
 		})?;
 
 		Self::deposit_event(Event::CollectionPropertySet(collection.id, property));
@@ -811,7 +823,9 @@
 			let property_permission = property_permission.clone();
 			permissions.try_insert(property_permission.key, property_permission.permission)
 		})
-		.map_err(|_| PropertiesError::PropertyLimitReached)?;
+		.map_err(|_| -> Error::<T> {
+			PropertiesError::PropertyLimitReached.into()
+		})?;
 
 		Self::deposit_event(Event::PropertyPermissionSet(
 			collection.id,
@@ -1133,3 +1147,12 @@
 		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),
 	}
 }
+
+impl<T: Config> From<PropertiesError> for Error<T> {
+	fn from(error: PropertiesError) -> Self {
+		match error {
+			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,
+			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,
+		}
+	}
+}
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 frame_support::{BoundedVec, ensure, fail};21use up_data_structs::{22	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,23	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,24	PropertyKey, PropertyKeyPermission,25};26use pallet_evm::account::CrossAccountId;27use pallet_common::{28	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,29	dispatch::CollectionDispatch,30};31use pallet_structure::Pallet as PalletStructure;32use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};33use sp_core::H160;34use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};35use sp_std::{vec::Vec, vec};36use core::ops::Deref;37use sp_std::collections::btree_map::BTreeMap;38use codec::{Encode, Decode, MaxEncodedLen};39use scale_info::TypeInfo;4041pub use pallet::*;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;44pub mod common;45pub mod erc;46pub mod weights;4748pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;49pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5051#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]52pub struct ItemData<CrossAccountId> {53	pub const_data: BoundedVec<u8, CustomDataLimit>,54	pub variable_data: BoundedVec<u8, CustomDataLimit>,55	pub owner: CrossAccountId,56}5758#[frame_support::pallet]59pub mod pallet {60	use super::*;61	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};62	use up_data_structs::{CollectionId, TokenId};63	use super::weights::WeightInfo;6465	#[pallet::error]66	pub enum Error<T> {67		/// Not Nonfungible item data used to mint in Nonfungible collection.68		NotNonfungibleDataUsedToMintFungibleCollectionToken,69		/// Used amount > 1 with NFT70		NonfungibleItemsHaveNoAmount,71	}7273	#[pallet::config]74	pub trait Config:75		frame_system::Config + pallet_common::Config + pallet_structure::Config76	{77		type WeightInfo: WeightInfo;78	}7980	#[pallet::pallet]81	#[pallet::generate_store(pub(super) trait Store)]82	pub struct Pallet<T>(_);8384	#[pallet::storage]85	pub type TokensMinted<T: Config> =86		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;87	#[pallet::storage]88	pub type TokensBurnt<T: Config> =89		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9091	#[pallet::storage]92	pub type TokenData<T: Config> = StorageNMap<93		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),94		Value = ItemData<T::CrossAccountId>,95		QueryKind = OptionQuery,96	>;9798	#[pallet::storage]99	pub type TokenProperties<T: Config> = StorageNMap<100		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),101		Value = up_data_structs::Properties,102		QueryKind = ValueQuery,103		OnEmpty = up_data_structs::TokenProperties,104	>;105106	/// Used to enumerate tokens owned by account107	#[pallet::storage]108	pub type Owned<T: Config> = StorageNMap<109		Key = (110			Key<Twox64Concat, CollectionId>,111			Key<Blake2_128Concat, T::CrossAccountId>,112			Key<Twox64Concat, TokenId>,113		),114		Value = bool,115		QueryKind = ValueQuery,116	>;117118	#[pallet::storage]119	pub type AccountBalance<T: Config> = StorageNMap<120		Key = (121			Key<Twox64Concat, CollectionId>,122			Key<Blake2_128Concat, T::CrossAccountId>,123		),124		Value = u32,125		QueryKind = ValueQuery,126	>;127128	#[pallet::storage]129	pub type Allowance<T: Config> = StorageNMap<130		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),131		Value = T::CrossAccountId,132		QueryKind = OptionQuery,133	>;134}135136pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);137impl<T: Config> NonfungibleHandle<T> {138	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {139		Self(inner)140	}141	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {142		self.0143	}144}145impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {146	fn recorder(&self) -> &SubstrateRecorder<T> {147		self.0.recorder()148	}149	fn into_recorder(self) -> SubstrateRecorder<T> {150		self.0.into_recorder()151	}152}153impl<T: Config> Deref for NonfungibleHandle<T> {154	type Target = pallet_common::CollectionHandle<T>;155156	fn deref(&self) -> &Self::Target {157		&self.0158	}159}160161impl<T: Config> Pallet<T> {162	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {163		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)164	}165	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {166		<TokenData<T>>::contains_key((collection.id, token))167	}168}169170// unchecked calls skips any permission checks171impl<T: Config> Pallet<T> {172	pub fn init_collection(173		owner: T::AccountId,174		data: CreateCollectionData<T::AccountId>,175	) -> Result<CollectionId, DispatchError> {176		<PalletCommon<T>>::init_collection(owner, data)177	}178	pub fn destroy_collection(179		collection: NonfungibleHandle<T>,180		sender: &T::CrossAccountId,181	) -> DispatchResult {182		let id = collection.id;183184		// =========185186		PalletCommon::destroy_collection(collection.0, sender)?;187188		<TokenData<T>>::remove_prefix((id,), None);189		<Owned<T>>::remove_prefix((id,), None);190		<TokensMinted<T>>::remove(id);191		<TokensBurnt<T>>::remove(id);192		<Allowance<T>>::remove_prefix((id,), None);193		<AccountBalance<T>>::remove_prefix((id,), None);194		Ok(())195	}196197	pub fn burn(198		collection: &NonfungibleHandle<T>,199		sender: &T::CrossAccountId,200		token: TokenId,201	) -> DispatchResult {202		let token_data =203			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;204		ensure!(205			&token_data.owner == sender206				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),207			<CommonError<T>>::NoPermission208		);209210		if collection.access == AccessMode::AllowList {211			collection.check_allowlist(sender)?;212		}213214		let burnt = <TokensBurnt<T>>::get(collection.id)215			.checked_add(1)216			.ok_or(ArithmeticError::Overflow)?;217218		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))219			.checked_sub(1)220			.ok_or(ArithmeticError::Overflow)?;221222		if balance == 0 {223			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));224		} else {225			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);226		}227		// =========228229		<Owned<T>>::remove((collection.id, &token_data.owner, token));230		<TokensBurnt<T>>::insert(collection.id, burnt);231		<TokenData<T>>::remove((collection.id, token));232		let old_spender = <Allowance<T>>::take((collection.id, token));233234		if let Some(old_spender) = old_spender {235			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(236				collection.id,237				token,238				sender.clone(),239				old_spender,240				0,241			));242		}243244		collection.log_mirrored(ERC721Events::Transfer {245			from: *token_data.owner.as_eth(),246			to: H160::default(),247			token_id: token.into(),248		});249		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(250			collection.id,251			token,252			token_data.owner,253			1,254		));255		Ok(())256	}257258	pub fn set_token_property(259		collection: &NonfungibleHandle<T>,260		sender: &T::CrossAccountId,261		token_id: TokenId,262		property: Property,263	) -> DispatchResult {264		Self::check_token_change_permission(collection, sender, token_id, &property.key)?;265266		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {267			properties.try_set_property(property.clone())268		})?;269270		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(271			collection.id,272			token_id,273			property,274		));275276		Ok(())277	}278279	pub fn set_token_properties(280		collection: &NonfungibleHandle<T>,281		sender: &T::CrossAccountId,282		token_id: TokenId,283		properties: Vec<Property>,284	) -> DispatchResult {285		for property in properties {286			Self::set_token_property(collection, sender, token_id, property)?;287		}288289		Ok(())290	}291292	pub fn delete_token_property(293		collection: &NonfungibleHandle<T>,294		sender: &T::CrossAccountId,295		token_id: TokenId,296		property_key: PropertyKey,297	) -> DispatchResult {298		Self::check_token_change_permission(collection, sender, token_id, &property_key)?;299300		<TokenProperties<T>>::mutate((collection.id, token_id), |properties| {301			properties.remove_property(&property_key);302		});303304		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(305			collection.id,306			token_id,307			property_key,308		));309310		Ok(())311	}312313	fn check_token_change_permission(314		collection: &NonfungibleHandle<T>,315		sender: &T::CrossAccountId,316		token_id: TokenId,317		property_key: &PropertyKey,318	) -> DispatchResult {319		let permission = <PalletCommon<T>>::property_permission(collection.id)320			.get(property_key)321			.map(|p| p.clone())322			.unwrap_or(PropertyPermission::None);323324		let token_data = <TokenData<T>>::get((collection.id, token_id))325			.ok_or(<CommonError<T>>::TokenNotFound)?;326327		let check_token_owner = || -> DispatchResult {328			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);329			Ok(())330		};331332		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))333			.get_property(property_key)334			.is_some();335336		match (permission, is_property_exists) {337			(PropertyPermission::AdminConst, false) => collection.check_is_owner_or_admin(sender),338			(PropertyPermission::Admin, _) => collection.check_is_owner_or_admin(sender),339			(PropertyPermission::ItemOwnerConst, false) => check_token_owner(),340			(PropertyPermission::ItemOwner, _) => check_token_owner(),341			(PropertyPermission::ItemOwnerOrAdmin, _) => {342				check_token_owner().or(collection.check_is_owner_or_admin(sender))343			}344			_ => Err(<CommonError<T>>::NoPermission.into()),345		}346	}347348	pub fn delete_token_properties(349		collection: &NonfungibleHandle<T>,350		sender: &T::CrossAccountId,351		token_id: TokenId,352		property_keys: Vec<PropertyKey>,353	) -> DispatchResult {354		for key in property_keys {355			Self::delete_token_property(collection, sender, token_id, key)?;356		}357358		Ok(())359	}360361	pub fn set_collection_properties(362		collection: &NonfungibleHandle<T>,363		sender: &T::CrossAccountId,364		properties: Vec<Property>,365	) -> DispatchResult {366		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)367	}368369	pub fn delete_collection_properties(370		collection: &CollectionHandle<T>,371		sender: &T::CrossAccountId,372		property_keys: Vec<PropertyKey>,373	) -> DispatchResult {374		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)375	}376377	pub fn set_property_permissions(378		collection: &CollectionHandle<T>,379		sender: &T::CrossAccountId,380		property_permissions: Vec<PropertyKeyPermission>,381	) -> DispatchResult {382		<PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)383	}384385	pub fn transfer(386		collection: &NonfungibleHandle<T>,387		from: &T::CrossAccountId,388		to: &T::CrossAccountId,389		token: TokenId,390		nesting_budget: &dyn Budget,391	) -> DispatchResult {392		ensure!(393			collection.limits.transfers_enabled(),394			<CommonError<T>>::TransferNotAllowed395		);396397		let token_data =398			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;399		// TODO: require sender to be token, owner, require admins to go through transfer_from400		ensure!(401			&token_data.owner == from402				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),403			<CommonError<T>>::NoPermission404		);405406		if collection.access == AccessMode::AllowList {407			collection.check_allowlist(from)?;408			collection.check_allowlist(to)?;409		}410		<PalletCommon<T>>::ensure_correct_receiver(to)?;411412		let balance_from = <AccountBalance<T>>::get((collection.id, from))413			.checked_sub(1)414			.ok_or(<CommonError<T>>::TokenValueTooLow)?;415		let balance_to = if from != to {416			let balance_to = <AccountBalance<T>>::get((collection.id, to))417				.checked_add(1)418				.ok_or(ArithmeticError::Overflow)?;419420			ensure!(421				balance_to < collection.limits.account_token_ownership_limit(),422				<CommonError<T>>::AccountTokenLimitExceeded,423			);424425			Some(balance_to)426		} else {427			None428		};429430		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {431			let handle = <CollectionHandle<T>>::try_get(target.0)?;432			let dispatch = T::CollectionDispatch::dispatch(handle);433			let dispatch = dispatch.as_dyn();434435			dispatch.check_nesting(436				from.clone(),437				(collection.id, token),438				target.1,439				nesting_budget,440			)?;441		}442443		// =========444445		<TokenData<T>>::insert(446			(collection.id, token),447			ItemData {448				owner: to.clone(),449				..token_data450			},451		);452453		if let Some(balance_to) = balance_to {454			// from != to455			if balance_from == 0 {456				<AccountBalance<T>>::remove((collection.id, from));457			} else {458				<AccountBalance<T>>::insert((collection.id, from), balance_from);459			}460			<AccountBalance<T>>::insert((collection.id, to), balance_to);461			<Owned<T>>::remove((collection.id, from, token));462			<Owned<T>>::insert((collection.id, to, token), true);463		}464		Self::set_allowance_unchecked(collection, from, token, None, true);465466		collection.log_mirrored(ERC721Events::Transfer {467			from: *from.as_eth(),468			to: *to.as_eth(),469			token_id: token.into(),470		});471		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(472			collection.id,473			token,474			from.clone(),475			to.clone(),476			1,477		));478		Ok(())479	}480481	pub fn create_multiple_items(482		collection: &NonfungibleHandle<T>,483		sender: &T::CrossAccountId,484		data: Vec<CreateItemData<T>>,485		nesting_budget: &dyn Budget,486	) -> DispatchResult {487		if !collection.is_owner_or_admin(sender) {488			ensure!(489				collection.mint_mode,490				<CommonError<T>>::PublicMintingNotAllowed491			);492			collection.check_allowlist(sender)?;493494			for item in data.iter() {495				collection.check_allowlist(&item.owner)?;496			}497		}498499		for data in data.iter() {500			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;501		}502503		let first_token = <TokensMinted<T>>::get(collection.id);504		let tokens_minted = first_token505			.checked_add(data.len() as u32)506			.ok_or(ArithmeticError::Overflow)?;507		ensure!(508			tokens_minted <= collection.limits.token_limit(),509			<CommonError<T>>::CollectionTokenLimitExceeded510		);511512		let mut balances = BTreeMap::new();513		for data in &data {514			let balance = balances515				.entry(&data.owner)516				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));517			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;518519			ensure!(520				*balance <= collection.limits.account_token_ownership_limit(),521				<CommonError<T>>::AccountTokenLimitExceeded,522			);523		}524525		for (i, data) in data.iter().enumerate() {526			let token = TokenId(first_token + i as u32 + 1);527			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {528				let handle = <CollectionHandle<T>>::try_get(target.0)?;529				let dispatch = T::CollectionDispatch::dispatch(handle);530				let dispatch = dispatch.as_dyn();531				dispatch.check_nesting(532					sender.clone(),533					(collection.id, token),534					target.1,535					nesting_budget,536				)?;537			}538		}539540		// =========541542		<TokensMinted<T>>::insert(collection.id, tokens_minted);543		for (account, balance) in balances {544			<AccountBalance<T>>::insert((collection.id, account), balance);545		}546		for (i, data) in data.into_iter().enumerate() {547			let token = first_token + i as u32 + 1;548549			<TokenData<T>>::insert(550				(collection.id, token),551				ItemData {552					const_data: data.const_data,553					variable_data: data.variable_data,554					owner: data.owner.clone(),555				},556			);557			<Owned<T>>::insert((collection.id, &data.owner, token), true);558559			collection.log_mirrored(ERC721Events::Transfer {560				from: H160::default(),561				to: *data.owner.as_eth(),562				token_id: token.into(),563			});564			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(565				collection.id,566				TokenId(token),567				data.owner.clone(),568				1,569			));570		}571		Ok(())572	}573574	pub fn set_allowance_unchecked(575		collection: &NonfungibleHandle<T>,576		sender: &T::CrossAccountId,577		token: TokenId,578		spender: Option<&T::CrossAccountId>,579		assume_implicit_eth: bool,580	) {581		if let Some(spender) = spender {582			let old_spender = <Allowance<T>>::get((collection.id, token));583			<Allowance<T>>::insert((collection.id, token), spender);584			// In ERC721 there is only one possible approved user of token, so we set585			// approved user to spender586			collection.log_mirrored(ERC721Events::Approval {587				owner: *sender.as_eth(),588				approved: *spender.as_eth(),589				token_id: token.into(),590			});591			// In Unique chain, any token can have any amount of approved users, so we need to592			// set allowance of old owner to 0, and allowance of new owner to 1593			if old_spender.as_ref() != Some(spender) {594				if let Some(old_owner) = old_spender {595					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(596						collection.id,597						token,598						sender.clone(),599						old_owner,600						0,601					));602				}603				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(604					collection.id,605					token,606					sender.clone(),607					spender.clone(),608					1,609				));610			}611		} else {612			let old_spender = <Allowance<T>>::take((collection.id, token));613			if !assume_implicit_eth {614				// In ERC721 there is only one possible approved user of token, so we set615				// approved user to zero address616				collection.log_mirrored(ERC721Events::Approval {617					owner: *sender.as_eth(),618					approved: H160::default(),619					token_id: token.into(),620				});621			}622			// In Unique chain, any token can have any amount of approved users, so we need to623			// set allowance of old owner to 0624			if let Some(old_spender) = old_spender {625				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(626					collection.id,627					token,628					sender.clone(),629					old_spender,630					0,631				));632			}633		}634	}635636	pub fn set_allowance(637		collection: &NonfungibleHandle<T>,638		sender: &T::CrossAccountId,639		token: TokenId,640		spender: Option<&T::CrossAccountId>,641	) -> DispatchResult {642		if collection.access == AccessMode::AllowList {643			collection.check_allowlist(sender)?;644			if let Some(spender) = spender {645				collection.check_allowlist(spender)?;646			}647		}648649		if let Some(spender) = spender {650			<PalletCommon<T>>::ensure_correct_receiver(spender)?;651		}652		let token_data =653			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;654		if &token_data.owner != sender {655			ensure!(656				collection.ignores_owned_amount(sender),657				<CommonError<T>>::CantApproveMoreThanOwned658			);659		}660661		// =========662663		Self::set_allowance_unchecked(collection, sender, token, spender, false);664		Ok(())665	}666667	fn check_allowed(668		collection: &NonfungibleHandle<T>,669		spender: &T::CrossAccountId,670		from: &T::CrossAccountId,671		token: TokenId,672		nesting_budget: &dyn Budget,673	) -> DispatchResult {674		if spender.conv_eq(from) {675			return Ok(());676		}677		if collection.access == AccessMode::AllowList {678			// `from`, `to` checked in [`transfer`]679			collection.check_allowlist(spender)?;680		}681		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {682			// TODO: should collection owner be allowed to perform this transfer?683			ensure!(684				<PalletStructure<T>>::check_indirectly_owned(685					spender.clone(),686					source.0,687					source.1,688					None,689					nesting_budget690				)?,691				<CommonError<T>>::ApprovedValueTooLow,692			);693			return Ok(());694		}695		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {696			return Ok(());697		}698		ensure!(699			collection.ignores_allowance(spender),700			<CommonError<T>>::ApprovedValueTooLow701		);702		Ok(())703	}704705	pub fn transfer_from(706		collection: &NonfungibleHandle<T>,707		spender: &T::CrossAccountId,708		from: &T::CrossAccountId,709		to: &T::CrossAccountId,710		token: TokenId,711		nesting_budget: &dyn Budget,712	) -> DispatchResult {713		Self::check_allowed(collection, spender, from, token, nesting_budget)?;714715		// =========716717		// Allowance is reset in [`transfer`]718		Self::transfer(collection, from, to, token, nesting_budget)719	}720721	pub fn burn_from(722		collection: &NonfungibleHandle<T>,723		spender: &T::CrossAccountId,724		from: &T::CrossAccountId,725		token: TokenId,726		nesting_budget: &dyn Budget,727	) -> DispatchResult {728		Self::check_allowed(collection, spender, from, token, nesting_budget)?;729730		// =========731732		Self::burn(collection, from, token)733	}734735	pub fn set_variable_metadata(736		collection: &NonfungibleHandle<T>,737		sender: &T::CrossAccountId,738		token: TokenId,739		data: BoundedVec<u8, CustomDataLimit>,740	) -> DispatchResult {741		let token_data =742			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;743		collection.check_can_update_meta(sender, &token_data.owner)?;744745		// =========746747		<TokenData<T>>::insert(748			(collection.id, token),749			ItemData {750				variable_data: data,751				..token_data752			},753		);754		Ok(())755	}756757	pub fn check_nesting(758		handle: &NonfungibleHandle<T>,759		sender: T::CrossAccountId,760		from: (CollectionId, TokenId),761		under: TokenId,762		nesting_budget: &dyn Budget,763	) -> DispatchResult {764		fn ensure_sender_allowed<T: Config>(765			collection: CollectionId,766			token: TokenId,767			for_nest: (CollectionId, TokenId),768			sender: T::CrossAccountId,769			budget: &dyn Budget,770		) -> DispatchResult {771			ensure!(772				<PalletStructure<T>>::check_indirectly_owned(773					sender,774					collection,775					token,776					Some(for_nest),777					budget778				)?,779				<CommonError<T>>::OnlyOwnerAllowedToNest,780			);781			Ok(())782		}783		match handle.limits.nesting_rule() {784			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),785			NestingRule::Owner => {786				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?787			}788			NestingRule::OwnerRestricted(whitelist) => {789				ensure!(790					whitelist.contains(&from.0),791					<CommonError<T>>::SourceCollectionIsNotAllowedToNest792				);793				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?794			}795		}796		Ok(())797	}798799	/// Delegated to `create_multiple_items`800	pub fn create_item(801		collection: &NonfungibleHandle<T>,802		sender: &T::CrossAccountId,803		data: CreateItemData<T>,804		nesting_budget: &dyn Budget,805	) -> DispatchResult {806		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)807	}808}
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 frame_support::{BoundedVec, ensure, fail};21use up_data_structs::{22	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,23	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,24	PropertyKey, PropertyKeyPermission,25};26use pallet_evm::account::CrossAccountId;27use pallet_common::{28	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,29	dispatch::CollectionDispatch,30};31use pallet_structure::Pallet as PalletStructure;32use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};33use sp_core::H160;34use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};35use sp_std::{vec::Vec, vec};36use core::ops::Deref;37use sp_std::collections::btree_map::BTreeMap;38use codec::{Encode, Decode, MaxEncodedLen};39use scale_info::TypeInfo;4041pub use pallet::*;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;44pub mod common;45pub mod erc;46pub mod weights;4748pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;49pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5051#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]52pub struct ItemData<CrossAccountId> {53	pub const_data: BoundedVec<u8, CustomDataLimit>,54	pub variable_data: BoundedVec<u8, CustomDataLimit>,55	pub owner: CrossAccountId,56}5758#[frame_support::pallet]59pub mod pallet {60	use super::*;61	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};62	use up_data_structs::{CollectionId, TokenId};63	use super::weights::WeightInfo;6465	#[pallet::error]66	pub enum Error<T> {67		/// Not Nonfungible item data used to mint in Nonfungible collection.68		NotNonfungibleDataUsedToMintFungibleCollectionToken,69		/// Used amount > 1 with NFT70		NonfungibleItemsHaveNoAmount,71	}7273	#[pallet::config]74	pub trait Config:75		frame_system::Config + pallet_common::Config + pallet_structure::Config76	{77		type WeightInfo: WeightInfo;78	}7980	#[pallet::pallet]81	#[pallet::generate_store(pub(super) trait Store)]82	pub struct Pallet<T>(_);8384	#[pallet::storage]85	pub type TokensMinted<T: Config> =86		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;87	#[pallet::storage]88	pub type TokensBurnt<T: Config> =89		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9091	#[pallet::storage]92	pub type TokenData<T: Config> = StorageNMap<93		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),94		Value = ItemData<T::CrossAccountId>,95		QueryKind = OptionQuery,96	>;9798	#[pallet::storage]99	pub type TokenProperties<T: Config> = StorageNMap<100		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),101		Value = up_data_structs::Properties,102		QueryKind = ValueQuery,103		OnEmpty = up_data_structs::TokenProperties,104	>;105106	/// Used to enumerate tokens owned by account107	#[pallet::storage]108	pub type Owned<T: Config> = StorageNMap<109		Key = (110			Key<Twox64Concat, CollectionId>,111			Key<Blake2_128Concat, T::CrossAccountId>,112			Key<Twox64Concat, TokenId>,113		),114		Value = bool,115		QueryKind = ValueQuery,116	>;117118	#[pallet::storage]119	pub type AccountBalance<T: Config> = StorageNMap<120		Key = (121			Key<Twox64Concat, CollectionId>,122			Key<Blake2_128Concat, T::CrossAccountId>,123		),124		Value = u32,125		QueryKind = ValueQuery,126	>;127128	#[pallet::storage]129	pub type Allowance<T: Config> = StorageNMap<130		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),131		Value = T::CrossAccountId,132		QueryKind = OptionQuery,133	>;134}135136pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);137impl<T: Config> NonfungibleHandle<T> {138	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {139		Self(inner)140	}141	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {142		self.0143	}144}145impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {146	fn recorder(&self) -> &SubstrateRecorder<T> {147		self.0.recorder()148	}149	fn into_recorder(self) -> SubstrateRecorder<T> {150		self.0.into_recorder()151	}152}153impl<T: Config> Deref for NonfungibleHandle<T> {154	type Target = pallet_common::CollectionHandle<T>;155156	fn deref(&self) -> &Self::Target {157		&self.0158	}159}160161impl<T: Config> Pallet<T> {162	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {163		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)164	}165	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {166		<TokenData<T>>::contains_key((collection.id, token))167	}168}169170// unchecked calls skips any permission checks171impl<T: Config> Pallet<T> {172	pub fn init_collection(173		owner: T::AccountId,174		data: CreateCollectionData<T::AccountId>,175	) -> Result<CollectionId, DispatchError> {176		<PalletCommon<T>>::init_collection(owner, data)177	}178	pub fn destroy_collection(179		collection: NonfungibleHandle<T>,180		sender: &T::CrossAccountId,181	) -> DispatchResult {182		let id = collection.id;183184		// =========185186		PalletCommon::destroy_collection(collection.0, sender)?;187188		<TokenData<T>>::remove_prefix((id,), None);189		<Owned<T>>::remove_prefix((id,), None);190		<TokensMinted<T>>::remove(id);191		<TokensBurnt<T>>::remove(id);192		<Allowance<T>>::remove_prefix((id,), None);193		<AccountBalance<T>>::remove_prefix((id,), None);194		Ok(())195	}196197	pub fn burn(198		collection: &NonfungibleHandle<T>,199		sender: &T::CrossAccountId,200		token: TokenId,201	) -> DispatchResult {202		let token_data =203			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;204		ensure!(205			&token_data.owner == sender206				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),207			<CommonError<T>>::NoPermission208		);209210		if collection.access == AccessMode::AllowList {211			collection.check_allowlist(sender)?;212		}213214		let burnt = <TokensBurnt<T>>::get(collection.id)215			.checked_add(1)216			.ok_or(ArithmeticError::Overflow)?;217218		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))219			.checked_sub(1)220			.ok_or(ArithmeticError::Overflow)?;221222		if balance == 0 {223			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));224		} else {225			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);226		}227		// =========228229		<Owned<T>>::remove((collection.id, &token_data.owner, token));230		<TokensBurnt<T>>::insert(collection.id, burnt);231		<TokenData<T>>::remove((collection.id, token));232		let old_spender = <Allowance<T>>::take((collection.id, token));233234		if let Some(old_spender) = old_spender {235			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(236				collection.id,237				token,238				sender.clone(),239				old_spender,240				0,241			));242		}243244		collection.log_mirrored(ERC721Events::Transfer {245			from: *token_data.owner.as_eth(),246			to: H160::default(),247			token_id: token.into(),248		});249		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(250			collection.id,251			token,252			token_data.owner,253			1,254		));255		Ok(())256	}257258	pub fn set_token_property(259		collection: &NonfungibleHandle<T>,260		sender: &T::CrossAccountId,261		token_id: TokenId,262		property: Property,263	) -> DispatchResult {264		Self::check_token_change_permission(collection, sender, token_id, &property.key)?;265266		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {267			properties.try_set_property(property.clone())268		}).map_err(|e| -> CommonError::<T> {269			e.into()270		})?;271272		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(273			collection.id,274			token_id,275			property,276		));277278		Ok(())279	}280281	pub fn set_token_properties(282		collection: &NonfungibleHandle<T>,283		sender: &T::CrossAccountId,284		token_id: TokenId,285		properties: Vec<Property>,286	) -> DispatchResult {287		for property in properties {288			Self::set_token_property(collection, sender, token_id, property)?;289		}290291		Ok(())292	}293294	pub fn delete_token_property(295		collection: &NonfungibleHandle<T>,296		sender: &T::CrossAccountId,297		token_id: TokenId,298		property_key: PropertyKey,299	) -> DispatchResult {300		Self::check_token_change_permission(collection, sender, token_id, &property_key)?;301302		<TokenProperties<T>>::mutate((collection.id, token_id), |properties| {303			properties.remove_property(&property_key);304		});305306		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(307			collection.id,308			token_id,309			property_key,310		));311312		Ok(())313	}314315	fn check_token_change_permission(316		collection: &NonfungibleHandle<T>,317		sender: &T::CrossAccountId,318		token_id: TokenId,319		property_key: &PropertyKey,320	) -> DispatchResult {321		let permission = <PalletCommon<T>>::property_permission(collection.id)322			.get(property_key)323			.map(|p| p.clone())324			.unwrap_or(PropertyPermission::None);325326		let token_data = <TokenData<T>>::get((collection.id, token_id))327			.ok_or(<CommonError<T>>::TokenNotFound)?;328329		let check_token_owner = || -> DispatchResult {330			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);331			Ok(())332		};333334		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))335			.get_property(property_key)336			.is_some();337338		match (permission, is_property_exists) {339			(PropertyPermission::AdminConst, false) => collection.check_is_owner_or_admin(sender),340			(PropertyPermission::Admin, _) => collection.check_is_owner_or_admin(sender),341			(PropertyPermission::ItemOwnerConst, false) => check_token_owner(),342			(PropertyPermission::ItemOwner, _) => check_token_owner(),343			(PropertyPermission::ItemOwnerOrAdmin, _) => {344				check_token_owner().or(collection.check_is_owner_or_admin(sender))345			}346			_ => Err(<CommonError<T>>::NoPermission.into()),347		}348	}349350	pub fn delete_token_properties(351		collection: &NonfungibleHandle<T>,352		sender: &T::CrossAccountId,353		token_id: TokenId,354		property_keys: Vec<PropertyKey>,355	) -> DispatchResult {356		for key in property_keys {357			Self::delete_token_property(collection, sender, token_id, key)?;358		}359360		Ok(())361	}362363	pub fn set_collection_properties(364		collection: &NonfungibleHandle<T>,365		sender: &T::CrossAccountId,366		properties: Vec<Property>,367	) -> DispatchResult {368		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)369	}370371	pub fn delete_collection_properties(372		collection: &CollectionHandle<T>,373		sender: &T::CrossAccountId,374		property_keys: Vec<PropertyKey>,375	) -> DispatchResult {376		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)377	}378379	pub fn set_property_permissions(380		collection: &CollectionHandle<T>,381		sender: &T::CrossAccountId,382		property_permissions: Vec<PropertyKeyPermission>,383	) -> DispatchResult {384		<PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)385	}386387	pub fn transfer(388		collection: &NonfungibleHandle<T>,389		from: &T::CrossAccountId,390		to: &T::CrossAccountId,391		token: TokenId,392		nesting_budget: &dyn Budget,393	) -> DispatchResult {394		ensure!(395			collection.limits.transfers_enabled(),396			<CommonError<T>>::TransferNotAllowed397		);398399		let token_data =400			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;401		// TODO: require sender to be token, owner, require admins to go through transfer_from402		ensure!(403			&token_data.owner == from404				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),405			<CommonError<T>>::NoPermission406		);407408		if collection.access == AccessMode::AllowList {409			collection.check_allowlist(from)?;410			collection.check_allowlist(to)?;411		}412		<PalletCommon<T>>::ensure_correct_receiver(to)?;413414		let balance_from = <AccountBalance<T>>::get((collection.id, from))415			.checked_sub(1)416			.ok_or(<CommonError<T>>::TokenValueTooLow)?;417		let balance_to = if from != to {418			let balance_to = <AccountBalance<T>>::get((collection.id, to))419				.checked_add(1)420				.ok_or(ArithmeticError::Overflow)?;421422			ensure!(423				balance_to < collection.limits.account_token_ownership_limit(),424				<CommonError<T>>::AccountTokenLimitExceeded,425			);426427			Some(balance_to)428		} else {429			None430		};431432		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {433			let handle = <CollectionHandle<T>>::try_get(target.0)?;434			let dispatch = T::CollectionDispatch::dispatch(handle);435			let dispatch = dispatch.as_dyn();436437			dispatch.check_nesting(438				from.clone(),439				(collection.id, token),440				target.1,441				nesting_budget,442			)?;443		}444445		// =========446447		<TokenData<T>>::insert(448			(collection.id, token),449			ItemData {450				owner: to.clone(),451				..token_data452			},453		);454455		if let Some(balance_to) = balance_to {456			// from != to457			if balance_from == 0 {458				<AccountBalance<T>>::remove((collection.id, from));459			} else {460				<AccountBalance<T>>::insert((collection.id, from), balance_from);461			}462			<AccountBalance<T>>::insert((collection.id, to), balance_to);463			<Owned<T>>::remove((collection.id, from, token));464			<Owned<T>>::insert((collection.id, to, token), true);465		}466		Self::set_allowance_unchecked(collection, from, token, None, true);467468		collection.log_mirrored(ERC721Events::Transfer {469			from: *from.as_eth(),470			to: *to.as_eth(),471			token_id: token.into(),472		});473		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(474			collection.id,475			token,476			from.clone(),477			to.clone(),478			1,479		));480		Ok(())481	}482483	pub fn create_multiple_items(484		collection: &NonfungibleHandle<T>,485		sender: &T::CrossAccountId,486		data: Vec<CreateItemData<T>>,487		nesting_budget: &dyn Budget,488	) -> DispatchResult {489		if !collection.is_owner_or_admin(sender) {490			ensure!(491				collection.mint_mode,492				<CommonError<T>>::PublicMintingNotAllowed493			);494			collection.check_allowlist(sender)?;495496			for item in data.iter() {497				collection.check_allowlist(&item.owner)?;498			}499		}500501		for data in data.iter() {502			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;503		}504505		let first_token = <TokensMinted<T>>::get(collection.id);506		let tokens_minted = first_token507			.checked_add(data.len() as u32)508			.ok_or(ArithmeticError::Overflow)?;509		ensure!(510			tokens_minted <= collection.limits.token_limit(),511			<CommonError<T>>::CollectionTokenLimitExceeded512		);513514		let mut balances = BTreeMap::new();515		for data in &data {516			let balance = balances517				.entry(&data.owner)518				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));519			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;520521			ensure!(522				*balance <= collection.limits.account_token_ownership_limit(),523				<CommonError<T>>::AccountTokenLimitExceeded,524			);525		}526527		for (i, data) in data.iter().enumerate() {528			let token = TokenId(first_token + i as u32 + 1);529			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {530				let handle = <CollectionHandle<T>>::try_get(target.0)?;531				let dispatch = T::CollectionDispatch::dispatch(handle);532				let dispatch = dispatch.as_dyn();533				dispatch.check_nesting(534					sender.clone(),535					(collection.id, token),536					target.1,537					nesting_budget,538				)?;539			}540		}541542		// =========543544		<TokensMinted<T>>::insert(collection.id, tokens_minted);545		for (account, balance) in balances {546			<AccountBalance<T>>::insert((collection.id, account), balance);547		}548		for (i, data) in data.into_iter().enumerate() {549			let token = first_token + i as u32 + 1;550551			<TokenData<T>>::insert(552				(collection.id, token),553				ItemData {554					const_data: data.const_data,555					variable_data: data.variable_data,556					owner: data.owner.clone(),557				},558			);559			<Owned<T>>::insert((collection.id, &data.owner, token), true);560561			collection.log_mirrored(ERC721Events::Transfer {562				from: H160::default(),563				to: *data.owner.as_eth(),564				token_id: token.into(),565			});566			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(567				collection.id,568				TokenId(token),569				data.owner.clone(),570				1,571			));572		}573		Ok(())574	}575576	pub fn set_allowance_unchecked(577		collection: &NonfungibleHandle<T>,578		sender: &T::CrossAccountId,579		token: TokenId,580		spender: Option<&T::CrossAccountId>,581		assume_implicit_eth: bool,582	) {583		if let Some(spender) = spender {584			let old_spender = <Allowance<T>>::get((collection.id, token));585			<Allowance<T>>::insert((collection.id, token), spender);586			// In ERC721 there is only one possible approved user of token, so we set587			// approved user to spender588			collection.log_mirrored(ERC721Events::Approval {589				owner: *sender.as_eth(),590				approved: *spender.as_eth(),591				token_id: token.into(),592			});593			// In Unique chain, any token can have any amount of approved users, so we need to594			// set allowance of old owner to 0, and allowance of new owner to 1595			if old_spender.as_ref() != Some(spender) {596				if let Some(old_owner) = old_spender {597					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(598						collection.id,599						token,600						sender.clone(),601						old_owner,602						0,603					));604				}605				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(606					collection.id,607					token,608					sender.clone(),609					spender.clone(),610					1,611				));612			}613		} else {614			let old_spender = <Allowance<T>>::take((collection.id, token));615			if !assume_implicit_eth {616				// In ERC721 there is only one possible approved user of token, so we set617				// approved user to zero address618				collection.log_mirrored(ERC721Events::Approval {619					owner: *sender.as_eth(),620					approved: H160::default(),621					token_id: token.into(),622				});623			}624			// In Unique chain, any token can have any amount of approved users, so we need to625			// set allowance of old owner to 0626			if let Some(old_spender) = old_spender {627				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(628					collection.id,629					token,630					sender.clone(),631					old_spender,632					0,633				));634			}635		}636	}637638	pub fn set_allowance(639		collection: &NonfungibleHandle<T>,640		sender: &T::CrossAccountId,641		token: TokenId,642		spender: Option<&T::CrossAccountId>,643	) -> DispatchResult {644		if collection.access == AccessMode::AllowList {645			collection.check_allowlist(sender)?;646			if let Some(spender) = spender {647				collection.check_allowlist(spender)?;648			}649		}650651		if let Some(spender) = spender {652			<PalletCommon<T>>::ensure_correct_receiver(spender)?;653		}654		let token_data =655			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;656		if &token_data.owner != sender {657			ensure!(658				collection.ignores_owned_amount(sender),659				<CommonError<T>>::CantApproveMoreThanOwned660			);661		}662663		// =========664665		Self::set_allowance_unchecked(collection, sender, token, spender, false);666		Ok(())667	}668669	fn check_allowed(670		collection: &NonfungibleHandle<T>,671		spender: &T::CrossAccountId,672		from: &T::CrossAccountId,673		token: TokenId,674		nesting_budget: &dyn Budget,675	) -> DispatchResult {676		if spender.conv_eq(from) {677			return Ok(());678		}679		if collection.access == AccessMode::AllowList {680			// `from`, `to` checked in [`transfer`]681			collection.check_allowlist(spender)?;682		}683		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {684			// TODO: should collection owner be allowed to perform this transfer?685			ensure!(686				<PalletStructure<T>>::check_indirectly_owned(687					spender.clone(),688					source.0,689					source.1,690					None,691					nesting_budget692				)?,693				<CommonError<T>>::ApprovedValueTooLow,694			);695			return Ok(());696		}697		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {698			return Ok(());699		}700		ensure!(701			collection.ignores_allowance(spender),702			<CommonError<T>>::ApprovedValueTooLow703		);704		Ok(())705	}706707	pub fn transfer_from(708		collection: &NonfungibleHandle<T>,709		spender: &T::CrossAccountId,710		from: &T::CrossAccountId,711		to: &T::CrossAccountId,712		token: TokenId,713		nesting_budget: &dyn Budget,714	) -> DispatchResult {715		Self::check_allowed(collection, spender, from, token, nesting_budget)?;716717		// =========718719		// Allowance is reset in [`transfer`]720		Self::transfer(collection, from, to, token, nesting_budget)721	}722723	pub fn burn_from(724		collection: &NonfungibleHandle<T>,725		spender: &T::CrossAccountId,726		from: &T::CrossAccountId,727		token: TokenId,728		nesting_budget: &dyn Budget,729	) -> DispatchResult {730		Self::check_allowed(collection, spender, from, token, nesting_budget)?;731732		// =========733734		Self::burn(collection, from, token)735	}736737	pub fn set_variable_metadata(738		collection: &NonfungibleHandle<T>,739		sender: &T::CrossAccountId,740		token: TokenId,741		data: BoundedVec<u8, CustomDataLimit>,742	) -> DispatchResult {743		let token_data =744			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;745		collection.check_can_update_meta(sender, &token_data.owner)?;746747		// =========748749		<TokenData<T>>::insert(750			(collection.id, token),751			ItemData {752				variable_data: data,753				..token_data754			},755		);756		Ok(())757	}758759	pub fn check_nesting(760		handle: &NonfungibleHandle<T>,761		sender: T::CrossAccountId,762		from: (CollectionId, TokenId),763		under: TokenId,764		nesting_budget: &dyn Budget,765	) -> DispatchResult {766		fn ensure_sender_allowed<T: Config>(767			collection: CollectionId,768			token: TokenId,769			for_nest: (CollectionId, TokenId),770			sender: T::CrossAccountId,771			budget: &dyn Budget,772		) -> DispatchResult {773			ensure!(774				<PalletStructure<T>>::check_indirectly_owned(775					sender,776					collection,777					token,778					Some(for_nest),779					budget780				)?,781				<CommonError<T>>::OnlyOwnerAllowedToNest,782			);783			Ok(())784		}785		match handle.limits.nesting_rule() {786			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),787			NestingRule::Owner => {788				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?789			}790			NestingRule::OwnerRestricted(whitelist) => {791				ensure!(792					whitelist.contains(&from.0),793					<CommonError<T>>::SourceCollectionIsNotAllowedToNest794				);795				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?796			}797		}798		Ok(())799	}800801	/// Delegated to `create_multiple_items`802	pub fn create_item(803		collection: &NonfungibleHandle<T>,804		sender: &T::CrossAccountId,805		data: CreateItemData<T>,806		nesting_budget: &dyn Budget,807	) -> DispatchResult {808		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)809	}810}
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -660,17 +660,6 @@
 	PropertyLimitReached,
 }
 
-impl From<PropertiesError> for DispatchError {
-	fn from(error: PropertiesError) -> Self {
-		match error {
-			PropertiesError::NoSpaceForProperty => DispatchError::Other("no space for property"),
-			PropertiesError::PropertyLimitReached => {
-				DispatchError::Other("property key limit reached")
-			}
-		}
-	}
-}
-
 pub type PropertiesMap =
 	BoundedBTreeMap<PropertyKey, PropertyValue, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
 pub type PropertiesPermissionMap =