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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs41.6 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::*;4748const 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::storage]72	pub type RmrkInernalCollectionId<T: Config> =73		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475	#[pallet::pallet]76	#[pallet::generate_store(pub(super) trait Store)]77	pub struct Pallet<T>(_);7879	#[pallet::event]80	#[pallet::generate_deposit(pub(super) fn deposit_event)]81	pub enum Event<T: Config> {82		CollectionCreated {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		CollectionDestroyed {87			issuer: T::AccountId,88			collection_id: RmrkCollectionId,89		},90		IssuerChanged {91			old_issuer: T::AccountId,92			new_issuer: T::AccountId,93			collection_id: RmrkCollectionId,94		},95		CollectionLocked {96			issuer: T::AccountId,97			collection_id: RmrkCollectionId,98		},99		NftMinted {100			owner: T::AccountId,101			collection_id: RmrkCollectionId,102			nft_id: RmrkNftId,103		},104		NFTBurned {105			owner: T::AccountId,106			nft_id: RmrkNftId,107		},108		NFTSent {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113			approval_required: bool,114		},115		NFTAccepted {116			sender: T::AccountId,117			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118			collection_id: RmrkCollectionId,119			nft_id: RmrkNftId,120		},121		NFTRejected {122			sender: T::AccountId,123			collection_id: RmrkCollectionId,124			nft_id: RmrkNftId,125		},126		PropertySet {127			collection_id: RmrkCollectionId,128			maybe_nft_id: Option<RmrkNftId>,129			key: RmrkKeyString,130			value: RmrkValueString,131		},132		ResourceAdded {133			nft_id: RmrkNftId,134			resource_id: RmrkResourceId,135		},136		ResourceRemoval {137			nft_id: RmrkNftId,138			resource_id: RmrkResourceId,139		},140		ResourceAccepted {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		ResourceRemovalAccepted {145			nft_id: RmrkNftId,146			resource_id: RmrkResourceId,147		},148		PrioritySet {149			collection_id: RmrkCollectionId,150			nft_id: RmrkNftId,151		},152	}153154	#[pallet::error]155	pub enum Error<T> {156		/* Unique-specific events */157		CorruptedCollectionType,158		NftTypeEncodeError,159		RmrkPropertyKeyIsTooLong,160		RmrkPropertyValueIsTooLong,161162		/* RMRK compatible events */163		CollectionNotEmpty,164		NoAvailableCollectionId,165		NoAvailableNftId,166		CollectionUnknown,167		NoPermission,168		NonTransferable,169		CollectionFullOrLocked,170		ResourceDoesntExist,171		CannotSendToDescendentOrSelf,172		CannotAcceptNonOwnedNft,173		CannotRejectNonOwnedNft,174		ResourceNotPending,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(NestingRule::Owner),204					..Default::default()205				}),206				..Default::default()207			};208209			let unique_collection_id = Self::init_collection(210				T::CrossAccountId::from_sub(sender.clone()),211				data,212				[213					Self::rmrk_property(Metadata, &metadata)?,214					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,215				]216				.into_iter(),217			)?;218			let rmrk_collection_id = <CollectionIndex<T>>::get();219220			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);221			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);222223			<CollectionIndex<T>>::mutate(|n| *n += 1);224225			Self::deposit_event(Event::CollectionCreated {226				issuer: sender,227				collection_id: rmrk_collection_id,228			});229230			Ok(())231		}232233		/// destroy collection234		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]235		#[transactional]236		pub fn destroy_collection(237			origin: OriginFor<T>,238			collection_id: RmrkCollectionId,239		) -> DispatchResult {240			let sender = ensure_signed(origin)?;241			let cross_sender = T::CrossAccountId::from_sub(sender.clone());242243			let collection = Self::get_typed_nft_collection(244				Self::unique_collection_id(collection_id)?,245				misc::CollectionType::Regular,246			)?;247			collection.check_is_external()?;248249			<PalletNft<T>>::destroy_collection(collection, &cross_sender)250				.map_err(Self::map_unique_err_to_proxy)?;251252			Self::deposit_event(Event::CollectionDestroyed {253				issuer: sender,254				collection_id,255			});256257			Ok(())258		}259260		/// Change the issuer of a collection261		///262		/// Parameters:263		/// - `origin`: sender of the transaction264		/// - `collection_id`: collection id of the nft to change issuer of265		/// - `new_issuer`: Collection's new issuer266		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]267		#[transactional]268		pub fn change_collection_issuer(269			origin: OriginFor<T>,270			collection_id: RmrkCollectionId,271			new_issuer: <T::Lookup as StaticLookup>::Source,272		) -> DispatchResult {273			let sender = ensure_signed(origin)?;274275			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;276			collection.check_is_external()?;277278			let new_issuer = T::Lookup::lookup(new_issuer)?;279280			Self::change_collection_owner(281				Self::unique_collection_id(collection_id)?,282				misc::CollectionType::Regular,283				sender.clone(),284				new_issuer.clone(),285			)?;286287			Self::deposit_event(Event::IssuerChanged {288				old_issuer: sender,289				new_issuer,290				collection_id,291			});292293			Ok(())294		}295296		/// lock collection297		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]298		#[transactional]299		pub fn lock_collection(300			origin: OriginFor<T>,301			collection_id: RmrkCollectionId,302		) -> DispatchResult {303			let sender = ensure_signed(origin)?;304			let cross_sender = T::CrossAccountId::from_sub(sender.clone());305306			let collection = Self::get_typed_nft_collection(307				Self::unique_collection_id(collection_id)?,308				misc::CollectionType::Regular,309			)?;310			collection.check_is_external()?;311312			Self::check_collection_owner(&collection, &cross_sender)?;313314			let token_count = collection.total_supply();315316			let mut collection = collection.into_inner();317			collection.limits.token_limit = Some(token_count);318			collection.save()?;319320			Self::deposit_event(Event::CollectionLocked {321				issuer: sender,322				collection_id,323			});324325			Ok(())326		}327328		/// Mints an NFT in the specified collection329		/// Sets metadata and the royalty attribute330		///331		/// Parameters:332		/// - `collection_id`: The class of the asset to be minted.333		/// - `nft_id`: The nft value of the asset to be minted.334		/// - `recipient`: Receiver of the royalty335		/// - `royalty`: Permillage reward from each trade for the Recipient336		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash337		/// - `transferable`: Ability to transfer this NFT338		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]339		#[transactional]340		pub fn mint_nft(341			origin: OriginFor<T>,342			owner: T::AccountId,343			collection_id: RmrkCollectionId,344			recipient: Option<T::AccountId>,345			royalty_amount: Option<Permill>,346			metadata: RmrkString,347			transferable: bool,348		) -> DispatchResult {349			let sender = ensure_signed(origin)?;350			let sender = T::CrossAccountId::from_sub(sender);351			let cross_owner = T::CrossAccountId::from_sub(owner.clone());352353			let collection = Self::get_typed_nft_collection(354				Self::unique_collection_id(collection_id)?,355				misc::CollectionType::Regular,356			)?;357			collection.check_is_external()?;358359			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {360				recipient: recipient.unwrap_or_else(|| owner.clone()),361				amount,362			});363364			let nft_id = Self::create_nft(365				&sender,366				&cross_owner,367				&collection,368				[369					Self::rmrk_property(TokenType, &NftType::Regular)?,370					Self::rmrk_property(Transferable, &transferable)?,371					Self::rmrk_property(PendingNftAccept, &false)?,372					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,373					Self::rmrk_property(Metadata, &metadata)?,374					Self::rmrk_property(Equipped, &false)?,375					Self::rmrk_property(376						ResourceCollection,377						&Self::init_collection(378							sender.clone(),379							CreateCollectionData {380								..Default::default()381							},382							[Self::rmrk_property(383								CollectionType,384								&misc::CollectionType::Resource,385							)?]386							.into_iter(),387						)?,388					)?, // todo possibly add limits to the collection if rmrk warrants them389					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390				]391				.into_iter(),392			)393			.map_err(|err| match err {394				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),395				err => Self::map_unique_err_to_proxy(err),396			})?;397398			Self::deposit_event(Event::NftMinted {399				owner,400				collection_id,401				nft_id: nft_id.0,402			});403404			Ok(())405		}406407		/// burn nft408		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]409		#[transactional]410		pub fn burn_nft(411			origin: OriginFor<T>,412			collection_id: RmrkCollectionId,413			nft_id: RmrkNftId,414		) -> DispatchResult {415			let sender = ensure_signed(origin)?;416			let cross_sender = T::CrossAccountId::from_sub(sender.clone());417418			let collection = Self::get_typed_nft_collection(419				Self::unique_collection_id(collection_id)?,420				misc::CollectionType::Regular,421			)?;422			collection.check_is_external()?;423424			Self::destroy_nft(425				cross_sender,426				Self::unique_collection_id(collection_id)?,427				nft_id.into(),428			)429			.map_err(Self::map_unique_err_to_proxy)?;430431			Self::deposit_event(Event::NFTBurned {432				owner: sender,433				nft_id,434			});435436			Ok(())437		}438439		/// Transfers a NFT from an Account or NFT A to another Account or NFT B440		///441		/// Parameters:442		/// - `origin`: sender of the transaction443		/// - `rmrk_collection_id`: collection id of the nft to be transferred444		/// - `rmrk_nft_id`: nft id of the nft to be transferred445		/// - `new_owner`: new owner of the nft which can be either an account or a NFT446		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]447		#[transactional]448		pub fn send(449			origin: OriginFor<T>,450			rmrk_collection_id: RmrkCollectionId,451			rmrk_nft_id: RmrkNftId,452			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,453		) -> DispatchResult {454			let sender = ensure_signed(origin.clone())?;455			let cross_sender = T::CrossAccountId::from_sub(sender.clone());456457			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;458			let nft_id = rmrk_nft_id.into();459460			let collection =461				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;462			collection.check_is_external()?;463464			let token_data =465				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;466467			let from = token_data.owner;468469			ensure!(470				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,471				<Error<T>>::NonTransferable472			);473474			ensure!(475				!Self::get_nft_property_decoded(476					collection_id,477					nft_id,478					RmrkProperty::PendingNftAccept479				)?,480				<Error<T>>::NoPermission481			);482483			let target_owner;484			let approval_required;485486			match new_owner {487				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {488					target_owner = T::CrossAccountId::from_sub(account_id.clone());489					approval_required = false;490				}491				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(492					target_collection_id,493					target_nft_id,494				) => {495					let target_collection_id = Self::unique_collection_id(target_collection_id)?;496497					let target_nft_budget = budget::Value::new(NESTING_BUDGET);498499					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(500						target_collection_id,501						target_nft_id.into(),502						Some((collection_id, nft_id)),503						&target_nft_budget,504					)505					.map_err(Self::map_unique_err_to_proxy)?;506507					approval_required = cross_sender != target_nft_owner;508509					if approval_required {510						target_owner = target_nft_owner;511512						<PalletNft<T>>::set_scoped_token_property(513							collection.id,514							nft_id,515							PropertyScope::Rmrk,516							Self::rmrk_property(PendingNftAccept, &approval_required)?,517						)?;518					} else {519						target_owner = T::CrossTokenAddressMapping::token_to_address(520							target_collection_id,521							target_nft_id.into(),522						);523					}524				}525			}526527			let src_nft_budget = budget::Value::new(NESTING_BUDGET);528529			<PalletNft<T>>::transfer_from(530				&collection,531				&cross_sender,532				&from,533				&target_owner,534				nft_id,535				&src_nft_budget,536			)537			.map_err(Self::map_unique_err_to_proxy)?;538539			Self::deposit_event(Event::NFTSent {540				sender,541				recipient: new_owner,542				collection_id: rmrk_collection_id,543				nft_id: rmrk_nft_id,544				approval_required,545			});546547			Ok(())548		}549550		/// Accepts an NFT sent from another account to self or owned NFT551		///552		/// Parameters:553		/// - `origin`: sender of the transaction554		/// - `rmrk_collection_id`: collection id of the nft to be accepted555		/// - `rmrk_nft_id`: nft id of the nft to be accepted556		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was557		///   sent to558		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]559		#[transactional]560		pub fn accept_nft(561			origin: OriginFor<T>,562			rmrk_collection_id: RmrkCollectionId,563			rmrk_nft_id: RmrkNftId,564			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,565		) -> DispatchResult {566			let sender = ensure_signed(origin.clone())?;567			let cross_sender = T::CrossAccountId::from_sub(sender.clone());568569			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;570			let nft_id = rmrk_nft_id.into();571572			let collection =573				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;574			collection.check_is_external()?;575576			let new_cross_owner = match new_owner {577				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {578					T::CrossAccountId::from_sub(account_id.clone())579				}580				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(581					target_collection_id,582					target_nft_id,583				) => {584					let target_collection_id = Self::unique_collection_id(target_collection_id)?;585586					T::CrossTokenAddressMapping::token_to_address(587						target_collection_id,588						TokenId(target_nft_id),589					)590				}591			};592593			let budget = budget::Value::new(NESTING_BUDGET);594595			<PalletNft<T>>::transfer(596				&collection,597				&cross_sender,598				&new_cross_owner,599				nft_id,600				&budget,601			)602			.map_err(|err| {603				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {604					<Error<T>>::CannotAcceptNonOwnedNft.into()605				} else {606					Self::map_unique_err_to_proxy(err)607				}608			})?;609610			<PalletNft<T>>::set_scoped_token_property(611				collection.id,612				nft_id,613				PropertyScope::Rmrk,614				Self::rmrk_property(PendingNftAccept, &false)?,615			)?;616617			Self::deposit_event(Event::NFTAccepted {618				sender,619				recipient: new_owner,620				collection_id: rmrk_collection_id,621				nft_id: rmrk_nft_id,622			});623624			Ok(())625		}626627		/// Rejects an NFT sent from another account to self or owned NFT628		///629		/// Parameters:630		/// - `origin`: sender of the transaction631		/// - `rmrk_collection_id`: collection id of the nft to be accepted632		/// - `rmrk_nft_id`: nft id of the nft to be accepted633		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]634		#[transactional]635		pub fn reject_nft(636			origin: OriginFor<T>,637			rmrk_collection_id: RmrkCollectionId,638			rmrk_nft_id: RmrkNftId,639		) -> DispatchResult {640			let sender = ensure_signed(origin)?;641			let cross_sender = T::CrossAccountId::from_sub(sender.clone());642643			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;644			let nft_id = rmrk_nft_id.into();645646			let collection =647				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648			collection.check_is_external()?;649650			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {651				if err == <CommonError<T>>::NoPermission.into()652					|| err == <CommonError<T>>::ApprovedValueTooLow.into()653				{654					<Error<T>>::CannotRejectNonOwnedNft.into()655				} else {656					Self::map_unique_err_to_proxy(err)657				}658			})?;659660			Self::deposit_event(Event::NFTRejected {661				sender,662				collection_id: rmrk_collection_id,663				nft_id: rmrk_nft_id,664			});665666			Ok(())667		}668669		/// accept the addition of a new resource to an existing NFT670		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]671		#[transactional]672		pub fn accept_resource(673			origin: OriginFor<T>,674			rmrk_collection_id: RmrkCollectionId,675			rmrk_nft_id: RmrkNftId,676			rmrk_resource_id: RmrkResourceId,677		) -> DispatchResult {678			let sender = ensure_signed(origin)?;679			let cross_sender = T::CrossAccountId::from_sub(sender);680681			let collection_id = Self::unique_collection_id(rmrk_collection_id)682				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;683			let collection =684				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;685			collection.check_is_external()?;686687			let nft_id = rmrk_nft_id.into();688			let resource_id = rmrk_resource_id.into();689690			let budget = budget::Value::new(NESTING_BUDGET);691692			let nft_owner =693				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)694					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;695696			let resource_collection_id: CollectionId =697				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)698					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;699700			let is_pending: bool = Self::get_nft_property_decoded(701				resource_collection_id,702				resource_id,703				PendingResourceAccept,704			)705			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707			ensure!(is_pending, <Error<T>>::ResourceNotPending);708709			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711			<PalletNft<T>>::set_scoped_token_property(712				resource_collection_id,713				rmrk_resource_id.into(),714				PropertyScope::Rmrk,715				Self::rmrk_property(PendingResourceAccept, &false)?,716			)?;717718			Self::deposit_event(Event::<T>::ResourceAccepted {719				nft_id: rmrk_nft_id,720				resource_id: rmrk_resource_id,721			});722723			Ok(())724		}725726		/// accept the removal of a resource of an existing NFT727		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]728		#[transactional]729		pub fn accept_resource_removal(730			origin: OriginFor<T>,731			rmrk_collection_id: RmrkCollectionId,732			rmrk_nft_id: RmrkNftId,733			rmrk_resource_id: RmrkResourceId,734		) -> DispatchResult {735			let sender = ensure_signed(origin)?;736			let cross_sender = T::CrossAccountId::from_sub(sender);737738			let collection_id = Self::unique_collection_id(rmrk_collection_id)739				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;740			let collection =741				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742			collection.check_is_external()?;743744			let nft_id = rmrk_nft_id.into();745			let resource_id = rmrk_resource_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755			let resource_collection_id: CollectionId =756				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759			let is_pending: bool = Self::get_nft_property_decoded(760				resource_collection_id,761				resource_id,762				PendingResourceRemoval,763			)764			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;765766			ensure!(is_pending, <Error<T>>::ResourceNotPending);767768			let resource_collection = Self::get_typed_nft_collection(769				resource_collection_id,770				misc::CollectionType::Resource,771			)?;772773			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())774				.map_err(Self::map_unique_err_to_proxy)?;775776			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {777				nft_id: rmrk_nft_id,778				resource_id: rmrk_resource_id,779			});780781			Ok(())782		}783784		/// set a custom value on an NFT785		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]786		#[transactional]787		pub fn set_property(788			origin: OriginFor<T>,789			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,790			maybe_nft_id: Option<RmrkNftId>,791			key: RmrkKeyString,792			value: RmrkValueString,793		) -> DispatchResult {794			let sender = ensure_signed(origin)?;795			let sender = T::CrossAccountId::from_sub(sender);796797			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;798			let collection =799				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;800			collection.check_is_external()?;801802			let budget = budget::Value::new(NESTING_BUDGET);803804			match maybe_nft_id {805				Some(nft_id) => {806					let token_id: TokenId = nft_id.into();807808					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;809					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;810811					<PalletNft<T>>::set_scoped_token_property(812						collection_id,813						token_id,814						PropertyScope::Rmrk,815						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,816					)?;817				}818				None => {819					let collection = Self::get_typed_nft_collection(820						collection_id,821						misc::CollectionType::Regular,822					)?;823824					Self::check_collection_owner(&collection, &sender)?;825826					<PalletCommon<T>>::set_scoped_collection_property(827						collection_id,828						PropertyScope::Rmrk,829						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,830					)?;831				}832			}833834			Self::deposit_event(Event::PropertySet {835				collection_id: rmrk_collection_id,836				maybe_nft_id,837				key,838				value,839			});840841			Ok(())842		}843844		/// set a different order of resource priority845		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]846		#[transactional]847		pub fn set_priority(848			origin: OriginFor<T>,849			rmrk_collection_id: RmrkCollectionId,850			rmrk_nft_id: RmrkNftId,851			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,852		) -> DispatchResult {853			let sender = ensure_signed(origin)?;854			let sender = T::CrossAccountId::from_sub(sender);855856			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;857			let nft_id = rmrk_nft_id.into();858859			let collection =860				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;861			collection.check_is_external()?;862863			let budget = budget::Value::new(NESTING_BUDGET);864865			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;866			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;867868			<PalletNft<T>>::set_scoped_token_property(869				collection_id,870				nft_id,871				PropertyScope::Rmrk,872				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,873			)?;874875			Self::deposit_event(Event::<T>::PrioritySet {876				collection_id: rmrk_collection_id,877				nft_id: rmrk_nft_id,878			});879880			Ok(())881		}882883		/// Create basic resource884		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885		#[transactional]886		pub fn add_basic_resource(887			origin: OriginFor<T>,888			rmrk_collection_id: RmrkCollectionId,889			nft_id: RmrkNftId,890			resource: RmrkBasicResource,891		) -> DispatchResult {892			let sender = ensure_signed(origin.clone())?;893894			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;895			let collection =896				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;897			collection.check_is_external()?;898899			let resource_id = Self::resource_add(900				sender,901				collection_id,902				nft_id.into(),903				[904					Self::rmrk_property(TokenType, &NftType::Resource)?,905					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,906					Self::rmrk_property(Src, &resource.src)?,907					Self::rmrk_property(Metadata, &resource.metadata)?,908					Self::rmrk_property(License, &resource.license)?,909					Self::rmrk_property(Thumb, &resource.thumb)?,910				]911				.into_iter(),912			)?;913914			Self::deposit_event(Event::ResourceAdded {915				nft_id,916				resource_id,917			});918			Ok(())919		}920921		/// Create composable resource922		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]923		#[transactional]924		pub fn add_composable_resource(925			origin: OriginFor<T>,926			rmrk_collection_id: RmrkCollectionId,927			nft_id: RmrkNftId,928			_resource_id: RmrkBoundedResource,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				[943					Self::rmrk_property(TokenType, &NftType::Resource)?,944					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,945					Self::rmrk_property(Parts, &resource.parts)?,946					Self::rmrk_property(Base, &resource.base)?,947					Self::rmrk_property(Src, &resource.src)?,948					Self::rmrk_property(Metadata, &resource.metadata)?,949					Self::rmrk_property(License, &resource.license)?,950					Self::rmrk_property(Thumb, &resource.thumb)?,951				]952				.into_iter(),953			)?;954955			Self::deposit_event(Event::ResourceAdded {956				nft_id,957				resource_id,958			});959			Ok(())960		}961962		/// Create slot resource963		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]964		#[transactional]965		pub fn add_slot_resource(966			origin: OriginFor<T>,967			rmrk_collection_id: RmrkCollectionId,968			nft_id: RmrkNftId,969			resource: RmrkSlotResource,970		) -> DispatchResult {971			let sender = ensure_signed(origin.clone())?;972973			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;974			let collection =975				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;976			collection.check_is_external()?;977978			let resource_id = Self::resource_add(979				sender,980				collection_id,981				nft_id.into(),982				[983					Self::rmrk_property(TokenType, &NftType::Resource)?,984					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,985					Self::rmrk_property(Base, &resource.base)?,986					Self::rmrk_property(Src, &resource.src)?,987					Self::rmrk_property(Metadata, &resource.metadata)?,988					Self::rmrk_property(Slot, &resource.slot)?,989					Self::rmrk_property(License, &resource.license)?,990					Self::rmrk_property(Thumb, &resource.thumb)?,991				]992				.into_iter(),993			)?;994995			Self::deposit_event(Event::ResourceAdded {996				nft_id,997				resource_id,998			});999			Ok(())1000		}10011002		/// remove resource1003		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1004		#[transactional]1005		pub fn remove_resource(1006			origin: OriginFor<T>,1007			rmrk_collection_id: RmrkCollectionId,1008			nft_id: RmrkNftId,1009			resource_id: RmrkResourceId,1010		) -> DispatchResult {1011			let sender = ensure_signed(origin.clone())?;10121013			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1014			let collection =1015				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1016			collection.check_is_external()?;10171018			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10191020			Self::deposit_event(Event::ResourceRemoval {1021				nft_id,1022				resource_id,1023			});1024			Ok(())1025		}1026	}1027}10281029impl<T: Config> Pallet<T> {1030	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1031		let key = rmrk_key.to_key::<T>()?;10321033		let scoped_key = PropertyScope::Rmrk1034			.apply(key)1035			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10361037		Ok(scoped_key)1038	}10391040	// todo think about renaming these1041	pub fn rmrk_property<E: Encode>(1042		rmrk_key: RmrkProperty,1043		value: &E,1044	) -> Result<Property, DispatchError> {1045		let key = rmrk_key.to_key::<T>()?;10461047		let value = value1048			.encode()1049			.try_into()1050			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10511052		let property = Property { key, value };10531054		Ok(property)1055	}10561057	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1058		vec.decode()1059			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060	}10611062	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1063	where1064		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1065	{1066		vec.rebind()1067			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1068	}10691070	fn init_collection(1071		sender: T::CrossAccountId,1072		data: CreateCollectionData<T::AccountId>,1073		properties: impl Iterator<Item = Property>,1074	) -> Result<CollectionId, DispatchError> {1075		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10761077		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1078			return Err(<Error<T>>::NoAvailableCollectionId.into());1079		}10801081		<PalletCommon<T>>::set_scoped_collection_properties(1082			collection_id?,1083			PropertyScope::Rmrk,1084			properties,1085		)?;10861087		collection_id1088	}10891090	pub fn create_nft(1091		sender: &T::CrossAccountId,1092		owner: &T::CrossAccountId,1093		collection: &NonfungibleHandle<T>,1094		properties: impl Iterator<Item = Property>,1095	) -> Result<TokenId, DispatchError> {1096		let data = CreateNftExData {1097			properties: BoundedVec::default(),1098			owner: owner.clone(),1099		};11001101		let budget = budget::Value::new(NESTING_BUDGET);11021103		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11041105		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11061107		<PalletNft<T>>::set_scoped_token_properties(1108			collection.id,1109			nft_id,1110			PropertyScope::Rmrk,1111			properties,1112		)?;11131114		Ok(nft_id)1115	}11161117	fn destroy_nft(1118		sender: T::CrossAccountId,1119		collection_id: CollectionId,1120		token_id: TokenId,1121	) -> DispatchResult {1122		let collection =1123			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11241125		let token_data =1126			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11271128		let from = token_data.owner;11291130		let budget = budget::Value::new(NESTING_BUDGET);11311132		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1133	}11341135	fn resource_add(1136		sender: T::AccountId,1137		collection_id: CollectionId,1138		token_id: TokenId,1139		resource_properties: impl Iterator<Item = Property>,1140	) -> Result<RmrkResourceId, DispatchError> {1141		let collection =1142			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1143		ensure!(collection.owner == sender, Error::<T>::NoPermission);11441145		let sender = T::CrossAccountId::from_sub(sender);1146		let budget = budget::Value::new(NESTING_BUDGET);11471148		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1149			.map_err(Self::map_unique_err_to_proxy)?;11501151		let pending = sender != nft_owner;11521153		let resource_collection_id: CollectionId =1154			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1155		let resource_collection =1156			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11571158		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11591160		let resource_id = Self::create_nft(1161			&sender,1162			&nft_owner,1163			&resource_collection,1164			resource_properties.chain(1165				[1166					Self::rmrk_property(PendingResourceAccept, &pending)?,1167					Self::rmrk_property(PendingResourceRemoval, &false)?,1168				]1169				.into_iter(),1170			),1171		)1172		.map_err(|err| match err {1173			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1174			err => Self::map_unique_err_to_proxy(err),1175		})?;11761177		Ok(resource_id.0)1178	}11791180	fn resource_remove(1181		sender: T::AccountId,1182		collection_id: CollectionId,1183		nft_id: TokenId,1184		resource_id: TokenId,1185	) -> DispatchResult {1186		let collection =1187			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1188		ensure!(collection.owner == sender, Error::<T>::NoPermission);11891190		let resource_collection_id: CollectionId =1191			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1192		let resource_collection =1193			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1194		ensure!(1195			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1196			Error::<T>::ResourceDoesntExist1197		);11981199		let budget = up_data_structs::budget::Value::new(10);1200		let topmost_owner =1201			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12021203		let sender = T::CrossAccountId::from_sub(sender);1204		if topmost_owner == sender {1205			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1206				.map_err(Self::map_unique_err_to_proxy)?;1207		} else {1208			<PalletNft<T>>::set_scoped_token_property(1209				resource_collection_id,1210				resource_id,1211				PropertyScope::Rmrk,1212				Self::rmrk_property(PendingResourceRemoval, &true)?,1213			)?;1214		}12151216		Ok(())1217	}12181219	fn change_collection_owner(1220		collection_id: CollectionId,1221		collection_type: misc::CollectionType,1222		sender: T::AccountId,1223		new_owner: T::AccountId,1224	) -> DispatchResult {1225		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1226		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12271228		let mut collection = collection.into_inner();12291230		collection.owner = new_owner;1231		collection.save()1232	}12331234	fn check_collection_owner(1235		collection: &NonfungibleHandle<T>,1236		account: &T::CrossAccountId,1237	) -> DispatchResult {1238		collection1239			.check_is_owner(account)1240			.map_err(Self::map_unique_err_to_proxy)1241	}12421243	pub fn last_collection_idx() -> RmrkCollectionId {1244		<CollectionIndex<T>>::get()1245	}12461247	pub fn unique_collection_id(1248		rmrk_collection_id: RmrkCollectionId,1249	) -> Result<CollectionId, DispatchError> {1250		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1251			.map_err(|_| <Error<T>>::CollectionUnknown.into())1252	}12531254	pub fn rmrk_collection_id(1255		unique_collection_id: CollectionId,1256	) -> Result<RmrkCollectionId, DispatchError> {1257		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1258			.map_err(|_| <Error<T>>::CollectionUnknown.into())1259	}12601261	pub fn get_nft_collection(1262		collection_id: CollectionId,1263	) -> Result<NonfungibleHandle<T>, DispatchError> {1264		let collection = <CollectionHandle<T>>::try_get(collection_id)1265			.map_err(|_| <Error<T>>::CollectionUnknown)?;12661267		match collection.mode {1268			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1269			_ => Err(<Error<T>>::CollectionUnknown.into()),1270		}1271	}12721273	pub fn collection_exists(collection_id: CollectionId) -> bool {1274		<CollectionHandle<T>>::try_get(collection_id).is_ok()1275	}12761277	pub fn get_collection_property(1278		collection_id: CollectionId,1279		key: RmrkProperty,1280	) -> Result<PropertyValue, DispatchError> {1281		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1282			.get(&Self::rmrk_property_key(key)?)1283			.ok_or(<Error<T>>::CollectionUnknown)?1284			.clone();12851286		Ok(collection_property)1287	}12881289	pub fn get_collection_property_decoded<V: Decode>(1290		collection_id: CollectionId,1291		key: RmrkProperty,1292	) -> Result<V, DispatchError> {1293		Self::decode_property(Self::get_collection_property(collection_id, key)?)1294	}12951296	pub fn get_collection_type(1297		collection_id: CollectionId,1298	) -> Result<misc::CollectionType, DispatchError> {1299		Self::get_collection_property_decoded(collection_id, CollectionType)1300			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1301	}13021303	pub fn ensure_collection_type(1304		collection_id: CollectionId,1305		collection_type: misc::CollectionType,1306	) -> DispatchResult {1307		let actual_type = Self::get_collection_type(collection_id)?;1308		ensure!(1309			actual_type == collection_type,1310			<CommonError<T>>::NoPermission1311		);13121313		Ok(())1314	}13151316	pub fn get_typed_nft_collection(1317		collection_id: CollectionId,1318		collection_type: misc::CollectionType,1319	) -> Result<NonfungibleHandle<T>, DispatchError> {1320		Self::ensure_collection_type(collection_id, collection_type)?;13211322		Self::get_nft_collection(collection_id)1323	}13241325	pub fn get_typed_nft_collection_mapped(1326		rmrk_collection_id: RmrkCollectionId,1327		collection_type: misc::CollectionType,1328	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1329		let unique_collection_id = match collection_type {1330			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1331			_ => rmrk_collection_id.into(),1332		};13331334		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13351336		Ok((collection, unique_collection_id))1337	}13381339	pub fn get_nft_property(1340		collection_id: CollectionId,1341		nft_id: TokenId,1342		key: RmrkProperty,1343	) -> Result<PropertyValue, DispatchError> {1344		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1345			.get(&Self::rmrk_property_key(key)?)1346			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1347			.clone();13481349		Ok(nft_property)1350	}13511352	pub fn get_nft_property_decoded<V: Decode>(1353		collection_id: CollectionId,1354		nft_id: TokenId,1355		key: RmrkProperty,1356	) -> Result<V, DispatchError> {1357		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1358	}13591360	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1361		<TokenData<T>>::contains_key((collection_id, nft_id))1362	}13631364	pub fn get_nft_type(1365		collection_id: CollectionId,1366		token_id: TokenId,1367	) -> Result<NftType, DispatchError> {1368		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1369			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1370	}13711372	pub fn ensure_nft_type(1373		collection_id: CollectionId,1374		token_id: TokenId,1375		nft_type: NftType,1376	) -> DispatchResult {1377		let actual_type = Self::get_nft_type(collection_id, token_id)?;1378		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13791380		Ok(())1381	}13821383	pub fn ensure_nft_owner(1384		collection_id: CollectionId,1385		token_id: TokenId,1386		possible_owner: &T::CrossAccountId,1387		nesting_budget: &dyn budget::Budget,1388	) -> DispatchResult {1389		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1390			possible_owner.clone(),1391			collection_id,1392			token_id,1393			None,1394			nesting_budget,1395		)1396		.map_err(Self::map_unique_err_to_proxy)?;13971398		ensure!(is_owned, <Error<T>>::NoPermission);13991400		Ok(())1401	}14021403	pub fn filter_user_properties<Key, Value, R, Mapper>(1404		collection_id: CollectionId,1405		token_id: Option<TokenId>,1406		filter_keys: Option<Vec<RmrkPropertyKey>>,1407		mapper: Mapper,1408	) -> Result<Vec<R>, DispatchError>1409	where1410		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1411		Value: Decode + Default,1412		Mapper: Fn(Key, Value) -> R,1413	{1414		filter_keys1415			.map(|keys| {1416				let properties = keys1417					.into_iter()1418					.filter_map(|key| {1419						let key: Key = key.try_into().ok()?;14201421						let value = match token_id {1422							Some(token_id) => Self::get_nft_property_decoded(1423								collection_id,1424								token_id,1425								UserProperty(key.as_ref()),1426							),1427							None => Self::get_collection_property_decoded(1428								collection_id,1429								UserProperty(key.as_ref()),1430							),1431						}1432						.ok()?;14331434						Some(mapper(key, value))1435					})1436					.collect();14371438				Ok(properties)1439			})1440			.unwrap_or_else(|| {1441				let properties =1442					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14431444				Ok(properties)1445			})1446	}14471448	pub fn iterate_user_properties<Key, Value, R, Mapper>(1449		collection_id: CollectionId,1450		token_id: Option<TokenId>,1451		mapper: Mapper,1452	) -> Result<impl Iterator<Item = R>, DispatchError>1453	where1454		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1455		Value: Decode + Default,1456		Mapper: Fn(Key, Value) -> R,1457	{1458		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14591460		let properties = match token_id {1461			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1462			None => <PalletCommon<T>>::collection_properties(collection_id),1463		};14641465		let properties = properties.into_iter().filter_map(move |(key, value)| {1466			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14671468			let key: Key = key.to_vec().try_into().ok()?;1469			let value: Value = value.decode().ok()?;14701471			Some(mapper(key, value))1472		});14731474		Ok(properties)1475	}14761477	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1478		map_unique_err_to_proxy! {1479			match err {1480				CommonError::NoPermission => NoPermission,1481				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1482				CommonError::PublicMintingNotAllowed => NoPermission,1483				CommonError::TokenNotFound => NoAvailableNftId,1484				CommonError::ApprovedValueTooLow => NoPermission,1485				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1486				StructureError::TokenNotFound => NoAvailableNftId,1487				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1488			}1489		}1490	}1491}