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}
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 =