git.delta.rocks / unique-network / refs/commits / 504df77ef385

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs41.7 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(NestingPermissions {204						token_owner: true,205						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);227			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);228229			<CollectionIndex<T>>::mutate(|n| *n += 1);230231			Self::deposit_event(Event::CollectionCreated {232				issuer: sender,233				collection_id: rmrk_collection_id,234			});235236			Ok(())237		}238239		/// destroy collection240		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]241		#[transactional]242		pub fn destroy_collection(243			origin: OriginFor<T>,244			collection_id: RmrkCollectionId,245		) -> DispatchResult {246			let sender = ensure_signed(origin)?;247			let cross_sender = T::CrossAccountId::from_sub(sender.clone());248249			let collection = Self::get_typed_nft_collection(250				Self::unique_collection_id(collection_id)?,251				misc::CollectionType::Regular,252			)?;253			collection.check_is_external()?;254255			<PalletNft<T>>::destroy_collection(collection, &cross_sender)256				.map_err(Self::map_unique_err_to_proxy)?;257258			Self::deposit_event(Event::CollectionDestroyed {259				issuer: sender,260				collection_id,261			});262263			Ok(())264		}265266		/// Change the issuer of a collection267		///268		/// Parameters:269		/// - `origin`: sender of the transaction270		/// - `collection_id`: collection id of the nft to change issuer of271		/// - `new_issuer`: Collection's new issuer272		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]273		#[transactional]274		pub fn change_collection_issuer(275			origin: OriginFor<T>,276			collection_id: RmrkCollectionId,277			new_issuer: <T::Lookup as StaticLookup>::Source,278		) -> DispatchResult {279			let sender = ensure_signed(origin)?;280281			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;282			collection.check_is_external()?;283284			let new_issuer = T::Lookup::lookup(new_issuer)?;285286			Self::change_collection_owner(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289				sender.clone(),290				new_issuer.clone(),291			)?;292293			Self::deposit_event(Event::IssuerChanged {294				old_issuer: sender,295				new_issuer,296				collection_id,297			});298299			Ok(())300		}301302		/// lock collection303		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304		#[transactional]305		pub fn lock_collection(306			origin: OriginFor<T>,307			collection_id: RmrkCollectionId,308		) -> DispatchResult {309			let sender = ensure_signed(origin)?;310			let cross_sender = T::CrossAccountId::from_sub(sender.clone());311312			let collection = Self::get_typed_nft_collection(313				Self::unique_collection_id(collection_id)?,314				misc::CollectionType::Regular,315			)?;316			collection.check_is_external()?;317318			Self::check_collection_owner(&collection, &cross_sender)?;319320			let token_count = collection.total_supply();321322			let mut collection = collection.into_inner();323			collection.limits.token_limit = Some(token_count);324			collection.save()?;325326			Self::deposit_event(Event::CollectionLocked {327				issuer: sender,328				collection_id,329			});330331			Ok(())332		}333334		/// Mints an NFT in the specified collection335		/// Sets metadata and the royalty attribute336		///337		/// Parameters:338		/// - `collection_id`: The class of the asset to be minted.339		/// - `nft_id`: The nft value of the asset to be minted.340		/// - `recipient`: Receiver of the royalty341		/// - `royalty`: Permillage reward from each trade for the Recipient342		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash343		/// - `transferable`: Ability to transfer this NFT344		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345		#[transactional]346		pub fn mint_nft(347			origin: OriginFor<T>,348			owner: T::AccountId,349			collection_id: RmrkCollectionId,350			recipient: Option<T::AccountId>,351			royalty_amount: Option<Permill>,352			metadata: RmrkString,353			transferable: bool,354		) -> DispatchResult {355			let sender = ensure_signed(origin)?;356			let sender = T::CrossAccountId::from_sub(sender);357			let cross_owner = T::CrossAccountId::from_sub(owner.clone());358359			let collection = Self::get_typed_nft_collection(360				Self::unique_collection_id(collection_id)?,361				misc::CollectionType::Regular,362			)?;363			collection.check_is_external()?;364365			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {366				recipient: recipient.unwrap_or_else(|| owner.clone()),367				amount,368			});369370			let nft_id = Self::create_nft(371				&sender,372				&cross_owner,373				&collection,374				[375					Self::rmrk_property(TokenType, &NftType::Regular)?,376					Self::rmrk_property(Transferable, &transferable)?,377					Self::rmrk_property(PendingNftAccept, &false)?,378					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,379					Self::rmrk_property(Metadata, &metadata)?,380					Self::rmrk_property(Equipped, &false)?,381					Self::rmrk_property(382						ResourceCollection,383						&Self::init_collection(384							sender.clone(),385							CreateCollectionData {386								..Default::default()387							},388							[Self::rmrk_property(389								CollectionType,390								&misc::CollectionType::Resource,391							)?]392							.into_iter(),393						)?,394					)?, // todo possibly add limits to the collection if rmrk warrants them395					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,396				]397				.into_iter(),398			)399			.map_err(|err| match err {400				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),401				err => Self::map_unique_err_to_proxy(err),402			})?;403404			Self::deposit_event(Event::NftMinted {405				owner,406				collection_id,407				nft_id: nft_id.0,408			});409410			Ok(())411		}412413		/// burn nft414		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]415		#[transactional]416		pub fn burn_nft(417			origin: OriginFor<T>,418			collection_id: RmrkCollectionId,419			nft_id: RmrkNftId,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			)435			.map_err(Self::map_unique_err_to_proxy)?;436437			Self::deposit_event(Event::NFTBurned {438				owner: sender,439				nft_id,440			});441442			Ok(())443		}444445		/// Transfers a NFT from an Account or NFT A to another Account or NFT B446		///447		/// Parameters:448		/// - `origin`: sender of the transaction449		/// - `rmrk_collection_id`: collection id of the nft to be transferred450		/// - `rmrk_nft_id`: nft id of the nft to be transferred451		/// - `new_owner`: new owner of the nft which can be either an account or a NFT452		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453		#[transactional]454		pub fn send(455			origin: OriginFor<T>,456			rmrk_collection_id: RmrkCollectionId,457			rmrk_nft_id: RmrkNftId,458			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,459		) -> DispatchResult {460			let sender = ensure_signed(origin.clone())?;461			let cross_sender = T::CrossAccountId::from_sub(sender.clone());462463			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464			let nft_id = rmrk_nft_id.into();465466			let collection =467				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;468			collection.check_is_external()?;469470			let token_data =471				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;472473			let from = token_data.owner;474475			ensure!(476				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,477				<Error<T>>::NonTransferable478			);479480			ensure!(481				!Self::get_nft_property_decoded(482					collection_id,483					nft_id,484					RmrkProperty::PendingNftAccept485				)?,486				<Error<T>>::NoPermission487			);488489			let target_owner;490			let approval_required;491492			match new_owner {493				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {494					target_owner = T::CrossAccountId::from_sub(account_id.clone());495					approval_required = false;496				}497				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(498					target_collection_id,499					target_nft_id,500				) => {501					let target_collection_id = Self::unique_collection_id(target_collection_id)?;502503					let target_nft_budget = budget::Value::new(NESTING_BUDGET);504505					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(506						target_collection_id,507						target_nft_id.into(),508						Some((collection_id, nft_id)),509						&target_nft_budget,510					)511					.map_err(Self::map_unique_err_to_proxy)?;512513					approval_required = cross_sender != target_nft_owner;514515					if approval_required {516						target_owner = target_nft_owner;517518						<PalletNft<T>>::set_scoped_token_property(519							collection.id,520							nft_id,521							PropertyScope::Rmrk,522							Self::rmrk_property(PendingNftAccept, &approval_required)?,523						)?;524					} else {525						target_owner = T::CrossTokenAddressMapping::token_to_address(526							target_collection_id,527							target_nft_id.into(),528						);529					}530				}531			}532533			let src_nft_budget = budget::Value::new(NESTING_BUDGET);534535			<PalletNft<T>>::transfer_from(536				&collection,537				&cross_sender,538				&from,539				&target_owner,540				nft_id,541				&src_nft_budget,542			)543			.map_err(Self::map_unique_err_to_proxy)?;544545			Self::deposit_event(Event::NFTSent {546				sender,547				recipient: new_owner,548				collection_id: rmrk_collection_id,549				nft_id: rmrk_nft_id,550				approval_required,551			});552553			Ok(())554		}555556		/// Accepts an NFT sent from another account to self or owned NFT557		///558		/// Parameters:559		/// - `origin`: sender of the transaction560		/// - `rmrk_collection_id`: collection id of the nft to be accepted561		/// - `rmrk_nft_id`: nft id of the nft to be accepted562		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was563		///   sent to564		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]565		#[transactional]566		pub fn accept_nft(567			origin: OriginFor<T>,568			rmrk_collection_id: RmrkCollectionId,569			rmrk_nft_id: RmrkNftId,570			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,571		) -> DispatchResult {572			let sender = ensure_signed(origin.clone())?;573			let cross_sender = T::CrossAccountId::from_sub(sender.clone());574575			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;576			let nft_id = rmrk_nft_id.into();577578			let collection =579				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;580			collection.check_is_external()?;581582			let new_cross_owner = match new_owner {583				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {584					T::CrossAccountId::from_sub(account_id.clone())585				}586				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(587					target_collection_id,588					target_nft_id,589				) => {590					let target_collection_id = Self::unique_collection_id(target_collection_id)?;591592					T::CrossTokenAddressMapping::token_to_address(593						target_collection_id,594						TokenId(target_nft_id),595					)596				}597			};598599			let budget = budget::Value::new(NESTING_BUDGET);600601			<PalletNft<T>>::transfer(602				&collection,603				&cross_sender,604				&new_cross_owner,605				nft_id,606				&budget,607			)608			.map_err(|err| {609				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {610					<Error<T>>::CannotAcceptNonOwnedNft.into()611				} else {612					Self::map_unique_err_to_proxy(err)613				}614			})?;615616			<PalletNft<T>>::set_scoped_token_property(617				collection.id,618				nft_id,619				PropertyScope::Rmrk,620				Self::rmrk_property(PendingNftAccept, &false)?,621			)?;622623			Self::deposit_event(Event::NFTAccepted {624				sender,625				recipient: new_owner,626				collection_id: rmrk_collection_id,627				nft_id: rmrk_nft_id,628			});629630			Ok(())631		}632633		/// Rejects an NFT sent from another account to self or owned NFT634		///635		/// Parameters:636		/// - `origin`: sender of the transaction637		/// - `rmrk_collection_id`: collection id of the nft to be accepted638		/// - `rmrk_nft_id`: nft id of the nft to be accepted639		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]640		#[transactional]641		pub fn reject_nft(642			origin: OriginFor<T>,643			rmrk_collection_id: RmrkCollectionId,644			rmrk_nft_id: RmrkNftId,645		) -> DispatchResult {646			let sender = ensure_signed(origin)?;647			let cross_sender = T::CrossAccountId::from_sub(sender.clone());648649			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;650			let nft_id = rmrk_nft_id.into();651652			let collection =653				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;654			collection.check_is_external()?;655656			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {657				if err == <CommonError<T>>::NoPermission.into()658					|| err == <CommonError<T>>::ApprovedValueTooLow.into()659				{660					<Error<T>>::CannotRejectNonOwnedNft.into()661				} else {662					Self::map_unique_err_to_proxy(err)663				}664			})?;665666			Self::deposit_event(Event::NFTRejected {667				sender,668				collection_id: rmrk_collection_id,669				nft_id: rmrk_nft_id,670			});671672			Ok(())673		}674675		/// accept the addition of a new resource to an existing NFT676		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]677		#[transactional]678		pub fn accept_resource(679			origin: OriginFor<T>,680			rmrk_collection_id: RmrkCollectionId,681			rmrk_nft_id: RmrkNftId,682			rmrk_resource_id: RmrkResourceId,683		) -> DispatchResult {684			let sender = ensure_signed(origin)?;685			let cross_sender = T::CrossAccountId::from_sub(sender);686687			let collection_id = Self::unique_collection_id(rmrk_collection_id)688				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;689			let collection =690				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691			collection.check_is_external()?;692693			let nft_id = rmrk_nft_id.into();694			let resource_id = rmrk_resource_id.into();695696			let budget = budget::Value::new(NESTING_BUDGET);697698			let nft_owner =699				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)700					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;701702			let resource_collection_id: CollectionId =703				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)704					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;705706			let is_pending: bool = Self::get_nft_property_decoded(707				resource_collection_id,708				resource_id,709				PendingResourceAccept,710			)711			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;712713			ensure!(is_pending, <Error<T>>::ResourceNotPending);714715			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);716717			<PalletNft<T>>::set_scoped_token_property(718				resource_collection_id,719				rmrk_resource_id.into(),720				PropertyScope::Rmrk,721				Self::rmrk_property(PendingResourceAccept, &false)?,722			)?;723724			Self::deposit_event(Event::<T>::ResourceAccepted {725				nft_id: rmrk_nft_id,726				resource_id: rmrk_resource_id,727			});728729			Ok(())730		}731732		/// accept the removal of a resource of an existing NFT733		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]734		#[transactional]735		pub fn accept_resource_removal(736			origin: OriginFor<T>,737			rmrk_collection_id: RmrkCollectionId,738			rmrk_nft_id: RmrkNftId,739			rmrk_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();751			let resource_id = rmrk_resource_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_collection_id: CollectionId =762				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)763					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;764765			let is_pending: bool = Self::get_nft_property_decoded(766				resource_collection_id,767				resource_id,768				PendingResourceRemoval,769			)770			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;771772			ensure!(is_pending, <Error<T>>::ResourceNotPending);773774			let resource_collection = Self::get_typed_nft_collection(775				resource_collection_id,776				misc::CollectionType::Resource,777			)?;778779			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())780				.map_err(Self::map_unique_err_to_proxy)?;781782			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {783				nft_id: rmrk_nft_id,784				resource_id: rmrk_resource_id,785			});786787			Ok(())788		}789790		/// set a custom value on an NFT791		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792		#[transactional]793		pub fn set_property(794			origin: OriginFor<T>,795			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,796			maybe_nft_id: Option<RmrkNftId>,797			key: RmrkKeyString,798			value: RmrkValueString,799		) -> DispatchResult {800			let sender = ensure_signed(origin)?;801			let sender = T::CrossAccountId::from_sub(sender);802803			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;804			let collection =805				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;806			collection.check_is_external()?;807808			let budget = budget::Value::new(NESTING_BUDGET);809810			match maybe_nft_id {811				Some(nft_id) => {812					let token_id: TokenId = nft_id.into();813814					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;815					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;816817					<PalletNft<T>>::set_scoped_token_property(818						collection_id,819						token_id,820						PropertyScope::Rmrk,821						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,822					)?;823				}824				None => {825					let collection = Self::get_typed_nft_collection(826						collection_id,827						misc::CollectionType::Regular,828					)?;829830					Self::check_collection_owner(&collection, &sender)?;831832					<PalletCommon<T>>::set_scoped_collection_property(833						collection_id,834						PropertyScope::Rmrk,835						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,836					)?;837				}838			}839840			Self::deposit_event(Event::PropertySet {841				collection_id: rmrk_collection_id,842				maybe_nft_id,843				key,844				value,845			});846847			Ok(())848		}849850		/// set a different order of resource priority851		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]852		#[transactional]853		pub fn set_priority(854			origin: OriginFor<T>,855			rmrk_collection_id: RmrkCollectionId,856			rmrk_nft_id: RmrkNftId,857			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,858		) -> DispatchResult {859			let sender = ensure_signed(origin)?;860			let sender = T::CrossAccountId::from_sub(sender);861862			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;863			let nft_id = rmrk_nft_id.into();864865			let collection =866				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;867			collection.check_is_external()?;868869			let budget = budget::Value::new(NESTING_BUDGET);870871			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;872			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;873874			<PalletNft<T>>::set_scoped_token_property(875				collection_id,876				nft_id,877				PropertyScope::Rmrk,878				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,879			)?;880881			Self::deposit_event(Event::<T>::PrioritySet {882				collection_id: rmrk_collection_id,883				nft_id: rmrk_nft_id,884			});885886			Ok(())887		}888889		/// Create basic resource890		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]891		#[transactional]892		pub fn add_basic_resource(893			origin: OriginFor<T>,894			rmrk_collection_id: RmrkCollectionId,895			nft_id: RmrkNftId,896			resource: RmrkBasicResource,897		) -> DispatchResult {898			let sender = ensure_signed(origin.clone())?;899900			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901			let collection =902				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;903			collection.check_is_external()?;904905			let resource_id = Self::resource_add(906				sender,907				collection_id,908				nft_id.into(),909				[910					Self::rmrk_property(TokenType, &NftType::Resource)?,911					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,912					Self::rmrk_property(Src, &resource.src)?,913					Self::rmrk_property(Metadata, &resource.metadata)?,914					Self::rmrk_property(License, &resource.license)?,915					Self::rmrk_property(Thumb, &resource.thumb)?,916				]917				.into_iter(),918			)?;919920			Self::deposit_event(Event::ResourceAdded {921				nft_id,922				resource_id,923			});924			Ok(())925		}926927		/// Create composable resource928		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]929		#[transactional]930		pub fn add_composable_resource(931			origin: OriginFor<T>,932			rmrk_collection_id: RmrkCollectionId,933			nft_id: RmrkNftId,934			_resource_id: RmrkBoundedResource,935			resource: RmrkComposableResource,936		) -> DispatchResult {937			let sender = ensure_signed(origin.clone())?;938939			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;940			let collection =941				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;942			collection.check_is_external()?;943944			let resource_id = Self::resource_add(945				sender,946				collection_id,947				nft_id.into(),948				[949					Self::rmrk_property(TokenType, &NftType::Resource)?,950					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,951					Self::rmrk_property(Parts, &resource.parts)?,952					Self::rmrk_property(Base, &resource.base)?,953					Self::rmrk_property(Src, &resource.src)?,954					Self::rmrk_property(Metadata, &resource.metadata)?,955					Self::rmrk_property(License, &resource.license)?,956					Self::rmrk_property(Thumb, &resource.thumb)?,957				]958				.into_iter(),959			)?;960961			Self::deposit_event(Event::ResourceAdded {962				nft_id,963				resource_id,964			});965			Ok(())966		}967968		/// Create slot resource969		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]970		#[transactional]971		pub fn add_slot_resource(972			origin: OriginFor<T>,973			rmrk_collection_id: RmrkCollectionId,974			nft_id: RmrkNftId,975			resource: RmrkSlotResource,976		) -> DispatchResult {977			let sender = ensure_signed(origin.clone())?;978979			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;980			let collection =981				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982			collection.check_is_external()?;983984			let resource_id = Self::resource_add(985				sender,986				collection_id,987				nft_id.into(),988				[989					Self::rmrk_property(TokenType, &NftType::Resource)?,990					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,991					Self::rmrk_property(Base, &resource.base)?,992					Self::rmrk_property(Src, &resource.src)?,993					Self::rmrk_property(Metadata, &resource.metadata)?,994					Self::rmrk_property(Slot, &resource.slot)?,995					Self::rmrk_property(License, &resource.license)?,996					Self::rmrk_property(Thumb, &resource.thumb)?,997				]998				.into_iter(),999			)?;10001001			Self::deposit_event(Event::ResourceAdded {1002				nft_id,1003				resource_id,1004			});1005			Ok(())1006		}10071008		/// remove resource1009		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1010		#[transactional]1011		pub fn remove_resource(1012			origin: OriginFor<T>,1013			rmrk_collection_id: RmrkCollectionId,1014			nft_id: RmrkNftId,1015			resource_id: RmrkResourceId,1016		) -> DispatchResult {1017			let sender = ensure_signed(origin.clone())?;10181019			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1020			let collection =1021				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1022			collection.check_is_external()?;10231024			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10251026			Self::deposit_event(Event::ResourceRemoval {1027				nft_id,1028				resource_id,1029			});1030			Ok(())1031		}1032	}1033}10341035impl<T: Config> Pallet<T> {1036	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1037		let key = rmrk_key.to_key::<T>()?;10381039		let scoped_key = PropertyScope::Rmrk1040			.apply(key)1041			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10421043		Ok(scoped_key)1044	}10451046	// todo think about renaming these1047	pub fn rmrk_property<E: Encode>(1048		rmrk_key: RmrkProperty,1049		value: &E,1050	) -> Result<Property, DispatchError> {1051		let key = rmrk_key.to_key::<T>()?;10521053		let value = value1054			.encode()1055			.try_into()1056			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10571058		let property = Property { key, value };10591060		Ok(property)1061	}10621063	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1064		vec.decode()1065			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1066	}10671068	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1069	where1070		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1071	{1072		vec.rebind()1073			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1074	}10751076	fn init_collection(1077		sender: T::CrossAccountId,1078		data: CreateCollectionData<T::AccountId>,1079		properties: impl Iterator<Item = Property>,1080	) -> Result<CollectionId, DispatchError> {1081		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10821083		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1084			return Err(<Error<T>>::NoAvailableCollectionId.into());1085		}10861087		<PalletCommon<T>>::set_scoped_collection_properties(1088			collection_id?,1089			PropertyScope::Rmrk,1090			properties,1091		)?;10921093		collection_id1094	}10951096	pub fn create_nft(1097		sender: &T::CrossAccountId,1098		owner: &T::CrossAccountId,1099		collection: &NonfungibleHandle<T>,1100		properties: impl Iterator<Item = Property>,1101	) -> Result<TokenId, DispatchError> {1102		let data = CreateNftExData {1103			properties: BoundedVec::default(),1104			owner: owner.clone(),1105		};11061107		let budget = budget::Value::new(NESTING_BUDGET);11081109		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11101111		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11121113		<PalletNft<T>>::set_scoped_token_properties(1114			collection.id,1115			nft_id,1116			PropertyScope::Rmrk,1117			properties,1118		)?;11191120		Ok(nft_id)1121	}11221123	fn destroy_nft(1124		sender: T::CrossAccountId,1125		collection_id: CollectionId,1126		token_id: TokenId,1127	) -> DispatchResult {1128		let collection =1129			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11301131		let token_data =1132			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11331134		let from = token_data.owner;11351136		let budget = budget::Value::new(NESTING_BUDGET);11371138		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1139	}11401141	fn resource_add(1142		sender: T::AccountId,1143		collection_id: CollectionId,1144		token_id: TokenId,1145		resource_properties: impl Iterator<Item = Property>,1146	) -> Result<RmrkResourceId, DispatchError> {1147		let collection =1148			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1149		ensure!(collection.owner == sender, Error::<T>::NoPermission);11501151		let sender = T::CrossAccountId::from_sub(sender);1152		let budget = budget::Value::new(NESTING_BUDGET);11531154		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1155			.map_err(Self::map_unique_err_to_proxy)?;11561157		let pending = sender != nft_owner;11581159		let resource_collection_id: CollectionId =1160			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1161		let resource_collection =1162			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11631164		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11651166		let resource_id = Self::create_nft(1167			&sender,1168			&nft_owner,1169			&resource_collection,1170			resource_properties.chain(1171				[1172					Self::rmrk_property(PendingResourceAccept, &pending)?,1173					Self::rmrk_property(PendingResourceRemoval, &false)?,1174				]1175				.into_iter(),1176			),1177		)1178		.map_err(|err| match err {1179			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1180			err => Self::map_unique_err_to_proxy(err),1181		})?;11821183		Ok(resource_id.0)1184	}11851186	fn resource_remove(1187		sender: T::AccountId,1188		collection_id: CollectionId,1189		nft_id: TokenId,1190		resource_id: TokenId,1191	) -> DispatchResult {1192		let collection =1193			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1194		ensure!(collection.owner == sender, Error::<T>::NoPermission);11951196		let resource_collection_id: CollectionId =1197			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1198		let resource_collection =1199			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1200		ensure!(1201			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1202			Error::<T>::ResourceDoesntExist1203		);12041205		let budget = up_data_structs::budget::Value::new(10);1206		let topmost_owner =1207			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12081209		let sender = T::CrossAccountId::from_sub(sender);1210		if topmost_owner == sender {1211			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1212				.map_err(Self::map_unique_err_to_proxy)?;1213		} else {1214			<PalletNft<T>>::set_scoped_token_property(1215				resource_collection_id,1216				resource_id,1217				PropertyScope::Rmrk,1218				Self::rmrk_property(PendingResourceRemoval, &true)?,1219			)?;1220		}12211222		Ok(())1223	}12241225	fn change_collection_owner(1226		collection_id: CollectionId,1227		collection_type: misc::CollectionType,1228		sender: T::AccountId,1229		new_owner: T::AccountId,1230	) -> DispatchResult {1231		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1232		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12331234		let mut collection = collection.into_inner();12351236		collection.owner = new_owner;1237		collection.save()1238	}12391240	fn check_collection_owner(1241		collection: &NonfungibleHandle<T>,1242		account: &T::CrossAccountId,1243	) -> DispatchResult {1244		collection1245			.check_is_owner(account)1246			.map_err(Self::map_unique_err_to_proxy)1247	}12481249	pub fn last_collection_idx() -> RmrkCollectionId {1250		<CollectionIndex<T>>::get()1251	}12521253	pub fn unique_collection_id(1254		rmrk_collection_id: RmrkCollectionId,1255	) -> Result<CollectionId, DispatchError> {1256		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1257			.map_err(|_| <Error<T>>::CollectionUnknown.into())1258	}12591260	pub fn rmrk_collection_id(1261		unique_collection_id: CollectionId,1262	) -> Result<RmrkCollectionId, DispatchError> {1263		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1264			.map_err(|_| <Error<T>>::CollectionUnknown.into())1265	}12661267	pub fn get_nft_collection(1268		collection_id: CollectionId,1269	) -> Result<NonfungibleHandle<T>, DispatchError> {1270		let collection = <CollectionHandle<T>>::try_get(collection_id)1271			.map_err(|_| <Error<T>>::CollectionUnknown)?;12721273		match collection.mode {1274			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1275			_ => Err(<Error<T>>::CollectionUnknown.into()),1276		}1277	}12781279	pub fn collection_exists(collection_id: CollectionId) -> bool {1280		<CollectionHandle<T>>::try_get(collection_id).is_ok()1281	}12821283	pub fn get_collection_property(1284		collection_id: CollectionId,1285		key: RmrkProperty,1286	) -> Result<PropertyValue, DispatchError> {1287		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1288			.get(&Self::rmrk_property_key(key)?)1289			.ok_or(<Error<T>>::CollectionUnknown)?1290			.clone();12911292		Ok(collection_property)1293	}12941295	pub fn get_collection_property_decoded<V: Decode>(1296		collection_id: CollectionId,1297		key: RmrkProperty,1298	) -> Result<V, DispatchError> {1299		Self::decode_property(Self::get_collection_property(collection_id, key)?)1300	}13011302	pub fn get_collection_type(1303		collection_id: CollectionId,1304	) -> Result<misc::CollectionType, DispatchError> {1305		Self::get_collection_property_decoded(collection_id, CollectionType)1306			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1307	}13081309	pub fn ensure_collection_type(1310		collection_id: CollectionId,1311		collection_type: misc::CollectionType,1312	) -> DispatchResult {1313		let actual_type = Self::get_collection_type(collection_id)?;1314		ensure!(1315			actual_type == collection_type,1316			<CommonError<T>>::NoPermission1317		);13181319		Ok(())1320	}13211322	pub fn get_typed_nft_collection(1323		collection_id: CollectionId,1324		collection_type: misc::CollectionType,1325	) -> Result<NonfungibleHandle<T>, DispatchError> {1326		Self::ensure_collection_type(collection_id, collection_type)?;13271328		Self::get_nft_collection(collection_id)1329	}13301331	pub fn get_typed_nft_collection_mapped(1332		rmrk_collection_id: RmrkCollectionId,1333		collection_type: misc::CollectionType,1334	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1335		let unique_collection_id = match collection_type {1336			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1337			_ => rmrk_collection_id.into(),1338		};13391340		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13411342		Ok((collection, unique_collection_id))1343	}13441345	pub fn get_nft_property(1346		collection_id: CollectionId,1347		nft_id: TokenId,1348		key: RmrkProperty,1349	) -> Result<PropertyValue, DispatchError> {1350		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1351			.get(&Self::rmrk_property_key(key)?)1352			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1353			.clone();13541355		Ok(nft_property)1356	}13571358	pub fn get_nft_property_decoded<V: Decode>(1359		collection_id: CollectionId,1360		nft_id: TokenId,1361		key: RmrkProperty,1362	) -> Result<V, DispatchError> {1363		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1364	}13651366	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1367		<TokenData<T>>::contains_key((collection_id, nft_id))1368	}13691370	pub fn get_nft_type(1371		collection_id: CollectionId,1372		token_id: TokenId,1373	) -> Result<NftType, DispatchError> {1374		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1375			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1376	}13771378	pub fn ensure_nft_type(1379		collection_id: CollectionId,1380		token_id: TokenId,1381		nft_type: NftType,1382	) -> DispatchResult {1383		let actual_type = Self::get_nft_type(collection_id, token_id)?;1384		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13851386		Ok(())1387	}13881389	pub fn ensure_nft_owner(1390		collection_id: CollectionId,1391		token_id: TokenId,1392		possible_owner: &T::CrossAccountId,1393		nesting_budget: &dyn budget::Budget,1394	) -> DispatchResult {1395		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1396			possible_owner.clone(),1397			collection_id,1398			token_id,1399			None,1400			nesting_budget,1401		)1402		.map_err(Self::map_unique_err_to_proxy)?;14031404		ensure!(is_owned, <Error<T>>::NoPermission);14051406		Ok(())1407	}14081409	pub fn filter_user_properties<Key, Value, R, Mapper>(1410		collection_id: CollectionId,1411		token_id: Option<TokenId>,1412		filter_keys: Option<Vec<RmrkPropertyKey>>,1413		mapper: Mapper,1414	) -> Result<Vec<R>, DispatchError>1415	where1416		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1417		Value: Decode + Default,1418		Mapper: Fn(Key, Value) -> R,1419	{1420		filter_keys1421			.map(|keys| {1422				let properties = keys1423					.into_iter()1424					.filter_map(|key| {1425						let key: Key = key.try_into().ok()?;14261427						let value = match token_id {1428							Some(token_id) => Self::get_nft_property_decoded(1429								collection_id,1430								token_id,1431								UserProperty(key.as_ref()),1432							),1433							None => Self::get_collection_property_decoded(1434								collection_id,1435								UserProperty(key.as_ref()),1436							),1437						}1438						.ok()?;14391440						Some(mapper(key, value))1441					})1442					.collect();14431444				Ok(properties)1445			})1446			.unwrap_or_else(|| {1447				let properties =1448					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14491450				Ok(properties)1451			})1452	}14531454	pub fn iterate_user_properties<Key, Value, R, Mapper>(1455		collection_id: CollectionId,1456		token_id: Option<TokenId>,1457		mapper: Mapper,1458	) -> Result<impl Iterator<Item = R>, DispatchError>1459	where1460		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1461		Value: Decode + Default,1462		Mapper: Fn(Key, Value) -> R,1463	{1464		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14651466		let properties = match token_id {1467			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1468			None => <PalletCommon<T>>::collection_properties(collection_id),1469		};14701471		let properties = properties.into_iter().filter_map(move |(key, value)| {1472			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14731474			let key: Key = key.to_vec().try_into().ok()?;1475			let value: Value = value.decode().ok()?;14761477			Some(mapper(key, value))1478		});14791480		Ok(properties)1481	}14821483	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1484		map_unique_err_to_proxy! {1485			match err {1486				CommonError::NoPermission => NoPermission,1487				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1488				CommonError::PublicMintingNotAllowed => NoPermission,1489				CommonError::TokenNotFound => NoAvailableNftId,1490				CommonError::ApprovedValueTooLow => NoPermission,1491				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1492				StructureError::TokenNotFound => NoAvailableNftId,1493				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1494			}1495		}1496	}1497}