git.delta.rocks / unique-network / refs/commits / 725c3feebb83

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs41.3 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 frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52	use super::*;53	use pallet_evm::account;5455	#[pallet::config]56	pub trait Config:57		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58	{59		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60		type WeightInfo: WeightInfo;61	}6263	#[pallet::storage]64	#[pallet::getter(fn collection_index)]65	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667	#[pallet::storage]68	pub type UniqueCollectionId<T: Config> =69		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071	#[pallet::pallet]72	#[pallet::generate_store(pub(super) trait Store)]73	pub struct Pallet<T>(_);7475	#[pallet::event]76	#[pallet::generate_deposit(pub(super) fn deposit_event)]77	pub enum Event<T: Config> {78		CollectionCreated {79			issuer: T::AccountId,80			collection_id: RmrkCollectionId,81		},82		CollectionDestroyed {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		IssuerChanged {87			old_issuer: T::AccountId,88			new_issuer: T::AccountId,89			collection_id: RmrkCollectionId,90		},91		CollectionLocked {92			issuer: T::AccountId,93			collection_id: RmrkCollectionId,94		},95		NftMinted {96			owner: T::AccountId,97			collection_id: RmrkCollectionId,98			nft_id: RmrkNftId,99		},100		NFTBurned {101			owner: T::AccountId,102			nft_id: RmrkNftId,103		},104		NFTSent {105			sender: T::AccountId,106			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107			collection_id: RmrkCollectionId,108			nft_id: RmrkNftId,109			approval_required: bool,110		},111		NFTAccepted {112			sender: T::AccountId,113			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114			collection_id: RmrkCollectionId,115			nft_id: RmrkNftId,116		},117		NFTRejected {118			sender: T::AccountId,119			collection_id: RmrkCollectionId,120			nft_id: RmrkNftId,121		},122		PropertySet {123			collection_id: RmrkCollectionId,124			maybe_nft_id: Option<RmrkNftId>,125			key: RmrkKeyString,126			value: RmrkValueString,127		},128		ResourceAdded {129			nft_id: RmrkNftId,130			resource_id: RmrkResourceId,131		},132		ResourceRemoval {133			nft_id: RmrkNftId,134			resource_id: RmrkResourceId,135		},136		ResourceAccepted {137			nft_id: RmrkNftId,138			resource_id: RmrkResourceId,139		},140		ResourceRemovalAccepted {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		PrioritySet {145			collection_id: RmrkCollectionId,146			nft_id: RmrkNftId,147		},148	}149150	#[pallet::error]151	pub enum Error<T> {152		/* Unique-specific events */153		CorruptedCollectionType,154		NftTypeEncodeError,155		RmrkPropertyKeyIsTooLong,156		RmrkPropertyValueIsTooLong,157		RmrkPropertyIsNotFound,158		UnableToDecodeRmrkData,159160		/* RMRK compatible events */161		CollectionNotEmpty,162		NoAvailableCollectionId,163		NoAvailableNftId,164		CollectionUnknown,165		NoPermission,166		NonTransferable,167		CollectionFullOrLocked,168		ResourceDoesntExist,169		CannotSendToDescendentOrSelf,170		CannotAcceptNonOwnedNft,171		CannotRejectNonOwnedNft,172		CannotRejectNonPendingNft,173		ResourceNotPending,174		NoAvailableResourceId,175	}176177	#[pallet::call]178	impl<T: Config> Pallet<T> {179		/// Create a collection180		#[transactional]181		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]182		pub fn create_collection(183			origin: OriginFor<T>,184			metadata: RmrkString,185			max: Option<u32>,186			symbol: RmrkCollectionSymbol,187		) -> DispatchResult {188			let sender = ensure_signed(origin)?;189190			let limits = CollectionLimits {191				owner_can_transfer: Some(false),192				token_limit: max,193				..Default::default()194			};195196			let data = CreateCollectionData {197				limits: Some(limits),198				token_prefix: symbol199					.into_inner()200					.try_into()201					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202				permissions: Some(CollectionPermissions {203					nesting: Some(NestingPermissions {204						token_owner: true,205						collection_admin: false,206						restricted: None,207208						permissive: false,209					}),210					..Default::default()211				}),212				..Default::default()213			};214215			let unique_collection_id = Self::init_collection(216				T::CrossAccountId::from_sub(sender.clone()),217				data,218				[219					Self::rmrk_property(Metadata, &metadata)?,220					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221				]222				.into_iter(),223			)?;224			let rmrk_collection_id = <CollectionIndex<T>>::get();225226			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227228			<PalletCommon<T>>::set_scoped_collection_property(229				unique_collection_id,230				PropertyScope::Rmrk,231				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,232			)?;233234			<CollectionIndex<T>>::mutate(|n| *n += 1);235236			Self::deposit_event(Event::CollectionCreated {237				issuer: sender,238				collection_id: rmrk_collection_id,239			});240241			Ok(())242		}243244		/// destroy collection245		#[transactional]246		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]247		pub fn destroy_collection(248			origin: OriginFor<T>,249			collection_id: RmrkCollectionId,250		) -> DispatchResult {251			let sender = ensure_signed(origin)?;252			let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254			let collection = Self::get_typed_nft_collection(255				Self::unique_collection_id(collection_id)?,256				misc::CollectionType::Regular,257			)?;258			collection.check_is_external()?;259260			<PalletNft<T>>::destroy_collection(collection, &cross_sender)261				.map_err(Self::map_unique_err_to_proxy)?;262263			Self::deposit_event(Event::CollectionDestroyed {264				issuer: sender,265				collection_id,266			});267268			Ok(())269		}270271		/// Change the issuer of a collection272		///273		/// Parameters:274		/// - `origin`: sender of the transaction275		/// - `collection_id`: collection id of the nft to change issuer of276		/// - `new_issuer`: Collection's new issuer277		#[transactional]278		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]279		pub fn change_collection_issuer(280			origin: OriginFor<T>,281			collection_id: RmrkCollectionId,282			new_issuer: <T::Lookup as StaticLookup>::Source,283		) -> DispatchResult {284			let sender = ensure_signed(origin)?;285286			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;287			collection.check_is_external()?;288289			let new_issuer = T::Lookup::lookup(new_issuer)?;290291			Self::change_collection_owner(292				Self::unique_collection_id(collection_id)?,293				misc::CollectionType::Regular,294				sender.clone(),295				new_issuer.clone(),296			)?;297298			Self::deposit_event(Event::IssuerChanged {299				old_issuer: sender,300				new_issuer,301				collection_id,302			});303304			Ok(())305		}306307		/// lock collection308		#[transactional]309		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]310		pub fn lock_collection(311			origin: OriginFor<T>,312			collection_id: RmrkCollectionId,313		) -> DispatchResult {314			let sender = ensure_signed(origin)?;315			let cross_sender = T::CrossAccountId::from_sub(sender.clone());316317			let collection = Self::get_typed_nft_collection(318				Self::unique_collection_id(collection_id)?,319				misc::CollectionType::Regular,320			)?;321			collection.check_is_external()?;322323			Self::check_collection_owner(&collection, &cross_sender)?;324325			let token_count = collection.total_supply();326327			let mut collection = collection.into_inner();328			collection.limits.token_limit = Some(token_count);329			collection.save()?;330331			Self::deposit_event(Event::CollectionLocked {332				issuer: sender,333				collection_id,334			});335336			Ok(())337		}338339		/// Mints an NFT in the specified collection340		/// Sets metadata and the royalty attribute341		///342		/// Parameters:343		/// - `collection_id`: The class of the asset to be minted.344		/// - `nft_id`: The nft value of the asset to be minted.345		/// - `recipient`: Receiver of the royalty346		/// - `royalty`: Permillage reward from each trade for the Recipient347		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash348		/// - `transferable`: Ability to transfer this NFT349		#[transactional]350		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]351		pub fn mint_nft(352			origin: OriginFor<T>,353			owner: T::AccountId,354			collection_id: RmrkCollectionId,355			recipient: Option<T::AccountId>,356			royalty_amount: Option<Permill>,357			metadata: RmrkString,358			transferable: bool,359			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,360		) -> DispatchResult {361			let sender = ensure_signed(origin)?;362			let cross_sender = T::CrossAccountId::from_sub(sender.clone());363			let cross_owner = T::CrossAccountId::from_sub(owner.clone());364365			let collection = Self::get_typed_nft_collection(366				Self::unique_collection_id(collection_id)?,367				misc::CollectionType::Regular,368			)?;369			collection.check_is_external()?;370371			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {372				recipient: recipient.unwrap_or_else(|| owner.clone()),373				amount,374			});375376			let nft_id = Self::create_nft(377				&cross_sender,378				&cross_owner,379				&collection,380				[381					Self::rmrk_property(TokenType, &NftType::Regular)?,382					Self::rmrk_property(Transferable, &transferable)?,383					Self::rmrk_property(PendingNftAccept, &false)?,384					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,385					Self::rmrk_property(Metadata, &metadata)?,386					Self::rmrk_property(Equipped, &false)?,387					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,388					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,389				]390				.into_iter(),391			)392			.map_err(|err| match err {393				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),394				err => Self::map_unique_err_to_proxy(err),395			})?;396397			if let Some(resources) = resources {398				for resource in resources {399					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;400				}401			}402403			Self::deposit_event(Event::NftMinted {404				owner,405				collection_id,406				nft_id: nft_id.0,407			});408409			Ok(())410		}411412		/// burn nft413		#[transactional]414		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]415		pub fn burn_nft(416			origin: OriginFor<T>,417			collection_id: RmrkCollectionId,418			nft_id: RmrkNftId,419			max_burns: u32,420		) -> DispatchResult {421			let sender = ensure_signed(origin)?;422			let cross_sender = T::CrossAccountId::from_sub(sender.clone());423424			let collection = Self::get_typed_nft_collection(425				Self::unique_collection_id(collection_id)?,426				misc::CollectionType::Regular,427			)?;428			collection.check_is_external()?;429430			Self::destroy_nft(431				cross_sender,432				Self::unique_collection_id(collection_id)?,433				nft_id.into(),434				max_burns,435				<Error<T>>::NoPermission,436			)437			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;438439			Self::deposit_event(Event::NFTBurned {440				owner: sender,441				nft_id,442			});443444			Ok(())445		}446447		/// Transfers a NFT from an Account or NFT A to another Account or NFT B448		///449		/// Parameters:450		/// - `origin`: sender of the transaction451		/// - `rmrk_collection_id`: collection id of the nft to be transferred452		/// - `rmrk_nft_id`: nft id of the nft to be transferred453		/// - `new_owner`: new owner of the nft which can be either an account or a NFT454		#[transactional]455		#[pallet::weight(<SelfWeightOf<T>>::send())]456		pub fn send(457			origin: OriginFor<T>,458			rmrk_collection_id: RmrkCollectionId,459			rmrk_nft_id: RmrkNftId,460			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,461		) -> DispatchResult {462			let sender = ensure_signed(origin.clone())?;463			let cross_sender = T::CrossAccountId::from_sub(sender.clone());464465			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;466			let nft_id = rmrk_nft_id.into();467468			let collection =469				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;470			collection.check_is_external()?;471472			let token_data =473				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;474475			let from = token_data.owner;476477			ensure!(478				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,479				<Error<T>>::NonTransferable480			);481482			ensure!(483				!Self::get_nft_property_decoded(484					collection_id,485					nft_id,486					RmrkProperty::PendingNftAccept487				)?,488				<Error<T>>::NoPermission489			);490491			let target_owner;492			let approval_required;493494			match new_owner {495				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {496					target_owner = T::CrossAccountId::from_sub(account_id.clone());497					approval_required = false;498				}499				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(500					target_collection_id,501					target_nft_id,502				) => {503					let target_collection_id = Self::unique_collection_id(target_collection_id)?;504505					let target_nft_budget = budget::Value::new(NESTING_BUDGET);506507					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(508						target_collection_id,509						target_nft_id.into(),510						Some((collection_id, nft_id)),511						&target_nft_budget,512					)513					.map_err(Self::map_unique_err_to_proxy)?;514515					approval_required = cross_sender != target_nft_owner;516517					if approval_required {518						target_owner = target_nft_owner;519520						<PalletNft<T>>::set_scoped_token_property(521							collection.id,522							nft_id,523							PropertyScope::Rmrk,524							Self::rmrk_property(PendingNftAccept, &approval_required)?,525						)?;526					} else {527						target_owner = T::CrossTokenAddressMapping::token_to_address(528							target_collection_id,529							target_nft_id.into(),530						);531					}532				}533			}534535			let src_nft_budget = budget::Value::new(NESTING_BUDGET);536537			<PalletNft<T>>::transfer_from(538				&collection,539				&cross_sender,540				&from,541				&target_owner,542				nft_id,543				&src_nft_budget,544			)545			.map_err(Self::map_unique_err_to_proxy)?;546547			Self::deposit_event(Event::NFTSent {548				sender,549				recipient: new_owner,550				collection_id: rmrk_collection_id,551				nft_id: rmrk_nft_id,552				approval_required,553			});554555			Ok(())556		}557558		/// Accepts an NFT sent from another account to self or owned NFT559		///560		/// Parameters:561		/// - `origin`: sender of the transaction562		/// - `rmrk_collection_id`: collection id of the nft to be accepted563		/// - `rmrk_nft_id`: nft id of the nft to be accepted564		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was565		///   sent to566		#[transactional]567		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]568		pub fn accept_nft(569			origin: OriginFor<T>,570			rmrk_collection_id: RmrkCollectionId,571			rmrk_nft_id: RmrkNftId,572			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,573		) -> DispatchResult {574			let sender = ensure_signed(origin.clone())?;575			let cross_sender = T::CrossAccountId::from_sub(sender.clone());576577			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;578			let nft_id = rmrk_nft_id.into();579580			let collection =581				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;582			collection.check_is_external()?;583584			let new_cross_owner = match new_owner {585				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {586					T::CrossAccountId::from_sub(account_id.clone())587				}588				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(589					target_collection_id,590					target_nft_id,591				) => {592					let target_collection_id = Self::unique_collection_id(target_collection_id)?;593594					T::CrossTokenAddressMapping::token_to_address(595						target_collection_id,596						TokenId(target_nft_id),597					)598				}599			};600601			let budget = budget::Value::new(NESTING_BUDGET);602603			<PalletNft<T>>::transfer(604				&collection,605				&cross_sender,606				&new_cross_owner,607				nft_id,608				&budget,609			)610			.map_err(|err| {611				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {612					<Error<T>>::CannotAcceptNonOwnedNft.into()613				} else {614					Self::map_unique_err_to_proxy(err)615				}616			})?;617618			<PalletNft<T>>::set_scoped_token_property(619				collection.id,620				nft_id,621				PropertyScope::Rmrk,622				Self::rmrk_property(PendingNftAccept, &false)?,623			)?;624625			Self::deposit_event(Event::NFTAccepted {626				sender,627				recipient: new_owner,628				collection_id: rmrk_collection_id,629				nft_id: rmrk_nft_id,630			});631632			Ok(())633		}634635		/// Rejects an NFT sent from another account to self or owned NFT636		///637		/// Parameters:638		/// - `origin`: sender of the transaction639		/// - `rmrk_collection_id`: collection id of the nft to be accepted640		/// - `rmrk_nft_id`: nft id of the nft to be accepted641		#[transactional]642		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]643		pub fn reject_nft(644			origin: OriginFor<T>,645			rmrk_collection_id: RmrkCollectionId,646			rmrk_nft_id: RmrkNftId,647		) -> DispatchResult {648			let sender = ensure_signed(origin)?;649			let cross_sender = T::CrossAccountId::from_sub(sender.clone());650651			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;652			let nft_id = rmrk_nft_id.into();653654			let collection =655				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;656			collection.check_is_external()?;657658			ensure!(659				<TokenData<T>>::get((collection_id, nft_id)).is_some(),660				<Error<T>>::NoAvailableNftId661			);662663			ensure!(664				Self::get_nft_property_decoded(665					collection_id,666					nft_id,667					RmrkProperty::PendingNftAccept668				)?,669				<Error<T>>::CannotRejectNonPendingNft670			);671672			Self::destroy_nft(673				cross_sender,674				collection_id,675				nft_id,676				NESTING_BUDGET,677				<Error<T>>::CannotRejectNonOwnedNft,678			)679			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;680681			Self::deposit_event(Event::NFTRejected {682				sender,683				collection_id: rmrk_collection_id,684				nft_id: rmrk_nft_id,685			});686687			Ok(())688		}689690		/// accept the addition of a new resource to an existing NFT691		#[transactional]692		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]693		pub fn accept_resource(694			origin: OriginFor<T>,695			rmrk_collection_id: RmrkCollectionId,696			rmrk_nft_id: RmrkNftId,697			resource_id: RmrkResourceId,698		) -> DispatchResult {699			let sender = ensure_signed(origin)?;700			let cross_sender = T::CrossAccountId::from_sub(sender);701702			let collection_id = Self::unique_collection_id(rmrk_collection_id)703				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;704			let collection =705				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;706			collection.check_is_external()?;707708			let nft_id = rmrk_nft_id.into();709710			let budget = budget::Value::new(NESTING_BUDGET);711712			let nft_owner =713				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)714					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {717				ensure!(res.pending, <Error<T>>::ResourceNotPending);718				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);719720				res.pending = false;721722				Ok(())723			})?;724725			Self::deposit_event(Event::<T>::ResourceAccepted {726				nft_id: rmrk_nft_id,727				resource_id,728			});729730			Ok(())731		}732733		/// accept the removal of a resource of an existing NFT734		#[transactional]735		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]736		pub fn accept_resource_removal(737			origin: OriginFor<T>,738			rmrk_collection_id: RmrkCollectionId,739			rmrk_nft_id: RmrkNftId,740			resource_id: RmrkResourceId,741		) -> DispatchResult {742			let sender = ensure_signed(origin)?;743			let cross_sender = T::CrossAccountId::from_sub(sender);744745			let collection_id = Self::unique_collection_id(rmrk_collection_id)746				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;747			let collection =748				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;749			collection.check_is_external()?;750751			let nft_id = rmrk_nft_id.into();752753			let budget = budget::Value::new(NESTING_BUDGET);754755			let nft_owner =756				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)757					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);760761			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;762763			let resource_info = <PalletNft<T>>::token_sys_property((764				collection_id,765				nft_id,766				PropertyScope::Rmrk,767				resource_id_key.clone(),768			))769			.ok_or(<Error<T>>::ResourceDoesntExist)?;770771			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;772773			ensure!(774				resource_info.pending_removal,775				<Error<T>>::ResourceNotPending776			);777778			<PalletNft<T>>::remove_token_sys_property(779				collection_id,780				nft_id,781				PropertyScope::Rmrk,782				resource_id_key,783			);784785			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {786				nft_id: rmrk_nft_id,787				resource_id,788			});789790			Ok(())791		}792793		/// set a custom value on an NFT794		#[transactional]795		#[pallet::weight(<SelfWeightOf<T>>::set_property())]796		pub fn set_property(797			origin: OriginFor<T>,798			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,799			maybe_nft_id: Option<RmrkNftId>,800			key: RmrkKeyString,801			value: RmrkValueString,802		) -> DispatchResult {803			let sender = ensure_signed(origin)?;804			let sender = T::CrossAccountId::from_sub(sender);805806			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;807			let collection =808				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809			collection.check_is_external()?;810811			let budget = budget::Value::new(NESTING_BUDGET);812813			match maybe_nft_id {814				Some(nft_id) => {815					let token_id: TokenId = nft_id.into();816817					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;818					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;819820					<PalletNft<T>>::set_scoped_token_property(821						collection_id,822						token_id,823						PropertyScope::Rmrk,824						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,825					)?;826				}827				None => {828					let collection = Self::get_typed_nft_collection(829						collection_id,830						misc::CollectionType::Regular,831					)?;832833					Self::check_collection_owner(&collection, &sender)?;834835					<PalletCommon<T>>::set_scoped_collection_property(836						collection_id,837						PropertyScope::Rmrk,838						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,839					)?;840				}841			}842843			Self::deposit_event(Event::PropertySet {844				collection_id: rmrk_collection_id,845				maybe_nft_id,846				key,847				value,848			});849850			Ok(())851		}852853		/// set a different order of resource priority854		#[transactional]855		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]856		pub fn set_priority(857			origin: OriginFor<T>,858			rmrk_collection_id: RmrkCollectionId,859			rmrk_nft_id: RmrkNftId,860			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,861		) -> DispatchResult {862			let sender = ensure_signed(origin)?;863			let sender = T::CrossAccountId::from_sub(sender);864865			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;866			let nft_id = rmrk_nft_id.into();867868			let collection =869				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;870			collection.check_is_external()?;871872			let budget = budget::Value::new(NESTING_BUDGET);873874			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;875			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;876877			<PalletNft<T>>::set_scoped_token_property(878				collection_id,879				nft_id,880				PropertyScope::Rmrk,881				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,882			)?;883884			Self::deposit_event(Event::<T>::PrioritySet {885				collection_id: rmrk_collection_id,886				nft_id: rmrk_nft_id,887			});888889			Ok(())890		}891892		/// Create basic resource893		#[transactional]894		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]895		pub fn add_basic_resource(896			origin: OriginFor<T>,897			rmrk_collection_id: RmrkCollectionId,898			nft_id: RmrkNftId,899			resource: RmrkBasicResource,900		) -> DispatchResult {901			let sender = ensure_signed(origin.clone())?;902903			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;904			let collection =905				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906			collection.check_is_external()?;907908			let resource_id = Self::resource_add(909				sender,910				collection_id,911				nft_id.into(),912				RmrkResourceTypes::Basic(resource),913			)?;914915			Self::deposit_event(Event::ResourceAdded {916				nft_id,917				resource_id,918			});919			Ok(())920		}921922		/// Create composable resource923		#[transactional]924		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]925		pub fn add_composable_resource(926			origin: OriginFor<T>,927			rmrk_collection_id: RmrkCollectionId,928			nft_id: RmrkNftId,929			resource: RmrkComposableResource,930		) -> DispatchResult {931			let sender = ensure_signed(origin.clone())?;932933			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;934			let collection =935				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;936			collection.check_is_external()?;937938			let resource_id = Self::resource_add(939				sender,940				collection_id,941				nft_id.into(),942				RmrkResourceTypes::Composable(resource),943			)?;944945			Self::deposit_event(Event::ResourceAdded {946				nft_id,947				resource_id,948			});949			Ok(())950		}951952		/// Create slot resource953		#[transactional]954		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]955		pub fn add_slot_resource(956			origin: OriginFor<T>,957			rmrk_collection_id: RmrkCollectionId,958			nft_id: RmrkNftId,959			resource: RmrkSlotResource,960		) -> DispatchResult {961			let sender = ensure_signed(origin.clone())?;962963			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;964			let collection =965				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;966			collection.check_is_external()?;967968			let resource_id = Self::resource_add(969				sender,970				collection_id,971				nft_id.into(),972				RmrkResourceTypes::Slot(resource),973			)?;974975			Self::deposit_event(Event::ResourceAdded {976				nft_id,977				resource_id,978			});979			Ok(())980		}981982		/// remove resource983		#[transactional]984		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]985		pub fn remove_resource(986			origin: OriginFor<T>,987			rmrk_collection_id: RmrkCollectionId,988			nft_id: RmrkNftId,989			resource_id: RmrkResourceId,990		) -> DispatchResult {991			let sender = ensure_signed(origin.clone())?;992993			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;994			let collection =995				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;996			collection.check_is_external()?;997998			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;9991000			Self::deposit_event(Event::ResourceRemoval {1001				nft_id,1002				resource_id,1003			});1004			Ok(())1005		}1006	}1007}10081009impl<T: Config> Pallet<T> {1010	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1011		let key = rmrk_key.to_key::<T>()?;10121013		let scoped_key = PropertyScope::Rmrk1014			.apply(key)1015			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10161017		Ok(scoped_key)1018	}10191020	// todo think about renaming these1021	pub fn rmrk_property<E: Encode>(1022		rmrk_key: RmrkProperty,1023		value: &E,1024	) -> Result<Property, DispatchError> {1025		let key = rmrk_key.to_key::<T>()?;10261027		let value = Self::encode_property(value)?;10281029		let property = Property { key, value };10301031		Ok(property)1032	}10331034	pub fn encode_property<E: Encode, S: Get<u32>>(1035		value: &E,1036	) -> Result<BoundedBytes<S>, DispatchError> {1037		let value = value1038			.encode()1039			.try_into()1040			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10411042		Ok(value)1043	}10441045	pub fn decode_property<D: Decode, S: Get<u32>>(1046		vec: &BoundedBytes<S>,1047	) -> Result<D, DispatchError> {1048		vec.decode()1049			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1050	}10511052	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1053	where1054		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1055	{1056		vec.rebind()1057			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1058	}10591060	fn init_collection(1061		sender: T::CrossAccountId,1062		data: CreateCollectionData<T::AccountId>,1063		properties: impl Iterator<Item = Property>,1064	) -> Result<CollectionId, DispatchError> {1065		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10661067		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1068			return Err(<Error<T>>::NoAvailableCollectionId.into());1069		}10701071		<PalletCommon<T>>::set_scoped_collection_properties(1072			collection_id?,1073			PropertyScope::Rmrk,1074			properties,1075		)?;10761077		collection_id1078	}10791080	pub fn create_nft(1081		sender: &T::CrossAccountId,1082		owner: &T::CrossAccountId,1083		collection: &NonfungibleHandle<T>,1084		properties: impl Iterator<Item = Property>,1085	) -> Result<TokenId, DispatchError> {1086		let data = CreateNftExData {1087			properties: BoundedVec::default(),1088			owner: owner.clone(),1089		};10901091		let budget = budget::Value::new(NESTING_BUDGET);10921093		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10941095		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10961097		<PalletNft<T>>::set_scoped_token_properties(1098			collection.id,1099			nft_id,1100			PropertyScope::Rmrk,1101			properties,1102		)?;11031104		Ok(nft_id)1105	}11061107	fn destroy_nft(1108		sender: T::CrossAccountId,1109		collection_id: CollectionId,1110		token_id: TokenId,1111		max_burns: u32,1112		error_if_not_owned: Error<T>,1113	) -> DispatchResultWithPostInfo {1114		let collection =1115			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11161117		let token_data =1118			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11191120		let from = token_data.owner;11211122		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11231124		ensure!(1125			<PalletStructure<T>>::check_indirectly_owned(1126				sender.clone(),1127				collection_id,1128				token_id,1129				None,1130				&owner_check_budget1131			)?,1132			error_if_not_owned,1133		);11341135		let burns_budget = budget::Value::new(max_burns);1136		let breadth_budget = budget::Value::new(max_burns);11371138		<PalletNft<T>>::burn_recursively(1139			&collection,1140			&from,1141			token_id,1142			&burns_budget,1143			&breadth_budget,1144		)1145	}11461147	fn acquire_next_resource_id(1148		collection_id: CollectionId,1149		nft_id: TokenId,1150	) -> Result<RmrkResourceId, DispatchError> {1151		let resource_id: RmrkResourceId =1152			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11531154		let next_id = resource_id1155			.checked_add(1)1156			.ok_or(<Error<T>>::NoAvailableResourceId)?;11571158		<PalletNft<T>>::set_scoped_token_property(1159			collection_id,1160			nft_id,1161			PropertyScope::Rmrk,1162			Self::rmrk_property(NextResourceId, &next_id)?,1163		)?;11641165		Ok(resource_id)1166	}11671168	fn resource_add(1169		sender: T::AccountId,1170		collection_id: CollectionId,1171		nft_id: TokenId,1172		resource: RmrkResourceTypes,1173	) -> Result<RmrkResourceId, DispatchError> {1174		let collection =1175			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176		ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178		let sender = T::CrossAccountId::from_sub(sender);1179		let budget = budget::Value::new(NESTING_BUDGET);11801181		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1182			.map_err(Self::map_unique_err_to_proxy)?;11831184		let pending = sender != nft_owner;11851186		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11871188		let resource_info = RmrkResourceInfo {1189			id,1190			resource,1191			pending,1192			pending_removal: false,1193		};11941195		<PalletNft<T>>::try_mutate_token_sys_property(1196			collection_id,1197			nft_id,1198			PropertyScope::Rmrk,1199			Self::rmrk_property_key(ResourceId(id))?,1200			|value| -> DispatchResult {1201				*value = Some(Self::encode_property(&resource_info)?);12021203				Ok(())1204			},1205		)?;12061207		Ok(id)1208	}12091210	fn resource_remove(1211		sender: T::AccountId,1212		collection_id: CollectionId,1213		nft_id: TokenId,1214		resource_id: RmrkResourceId,1215	) -> DispatchResult {1216		let collection =1217			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1218		ensure!(collection.owner == sender, Error::<T>::NoPermission);12191220		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1221		let scope = PropertyScope::Rmrk;12221223		ensure!(1224			<PalletNft<T>>::token_sys_property((1225				collection_id,1226				nft_id,1227				scope,1228				resource_id_key.clone()1229			))1230			.is_some(),1231			<Error<T>>::ResourceDoesntExist1232		);12331234		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1235		let topmost_owner =1236			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12371238		let sender = T::CrossAccountId::from_sub(sender);1239		if topmost_owner == sender {1240			<PalletNft<T>>::remove_token_sys_property(1241				collection_id,1242				nft_id,1243				PropertyScope::Rmrk,1244				Self::rmrk_property_key(ResourceId(resource_id))?,1245			);1246		} else {1247			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1248				res.pending_removal = true;12491250				Ok(())1251			})?;1252		}12531254		Ok(())1255	}12561257	fn try_mutate_resource_info(1258		collection_id: CollectionId,1259		nft_id: TokenId,1260		resource_id: RmrkResourceId,1261		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1262	) -> DispatchResult {1263		<PalletNft<T>>::try_mutate_token_sys_property(1264			collection_id,1265			nft_id,1266			PropertyScope::Rmrk,1267			Self::rmrk_property_key(ResourceId(resource_id))?,1268			|value| match value {1269				Some(value) => {1270					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12711272					f(&mut resource_info)?;12731274					*value = Self::encode_property(&resource_info)?;12751276					Ok(())1277				}1278				None => Err(<Error<T>>::ResourceDoesntExist.into()),1279			},1280		)1281	}12821283	fn change_collection_owner(1284		collection_id: CollectionId,1285		collection_type: misc::CollectionType,1286		sender: T::AccountId,1287		new_owner: T::AccountId,1288	) -> DispatchResult {1289		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292		let mut collection = collection.into_inner();12931294		collection.owner = new_owner;1295		collection.save()1296	}12971298	fn check_collection_owner(1299		collection: &NonfungibleHandle<T>,1300		account: &T::CrossAccountId,1301	) -> DispatchResult {1302		collection1303			.check_is_owner(account)1304			.map_err(Self::map_unique_err_to_proxy)1305	}13061307	pub fn last_collection_idx() -> RmrkCollectionId {1308		<CollectionIndex<T>>::get()1309	}13101311	pub fn unique_collection_id(1312		rmrk_collection_id: RmrkCollectionId,1313	) -> Result<CollectionId, DispatchError> {1314		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315			.map_err(|_| <Error<T>>::CollectionUnknown.into())1316	}13171318	pub fn rmrk_collection_id(1319		unique_collection_id: CollectionId,1320	) -> Result<RmrkCollectionId, DispatchError> {1321		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1322	}13231324	pub fn get_nft_collection(1325		collection_id: CollectionId,1326	) -> Result<NonfungibleHandle<T>, DispatchError> {1327		let collection = <CollectionHandle<T>>::try_get(collection_id)1328			.map_err(|_| <Error<T>>::CollectionUnknown)?;13291330		match collection.mode {1331			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1332			_ => Err(<Error<T>>::CollectionUnknown.into()),1333		}1334	}13351336	pub fn collection_exists(collection_id: CollectionId) -> bool {1337		<CollectionHandle<T>>::try_get(collection_id).is_ok()1338	}13391340	pub fn get_collection_property(1341		collection_id: CollectionId,1342		key: RmrkProperty,1343	) -> Result<PropertyValue, DispatchError> {1344		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1345			.get(&Self::rmrk_property_key(key)?)1346			.ok_or(<Error<T>>::CollectionUnknown)?1347			.clone();13481349		Ok(collection_property)1350	}13511352	pub fn get_collection_property_decoded<V: Decode>(1353		collection_id: CollectionId,1354		key: RmrkProperty,1355	) -> Result<V, DispatchError> {1356		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1357	}13581359	pub fn get_collection_type(1360		collection_id: CollectionId,1361	) -> Result<misc::CollectionType, DispatchError> {1362		Self::get_collection_property_decoded(collection_id, CollectionType)1363			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1364	}13651366	pub fn ensure_collection_type(1367		collection_id: CollectionId,1368		collection_type: misc::CollectionType,1369	) -> DispatchResult {1370		let actual_type = Self::get_collection_type(collection_id)?;1371		ensure!(1372			actual_type == collection_type,1373			<CommonError<T>>::NoPermission1374		);13751376		Ok(())1377	}13781379	pub fn get_typed_nft_collection(1380		collection_id: CollectionId,1381		collection_type: misc::CollectionType,1382	) -> Result<NonfungibleHandle<T>, DispatchError> {1383		Self::ensure_collection_type(collection_id, collection_type)?;13841385		Self::get_nft_collection(collection_id)1386	}13871388	pub fn get_typed_nft_collection_mapped(1389		rmrk_collection_id: RmrkCollectionId,1390		collection_type: misc::CollectionType,1391	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1392		let unique_collection_id = match collection_type {1393			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1394			_ => rmrk_collection_id.into(),1395		};13961397		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13981399		Ok((collection, unique_collection_id))1400	}14011402	pub fn get_nft_property(1403		collection_id: CollectionId,1404		nft_id: TokenId,1405		key: RmrkProperty,1406	) -> Result<PropertyValue, DispatchError> {1407		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1408			.get(&Self::rmrk_property_key(key)?)1409			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1410			.clone();14111412		Ok(nft_property)1413	}14141415	pub fn get_nft_property_decoded<V: Decode>(1416		collection_id: CollectionId,1417		nft_id: TokenId,1418		key: RmrkProperty,1419	) -> Result<V, DispatchError> {1420		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1421	}14221423	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1424		<TokenData<T>>::contains_key((collection_id, nft_id))1425	}14261427	pub fn get_nft_type(1428		collection_id: CollectionId,1429		token_id: TokenId,1430	) -> Result<NftType, DispatchError> {1431		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1432			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1433	}14341435	pub fn ensure_nft_type(1436		collection_id: CollectionId,1437		token_id: TokenId,1438		nft_type: NftType,1439	) -> DispatchResult {1440		let actual_type = Self::get_nft_type(collection_id, token_id)?;1441		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14421443		Ok(())1444	}14451446	pub fn ensure_nft_owner(1447		collection_id: CollectionId,1448		token_id: TokenId,1449		possible_owner: &T::CrossAccountId,1450		nesting_budget: &dyn budget::Budget,1451	) -> DispatchResult {1452		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1453			possible_owner.clone(),1454			collection_id,1455			token_id,1456			None,1457			nesting_budget,1458		)1459		.map_err(Self::map_unique_err_to_proxy)?;14601461		ensure!(is_owned, <Error<T>>::NoPermission);14621463		Ok(())1464	}14651466	pub fn filter_user_properties<Key, Value, R, Mapper>(1467		collection_id: CollectionId,1468		token_id: Option<TokenId>,1469		filter_keys: Option<Vec<RmrkPropertyKey>>,1470		mapper: Mapper,1471	) -> Result<Vec<R>, DispatchError>1472	where1473		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1474		Value: Decode + Default,1475		Mapper: Fn(Key, Value) -> R,1476	{1477		filter_keys1478			.map(|keys| {1479				let properties = keys1480					.into_iter()1481					.filter_map(|key| {1482						let key: Key = key.try_into().ok()?;14831484						let value = match token_id {1485							Some(token_id) => Self::get_nft_property_decoded(1486								collection_id,1487								token_id,1488								UserProperty(key.as_ref()),1489							),1490							None => Self::get_collection_property_decoded(1491								collection_id,1492								UserProperty(key.as_ref()),1493							),1494						}1495						.ok()?;14961497						Some(mapper(key, value))1498					})1499					.collect();15001501				Ok(properties)1502			})1503			.unwrap_or_else(|| {1504				let properties =1505					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15061507				Ok(properties)1508			})1509	}15101511	pub fn iterate_user_properties<Key, Value, R, Mapper>(1512		collection_id: CollectionId,1513		token_id: Option<TokenId>,1514		mapper: Mapper,1515	) -> Result<impl Iterator<Item = R>, DispatchError>1516	where1517		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1518		Value: Decode + Default,1519		Mapper: Fn(Key, Value) -> R,1520	{1521		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15221523		let properties = match token_id {1524			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1525			None => <PalletCommon<T>>::collection_properties(collection_id),1526		};15271528		let properties = properties.into_iter().filter_map(move |(key, value)| {1529			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15301531			let key: Key = key.to_vec().try_into().ok()?;1532			let value: Value = value.decode().ok()?;15331534			Some(mapper(key, value))1535		});15361537		Ok(properties)1538	}15391540	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1541		map_unique_err_to_proxy! {1542			match err {1543				CommonError::NoPermission => NoPermission,1544				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1545				CommonError::PublicMintingNotAllowed => NoPermission,1546				CommonError::TokenNotFound => NoAvailableNftId,1547				CommonError::ApprovedValueTooLow => NoPermission,1548				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1549				StructureError::TokenNotFound => NoAvailableNftId,1550				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1551			}1552		}1553	}1554}