git.delta.rocks / unique-network / refs/commits / 2bc96c589bf4

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		ResourceNotPending,173		NoAvailableResourceId,174	}175176	#[pallet::call]177	impl<T: Config> Pallet<T> {178		/// Create a collection179		#[transactional]180		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]181		pub fn create_collection(182			origin: OriginFor<T>,183			metadata: RmrkString,184			max: Option<u32>,185			symbol: RmrkCollectionSymbol,186		) -> DispatchResult {187			let sender = ensure_signed(origin)?;188189			let limits = CollectionLimits {190				owner_can_transfer: Some(false),191				token_limit: max,192				..Default::default()193			};194195			let data = CreateCollectionData {196				limits: Some(limits),197				token_prefix: symbol198					.into_inner()199					.try_into()200					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,201				permissions: Some(CollectionPermissions {202					nesting: Some(NestingPermissions {203						token_owner: true,204						collection_admin: false,205						restricted: None,206207						permissive: false,208					}),209					..Default::default()210				}),211				..Default::default()212			};213214			let unique_collection_id = Self::init_collection(215				T::CrossAccountId::from_sub(sender.clone()),216				data,217				[218					Self::rmrk_property(Metadata, &metadata)?,219					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,220				]221				.into_iter(),222			)?;223			let rmrk_collection_id = <CollectionIndex<T>>::get();224225			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);226227			<PalletCommon<T>>::set_scoped_collection_property(228				unique_collection_id,229				PropertyScope::Rmrk,230				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,231			)?;232233			<CollectionIndex<T>>::mutate(|n| *n += 1);234235			Self::deposit_event(Event::CollectionCreated {236				issuer: sender,237				collection_id: rmrk_collection_id,238			});239240			Ok(())241		}242243		/// destroy collection244		#[transactional]245		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]246		pub fn destroy_collection(247			origin: OriginFor<T>,248			collection_id: RmrkCollectionId,249		) -> DispatchResult {250			let sender = ensure_signed(origin)?;251			let cross_sender = T::CrossAccountId::from_sub(sender.clone());252253			let collection = Self::get_typed_nft_collection(254				Self::unique_collection_id(collection_id)?,255				misc::CollectionType::Regular,256			)?;257			collection.check_is_external()?;258259			<PalletNft<T>>::destroy_collection(collection, &cross_sender)260				.map_err(Self::map_unique_err_to_proxy)?;261262			Self::deposit_event(Event::CollectionDestroyed {263				issuer: sender,264				collection_id,265			});266267			Ok(())268		}269270		/// Change the issuer of a collection271		///272		/// Parameters:273		/// - `origin`: sender of the transaction274		/// - `collection_id`: collection id of the nft to change issuer of275		/// - `new_issuer`: Collection's new issuer276		#[transactional]277		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]278		pub fn change_collection_issuer(279			origin: OriginFor<T>,280			collection_id: RmrkCollectionId,281			new_issuer: <T::Lookup as StaticLookup>::Source,282		) -> DispatchResult {283			let sender = ensure_signed(origin)?;284285			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;286			collection.check_is_external()?;287288			let new_issuer = T::Lookup::lookup(new_issuer)?;289290			Self::change_collection_owner(291				Self::unique_collection_id(collection_id)?,292				misc::CollectionType::Regular,293				sender.clone(),294				new_issuer.clone(),295			)?;296297			Self::deposit_event(Event::IssuerChanged {298				old_issuer: sender,299				new_issuer,300				collection_id,301			});302303			Ok(())304		}305306		/// lock collection307		#[transactional]308		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]309		pub fn lock_collection(310			origin: OriginFor<T>,311			collection_id: RmrkCollectionId,312		) -> DispatchResult {313			let sender = ensure_signed(origin)?;314			let cross_sender = T::CrossAccountId::from_sub(sender.clone());315316			let collection = Self::get_typed_nft_collection(317				Self::unique_collection_id(collection_id)?,318				misc::CollectionType::Regular,319			)?;320			collection.check_is_external()?;321322			Self::check_collection_owner(&collection, &cross_sender)?;323324			let token_count = collection.total_supply();325326			let mut collection = collection.into_inner();327			collection.limits.token_limit = Some(token_count);328			collection.save()?;329330			Self::deposit_event(Event::CollectionLocked {331				issuer: sender,332				collection_id,333			});334335			Ok(())336		}337338		/// Mints an NFT in the specified collection339		/// Sets metadata and the royalty attribute340		///341		/// Parameters:342		/// - `collection_id`: The class of the asset to be minted.343		/// - `nft_id`: The nft value of the asset to be minted.344		/// - `recipient`: Receiver of the royalty345		/// - `royalty`: Permillage reward from each trade for the Recipient346		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash347		/// - `transferable`: Ability to transfer this NFT348		#[transactional]349		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]350		pub fn mint_nft(351			origin: OriginFor<T>,352			owner: T::AccountId,353			collection_id: RmrkCollectionId,354			recipient: Option<T::AccountId>,355			royalty_amount: Option<Permill>,356			metadata: RmrkString,357			transferable: bool,358			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,359		) -> DispatchResult {360			let sender = ensure_signed(origin)?;361			let cross_sender = T::CrossAccountId::from_sub(sender.clone());362			let cross_owner = T::CrossAccountId::from_sub(owner.clone());363364			let collection = Self::get_typed_nft_collection(365				Self::unique_collection_id(collection_id)?,366				misc::CollectionType::Regular,367			)?;368			collection.check_is_external()?;369370			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {371				recipient: recipient.unwrap_or_else(|| owner.clone()),372				amount,373			});374375			let nft_id = Self::create_nft(376				&cross_sender,377				&cross_owner,378				&collection,379				[380					Self::rmrk_property(TokenType, &NftType::Regular)?,381					Self::rmrk_property(Transferable, &transferable)?,382					Self::rmrk_property(PendingNftAccept, &false)?,383					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,384					Self::rmrk_property(Metadata, &metadata)?,385					Self::rmrk_property(Equipped, &false)?,386					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,387					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,388				]389				.into_iter(),390			)391			.map_err(|err| match err {392				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),393				err => Self::map_unique_err_to_proxy(err),394			})?;395396			if let Some(resources) = resources {397				for resource in resources {398					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;399				}400			}401402			Self::deposit_event(Event::NftMinted {403				owner,404				collection_id,405				nft_id: nft_id.0,406			});407408			Ok(())409		}410411		/// burn nft412		#[transactional]413		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]414		pub fn burn_nft(415			origin: OriginFor<T>,416			collection_id: RmrkCollectionId,417			nft_id: RmrkNftId,418			max_burns: u32,419		) -> DispatchResult {420			let sender = ensure_signed(origin)?;421			let cross_sender = T::CrossAccountId::from_sub(sender.clone());422423			let collection = Self::get_typed_nft_collection(424				Self::unique_collection_id(collection_id)?,425				misc::CollectionType::Regular,426			)?;427			collection.check_is_external()?;428429			Self::destroy_nft(430				cross_sender,431				Self::unique_collection_id(collection_id)?,432				nft_id.into(),433				max_burns,434				<Error<T>>::NoPermission,435			)436			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;437438			Self::deposit_event(Event::NFTBurned {439				owner: sender,440				nft_id,441			});442443			Ok(())444		}445446		/// Transfers a NFT from an Account or NFT A to another Account or NFT B447		///448		/// Parameters:449		/// - `origin`: sender of the transaction450		/// - `rmrk_collection_id`: collection id of the nft to be transferred451		/// - `rmrk_nft_id`: nft id of the nft to be transferred452		/// - `new_owner`: new owner of the nft which can be either an account or a NFT453		#[transactional]454		#[pallet::weight(<SelfWeightOf<T>>::send())]455		pub fn send(456			origin: OriginFor<T>,457			rmrk_collection_id: RmrkCollectionId,458			rmrk_nft_id: RmrkNftId,459			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,460		) -> DispatchResult {461			let sender = ensure_signed(origin.clone())?;462			let cross_sender = T::CrossAccountId::from_sub(sender.clone());463464			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;465			let nft_id = rmrk_nft_id.into();466467			let collection =468				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;469			collection.check_is_external()?;470471			let token_data =472				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;473474			let from = token_data.owner;475476			ensure!(477				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,478				<Error<T>>::NonTransferable479			);480481			ensure!(482				!Self::get_nft_property_decoded(483					collection_id,484					nft_id,485					RmrkProperty::PendingNftAccept486				)?,487				<Error<T>>::NoPermission488			);489490			let target_owner;491			let approval_required;492493			match new_owner {494				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {495					target_owner = T::CrossAccountId::from_sub(account_id.clone());496					approval_required = false;497				}498				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(499					target_collection_id,500					target_nft_id,501				) => {502					let target_collection_id = Self::unique_collection_id(target_collection_id)?;503504					let target_nft_budget = budget::Value::new(NESTING_BUDGET);505506					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(507						target_collection_id,508						target_nft_id.into(),509						Some((collection_id, nft_id)),510						&target_nft_budget,511					)512					.map_err(Self::map_unique_err_to_proxy)?;513514					approval_required = cross_sender != target_nft_owner;515516					if approval_required {517						target_owner = target_nft_owner;518519						<PalletNft<T>>::set_scoped_token_property(520							collection.id,521							nft_id,522							PropertyScope::Rmrk,523							Self::rmrk_property(PendingNftAccept, &approval_required)?,524						)?;525					} else {526						target_owner = T::CrossTokenAddressMapping::token_to_address(527							target_collection_id,528							target_nft_id.into(),529						);530					}531				}532			}533534			let src_nft_budget = budget::Value::new(NESTING_BUDGET);535536			<PalletNft<T>>::transfer_from(537				&collection,538				&cross_sender,539				&from,540				&target_owner,541				nft_id,542				&src_nft_budget,543			)544			.map_err(Self::map_unique_err_to_proxy)?;545546			Self::deposit_event(Event::NFTSent {547				sender,548				recipient: new_owner,549				collection_id: rmrk_collection_id,550				nft_id: rmrk_nft_id,551				approval_required,552			});553554			Ok(())555		}556557		/// Accepts an NFT sent from another account to self or owned NFT558		///559		/// Parameters:560		/// - `origin`: sender of the transaction561		/// - `rmrk_collection_id`: collection id of the nft to be accepted562		/// - `rmrk_nft_id`: nft id of the nft to be accepted563		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was564		///   sent to565		#[transactional]566		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]567		pub fn accept_nft(568			origin: OriginFor<T>,569			rmrk_collection_id: RmrkCollectionId,570			rmrk_nft_id: RmrkNftId,571			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,572		) -> DispatchResult {573			let sender = ensure_signed(origin.clone())?;574			let cross_sender = T::CrossAccountId::from_sub(sender.clone());575576			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;577			let nft_id = rmrk_nft_id.into();578579			let collection =580				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;581			collection.check_is_external()?;582583			let new_cross_owner = match new_owner {584				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {585					T::CrossAccountId::from_sub(account_id.clone())586				}587				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(588					target_collection_id,589					target_nft_id,590				) => {591					let target_collection_id = Self::unique_collection_id(target_collection_id)?;592593					T::CrossTokenAddressMapping::token_to_address(594						target_collection_id,595						TokenId(target_nft_id),596					)597				}598			};599600			let budget = budget::Value::new(NESTING_BUDGET);601602			<PalletNft<T>>::transfer(603				&collection,604				&cross_sender,605				&new_cross_owner,606				nft_id,607				&budget,608			)609			.map_err(|err| {610				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {611					<Error<T>>::CannotAcceptNonOwnedNft.into()612				} else {613					Self::map_unique_err_to_proxy(err)614				}615			})?;616617			<PalletNft<T>>::set_scoped_token_property(618				collection.id,619				nft_id,620				PropertyScope::Rmrk,621				Self::rmrk_property(PendingNftAccept, &false)?,622			)?;623624			Self::deposit_event(Event::NFTAccepted {625				sender,626				recipient: new_owner,627				collection_id: rmrk_collection_id,628				nft_id: rmrk_nft_id,629			});630631			Ok(())632		}633634		/// Rejects an NFT sent from another account to self or owned NFT635		///636		/// Parameters:637		/// - `origin`: sender of the transaction638		/// - `rmrk_collection_id`: collection id of the nft to be accepted639		/// - `rmrk_nft_id`: nft id of the nft to be accepted640		#[transactional]641		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]642		pub fn reject_nft(643			origin: OriginFor<T>,644			rmrk_collection_id: RmrkCollectionId,645			rmrk_nft_id: RmrkNftId,646		) -> DispatchResult {647			let sender = ensure_signed(origin)?;648			let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651			let nft_id = rmrk_nft_id.into();652653			let collection =654				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655			collection.check_is_external()?;656657			ensure!(658				<TokenData<T>>::get((collection_id, nft_id)).is_some(),659				<Error<T>>::NoAvailableNftId660			);661662			ensure!(663				Self::get_nft_property_decoded(664					collection_id,665					nft_id,666					RmrkProperty::PendingNftAccept667				)?,668				<Error<T>>::NoPermission669			);670671			Self::destroy_nft(672				cross_sender,673				collection_id,674				nft_id,675				NESTING_BUDGET,676				<Error<T>>::CannotRejectNonOwnedNft,677			)678			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;679680			Self::deposit_event(Event::NFTRejected {681				sender,682				collection_id: rmrk_collection_id,683				nft_id: rmrk_nft_id,684			});685686			Ok(())687		}688689		/// accept the addition of a new resource to an existing NFT690		#[transactional]691		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]692		pub fn accept_resource(693			origin: OriginFor<T>,694			rmrk_collection_id: RmrkCollectionId,695			rmrk_nft_id: RmrkNftId,696			resource_id: RmrkResourceId,697		) -> DispatchResult {698			let sender = ensure_signed(origin)?;699			let cross_sender = T::CrossAccountId::from_sub(sender);700701			let collection_id = Self::unique_collection_id(rmrk_collection_id)702				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;703			let collection =704				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;705			collection.check_is_external()?;706707			let nft_id = rmrk_nft_id.into();708709			let budget = budget::Value::new(NESTING_BUDGET);710711			let nft_owner =712				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)713					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;714715			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {716				ensure!(res.pending, <Error<T>>::ResourceNotPending);717				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);718719				res.pending = false;720721				Ok(())722			})?;723724			Self::deposit_event(Event::<T>::ResourceAccepted {725				nft_id: rmrk_nft_id,726				resource_id,727			});728729			Ok(())730		}731732		/// accept the removal of a resource of an existing NFT733		#[transactional]734		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]735		pub fn accept_resource_removal(736			origin: OriginFor<T>,737			rmrk_collection_id: RmrkCollectionId,738			rmrk_nft_id: RmrkNftId,739			resource_id: RmrkResourceId,740		) -> DispatchResult {741			let sender = ensure_signed(origin)?;742			let cross_sender = T::CrossAccountId::from_sub(sender);743744			let collection_id = Self::unique_collection_id(rmrk_collection_id)745				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;746			let collection =747				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;748			collection.check_is_external()?;749750			let nft_id = rmrk_nft_id.into();751752			let budget = budget::Value::new(NESTING_BUDGET);753754			let nft_owner =755				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)756					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;757758			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);759760			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;761762			let resource_info = <PalletNft<T>>::token_sys_property((763				collection_id,764				nft_id,765				PropertyScope::Rmrk,766				resource_id_key.clone(),767			))768			.ok_or(<Error<T>>::ResourceDoesntExist)?;769770			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;771772			ensure!(773				resource_info.pending_removal,774				<Error<T>>::ResourceNotPending775			);776777			<PalletNft<T>>::remove_token_sys_property(778				collection_id,779				nft_id,780				PropertyScope::Rmrk,781				resource_id_key,782			);783784			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {785				nft_id: rmrk_nft_id,786				resource_id,787			});788789			Ok(())790		}791792		/// set a custom value on an NFT793		#[transactional]794		#[pallet::weight(<SelfWeightOf<T>>::set_property())]795		pub fn set_property(796			origin: OriginFor<T>,797			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,798			maybe_nft_id: Option<RmrkNftId>,799			key: RmrkKeyString,800			value: RmrkValueString,801		) -> DispatchResult {802			let sender = ensure_signed(origin)?;803			let sender = T::CrossAccountId::from_sub(sender);804805			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;806			let collection =807				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;808			collection.check_is_external()?;809810			let budget = budget::Value::new(NESTING_BUDGET);811812			match maybe_nft_id {813				Some(nft_id) => {814					let token_id: TokenId = nft_id.into();815816					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;817					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;818819					<PalletNft<T>>::set_scoped_token_property(820						collection_id,821						token_id,822						PropertyScope::Rmrk,823						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,824					)?;825				}826				None => {827					let collection = Self::get_typed_nft_collection(828						collection_id,829						misc::CollectionType::Regular,830					)?;831832					Self::check_collection_owner(&collection, &sender)?;833834					<PalletCommon<T>>::set_scoped_collection_property(835						collection_id,836						PropertyScope::Rmrk,837						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,838					)?;839				}840			}841842			Self::deposit_event(Event::PropertySet {843				collection_id: rmrk_collection_id,844				maybe_nft_id,845				key,846				value,847			});848849			Ok(())850		}851852		/// set a different order of resource priority853		#[transactional]854		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]855		pub fn set_priority(856			origin: OriginFor<T>,857			rmrk_collection_id: RmrkCollectionId,858			rmrk_nft_id: RmrkNftId,859			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,860		) -> DispatchResult {861			let sender = ensure_signed(origin)?;862			let sender = T::CrossAccountId::from_sub(sender);863864			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865			let nft_id = rmrk_nft_id.into();866867			let collection =868				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869			collection.check_is_external()?;870871			let budget = budget::Value::new(NESTING_BUDGET);872873			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;874			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;875876			<PalletNft<T>>::set_scoped_token_property(877				collection_id,878				nft_id,879				PropertyScope::Rmrk,880				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,881			)?;882883			Self::deposit_event(Event::<T>::PrioritySet {884				collection_id: rmrk_collection_id,885				nft_id: rmrk_nft_id,886			});887888			Ok(())889		}890891		/// Create basic resource892		#[transactional]893		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]894		pub fn add_basic_resource(895			origin: OriginFor<T>,896			rmrk_collection_id: RmrkCollectionId,897			nft_id: RmrkNftId,898			resource: RmrkBasicResource,899		) -> DispatchResult {900			let sender = ensure_signed(origin.clone())?;901902			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903			let collection =904				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905			collection.check_is_external()?;906907			let resource_id = Self::resource_add(908				sender,909				collection_id,910				nft_id.into(),911				RmrkResourceTypes::Basic(resource),912			)?;913914			Self::deposit_event(Event::ResourceAdded {915				nft_id,916				resource_id,917			});918			Ok(())919		}920921		/// Create composable resource922		#[transactional]923		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]924		pub fn add_composable_resource(925			origin: OriginFor<T>,926			rmrk_collection_id: RmrkCollectionId,927			nft_id: RmrkNftId,928			resource: RmrkComposableResource,929		) -> DispatchResult {930			let sender = ensure_signed(origin.clone())?;931932			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;933			let collection =934				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935			collection.check_is_external()?;936937			let resource_id = Self::resource_add(938				sender,939				collection_id,940				nft_id.into(),941				RmrkResourceTypes::Composable(resource),942			)?;943944			Self::deposit_event(Event::ResourceAdded {945				nft_id,946				resource_id,947			});948			Ok(())949		}950951		/// Create slot resource952		#[transactional]953		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]954		pub fn add_slot_resource(955			origin: OriginFor<T>,956			rmrk_collection_id: RmrkCollectionId,957			nft_id: RmrkNftId,958			resource: RmrkSlotResource,959		) -> DispatchResult {960			let sender = ensure_signed(origin.clone())?;961962			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;963			let collection =964				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965			collection.check_is_external()?;966967			let resource_id = Self::resource_add(968				sender,969				collection_id,970				nft_id.into(),971				RmrkResourceTypes::Slot(resource),972			)?;973974			Self::deposit_event(Event::ResourceAdded {975				nft_id,976				resource_id,977			});978			Ok(())979		}980981		/// remove resource982		#[transactional]983		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]984		pub fn remove_resource(985			origin: OriginFor<T>,986			rmrk_collection_id: RmrkCollectionId,987			nft_id: RmrkNftId,988			resource_id: RmrkResourceId,989		) -> DispatchResult {990			let sender = ensure_signed(origin.clone())?;991992			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;993			let collection =994				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;995			collection.check_is_external()?;996997			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;998999			Self::deposit_event(Event::ResourceRemoval {1000				nft_id,1001				resource_id,1002			});1003			Ok(())1004		}1005	}1006}10071008impl<T: Config> Pallet<T> {1009	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1010		let key = rmrk_key.to_key::<T>()?;10111012		let scoped_key = PropertyScope::Rmrk1013			.apply(key)1014			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10151016		Ok(scoped_key)1017	}10181019	// todo think about renaming these1020	pub fn rmrk_property<E: Encode>(1021		rmrk_key: RmrkProperty,1022		value: &E,1023	) -> Result<Property, DispatchError> {1024		let key = rmrk_key.to_key::<T>()?;10251026		let value = Self::encode_property(value)?;10271028		let property = Property { key, value };10291030		Ok(property)1031	}10321033	pub fn encode_property<E: Encode, S: Get<u32>>(1034		value: &E,1035	) -> Result<BoundedBytes<S>, DispatchError> {1036		let value = value1037			.encode()1038			.try_into()1039			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10401041		Ok(value)1042	}10431044	pub fn decode_property<D: Decode, S: Get<u32>>(1045		vec: &BoundedBytes<S>,1046	) -> Result<D, DispatchError> {1047		vec.decode()1048			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1049	}10501051	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1052	where1053		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1054	{1055		vec.rebind()1056			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1057	}10581059	fn init_collection(1060		sender: T::CrossAccountId,1061		data: CreateCollectionData<T::AccountId>,1062		properties: impl Iterator<Item = Property>,1063	) -> Result<CollectionId, DispatchError> {1064		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10651066		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1067			return Err(<Error<T>>::NoAvailableCollectionId.into());1068		}10691070		<PalletCommon<T>>::set_scoped_collection_properties(1071			collection_id?,1072			PropertyScope::Rmrk,1073			properties,1074		)?;10751076		collection_id1077	}10781079	pub fn create_nft(1080		sender: &T::CrossAccountId,1081		owner: &T::CrossAccountId,1082		collection: &NonfungibleHandle<T>,1083		properties: impl Iterator<Item = Property>,1084	) -> Result<TokenId, DispatchError> {1085		let data = CreateNftExData {1086			properties: BoundedVec::default(),1087			owner: owner.clone(),1088		};10891090		let budget = budget::Value::new(NESTING_BUDGET);10911092		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10931094		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10951096		<PalletNft<T>>::set_scoped_token_properties(1097			collection.id,1098			nft_id,1099			PropertyScope::Rmrk,1100			properties,1101		)?;11021103		Ok(nft_id)1104	}11051106	fn destroy_nft(1107		sender: T::CrossAccountId,1108		collection_id: CollectionId,1109		token_id: TokenId,1110		max_burns: u32,1111		error_if_not_owned: Error<T>,1112	) -> DispatchResultWithPostInfo {1113		let collection =1114			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11151116		let token_data =1117			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11181119		let from = token_data.owner;11201121		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11221123		ensure!(1124			<PalletStructure<T>>::check_indirectly_owned(1125				sender.clone(),1126				collection_id,1127				token_id,1128				None,1129				&owner_check_budget1130			)?,1131			error_if_not_owned,1132		);11331134		let burns_budget = budget::Value::new(max_burns);1135		let breadth_budget = budget::Value::new(max_burns);11361137		<PalletNft<T>>::burn_recursively(1138			&collection,1139			&from,1140			token_id,1141			&burns_budget,1142			&breadth_budget,1143		)1144	}11451146	fn acquire_next_resource_id(1147		collection_id: CollectionId,1148		nft_id: TokenId,1149	) -> Result<RmrkResourceId, DispatchError> {1150		let resource_id: RmrkResourceId =1151			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11521153		let next_id = resource_id1154			.checked_add(1)1155			.ok_or(<Error<T>>::NoAvailableResourceId)?;11561157		<PalletNft<T>>::set_scoped_token_property(1158			collection_id,1159			nft_id,1160			PropertyScope::Rmrk,1161			Self::rmrk_property(NextResourceId, &next_id)?,1162		)?;11631164		Ok(resource_id)1165	}11661167	fn resource_add(1168		sender: T::AccountId,1169		collection_id: CollectionId,1170		nft_id: TokenId,1171		resource: RmrkResourceTypes,1172	) -> Result<RmrkResourceId, DispatchError> {1173		let collection =1174			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1175		ensure!(collection.owner == sender, Error::<T>::NoPermission);11761177		let sender = T::CrossAccountId::from_sub(sender);1178		let budget = budget::Value::new(NESTING_BUDGET);11791180		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1181			.map_err(Self::map_unique_err_to_proxy)?;11821183		let pending = sender != nft_owner;11841185		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11861187		let resource_info = RmrkResourceInfo {1188			id,1189			resource,1190			pending,1191			pending_removal: false,1192		};11931194		<PalletNft<T>>::try_mutate_token_sys_property(1195			collection_id,1196			nft_id,1197			PropertyScope::Rmrk,1198			Self::rmrk_property_key(ResourceId(id))?,1199			|value| -> DispatchResult {1200				*value = Some(Self::encode_property(&resource_info)?);12011202				Ok(())1203			},1204		)?;12051206		Ok(id)1207	}12081209	fn resource_remove(1210		sender: T::AccountId,1211		collection_id: CollectionId,1212		nft_id: TokenId,1213		resource_id: RmrkResourceId,1214	) -> DispatchResult {1215		let collection =1216			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1217		ensure!(collection.owner == sender, Error::<T>::NoPermission);12181219		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1220		let scope = PropertyScope::Rmrk;12211222		ensure!(1223			<PalletNft<T>>::token_sys_property((1224				collection_id,1225				nft_id,1226				scope,1227				resource_id_key.clone()1228			))1229			.is_some(),1230			<Error<T>>::ResourceDoesntExist1231		);12321233		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1234		let topmost_owner =1235			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12361237		let sender = T::CrossAccountId::from_sub(sender);1238		if topmost_owner == sender {1239			<PalletNft<T>>::remove_token_sys_property(1240				collection_id,1241				nft_id,1242				PropertyScope::Rmrk,1243				Self::rmrk_property_key(ResourceId(resource_id))?,1244			);1245		} else {1246			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1247				res.pending_removal = true;12481249				Ok(())1250			})?;1251		}12521253		Ok(())1254	}12551256	fn try_mutate_resource_info(1257		collection_id: CollectionId,1258		nft_id: TokenId,1259		resource_id: RmrkResourceId,1260		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1261	) -> DispatchResult {1262		<PalletNft<T>>::try_mutate_token_sys_property(1263			collection_id,1264			nft_id,1265			PropertyScope::Rmrk,1266			Self::rmrk_property_key(ResourceId(resource_id))?,1267			|value| match value {1268				Some(value) => {1269					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12701271					f(&mut resource_info)?;12721273					*value = Self::encode_property(&resource_info)?;12741275					Ok(())1276				}1277				None => Err(<Error<T>>::ResourceDoesntExist.into()),1278			},1279		)1280	}12811282	fn change_collection_owner(1283		collection_id: CollectionId,1284		collection_type: misc::CollectionType,1285		sender: T::AccountId,1286		new_owner: T::AccountId,1287	) -> DispatchResult {1288		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1289		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12901291		let mut collection = collection.into_inner();12921293		collection.owner = new_owner;1294		collection.save()1295	}12961297	fn check_collection_owner(1298		collection: &NonfungibleHandle<T>,1299		account: &T::CrossAccountId,1300	) -> DispatchResult {1301		collection1302			.check_is_owner(account)1303			.map_err(Self::map_unique_err_to_proxy)1304	}13051306	pub fn last_collection_idx() -> RmrkCollectionId {1307		<CollectionIndex<T>>::get()1308	}13091310	pub fn unique_collection_id(1311		rmrk_collection_id: RmrkCollectionId,1312	) -> Result<CollectionId, DispatchError> {1313		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1314			.map_err(|_| <Error<T>>::CollectionUnknown.into())1315	}13161317	pub fn rmrk_collection_id(1318		unique_collection_id: CollectionId,1319	) -> Result<RmrkCollectionId, DispatchError> {1320		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1321	}13221323	pub fn get_nft_collection(1324		collection_id: CollectionId,1325	) -> Result<NonfungibleHandle<T>, DispatchError> {1326		let collection = <CollectionHandle<T>>::try_get(collection_id)1327			.map_err(|_| <Error<T>>::CollectionUnknown)?;13281329		match collection.mode {1330			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1331			_ => Err(<Error<T>>::CollectionUnknown.into()),1332		}1333	}13341335	pub fn collection_exists(collection_id: CollectionId) -> bool {1336		<CollectionHandle<T>>::try_get(collection_id).is_ok()1337	}13381339	pub fn get_collection_property(1340		collection_id: CollectionId,1341		key: RmrkProperty,1342	) -> Result<PropertyValue, DispatchError> {1343		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1344			.get(&Self::rmrk_property_key(key)?)1345			.ok_or(<Error<T>>::CollectionUnknown)?1346			.clone();13471348		Ok(collection_property)1349	}13501351	pub fn get_collection_property_decoded<V: Decode>(1352		collection_id: CollectionId,1353		key: RmrkProperty,1354	) -> Result<V, DispatchError> {1355		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1356	}13571358	pub fn get_collection_type(1359		collection_id: CollectionId,1360	) -> Result<misc::CollectionType, DispatchError> {1361		Self::get_collection_property_decoded(collection_id, CollectionType)1362			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1363	}13641365	pub fn ensure_collection_type(1366		collection_id: CollectionId,1367		collection_type: misc::CollectionType,1368	) -> DispatchResult {1369		let actual_type = Self::get_collection_type(collection_id)?;1370		ensure!(1371			actual_type == collection_type,1372			<CommonError<T>>::NoPermission1373		);13741375		Ok(())1376	}13771378	pub fn get_typed_nft_collection(1379		collection_id: CollectionId,1380		collection_type: misc::CollectionType,1381	) -> Result<NonfungibleHandle<T>, DispatchError> {1382		Self::ensure_collection_type(collection_id, collection_type)?;13831384		Self::get_nft_collection(collection_id)1385	}13861387	pub fn get_typed_nft_collection_mapped(1388		rmrk_collection_id: RmrkCollectionId,1389		collection_type: misc::CollectionType,1390	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1391		let unique_collection_id = match collection_type {1392			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1393			_ => rmrk_collection_id.into(),1394		};13951396		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13971398		Ok((collection, unique_collection_id))1399	}14001401	pub fn get_nft_property(1402		collection_id: CollectionId,1403		nft_id: TokenId,1404		key: RmrkProperty,1405	) -> Result<PropertyValue, DispatchError> {1406		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1407			.get(&Self::rmrk_property_key(key)?)1408			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1409			.clone();14101411		Ok(nft_property)1412	}14131414	pub fn get_nft_property_decoded<V: Decode>(1415		collection_id: CollectionId,1416		nft_id: TokenId,1417		key: RmrkProperty,1418	) -> Result<V, DispatchError> {1419		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1420	}14211422	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1423		<TokenData<T>>::contains_key((collection_id, nft_id))1424	}14251426	pub fn get_nft_type(1427		collection_id: CollectionId,1428		token_id: TokenId,1429	) -> Result<NftType, DispatchError> {1430		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1431			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1432	}14331434	pub fn ensure_nft_type(1435		collection_id: CollectionId,1436		token_id: TokenId,1437		nft_type: NftType,1438	) -> DispatchResult {1439		let actual_type = Self::get_nft_type(collection_id, token_id)?;1440		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14411442		Ok(())1443	}14441445	pub fn ensure_nft_owner(1446		collection_id: CollectionId,1447		token_id: TokenId,1448		possible_owner: &T::CrossAccountId,1449		nesting_budget: &dyn budget::Budget,1450	) -> DispatchResult {1451		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1452			possible_owner.clone(),1453			collection_id,1454			token_id,1455			None,1456			nesting_budget,1457		)1458		.map_err(Self::map_unique_err_to_proxy)?;14591460		ensure!(is_owned, <Error<T>>::NoPermission);14611462		Ok(())1463	}14641465	pub fn filter_user_properties<Key, Value, R, Mapper>(1466		collection_id: CollectionId,1467		token_id: Option<TokenId>,1468		filter_keys: Option<Vec<RmrkPropertyKey>>,1469		mapper: Mapper,1470	) -> Result<Vec<R>, DispatchError>1471	where1472		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1473		Value: Decode + Default,1474		Mapper: Fn(Key, Value) -> R,1475	{1476		filter_keys1477			.map(|keys| {1478				let properties = keys1479					.into_iter()1480					.filter_map(|key| {1481						let key: Key = key.try_into().ok()?;14821483						let value = match token_id {1484							Some(token_id) => Self::get_nft_property_decoded(1485								collection_id,1486								token_id,1487								UserProperty(key.as_ref()),1488							),1489							None => Self::get_collection_property_decoded(1490								collection_id,1491								UserProperty(key.as_ref()),1492							),1493						}1494						.ok()?;14951496						Some(mapper(key, value))1497					})1498					.collect();14991500				Ok(properties)1501			})1502			.unwrap_or_else(|| {1503				let properties =1504					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15051506				Ok(properties)1507			})1508	}15091510	pub fn iterate_user_properties<Key, Value, R, Mapper>(1511		collection_id: CollectionId,1512		token_id: Option<TokenId>,1513		mapper: Mapper,1514	) -> Result<impl Iterator<Item = R>, DispatchError>1515	where1516		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1517		Value: Decode + Default,1518		Mapper: Fn(Key, Value) -> R,1519	{1520		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15211522		let properties = match token_id {1523			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1524			None => <PalletCommon<T>>::collection_properties(collection_id),1525		};15261527		let properties = properties.into_iter().filter_map(move |(key, value)| {1528			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15291530			let key: Key = key.to_vec().try_into().ok()?;1531			let value: Value = value.decode().ok()?;15321533			Some(mapper(key, value))1534		});15351536		Ok(properties)1537	}15381539	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1540		map_unique_err_to_proxy! {1541			match err {1542				CommonError::NoPermission => NoPermission,1543				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1544				CommonError::PublicMintingNotAllowed => NoPermission,1545				CommonError::TokenNotFound => NoAvailableNftId,1546				CommonError::ApprovedValueTooLow => NoPermission,1547				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1548				StructureError::TokenNotFound => NoAvailableNftId,1549				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1550			}1551		}1552	}1553}