git.delta.rocks / unique-network / refs/commits / 866e42a32607

difftreelog

fix(rmrk) map CollectionNotEmpty error

Daniel Shiposha2022-06-05parent: #800b656.patch.diff
in: master

2 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use 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::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136	}137138	#[pallet::call]139	impl<T: Config> Pallet<T> {140		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]141		#[transactional]142		pub fn create_collection(143			origin: OriginFor<T>,144			metadata: RmrkString,145			max: Option<u32>,146			symbol: RmrkCollectionSymbol,147		) -> DispatchResult {148			let sender = ensure_signed(origin)?;149150			let limits = CollectionLimits {151				owner_can_transfer: Some(false),152				token_limit: max,153				..Default::default()154			};155156			let data = CreateCollectionData {157				limits: Some(limits),158				token_prefix: symbol159					.into_inner()160					.try_into()161					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,162				permissions: Some(CollectionPermissions {163					nesting: Some(NestingRule::Owner),164					..Default::default()165				}),166				..Default::default()167			};168169			let unique_collection_id = Self::init_collection(170				T::CrossAccountId::from_sub(sender.clone()),171				data,172				[173					Self::rmrk_property(Metadata, &metadata)?,174					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,175				]176				.into_iter(),177			)?;178			let rmrk_collection_id = <CollectionIndex<T>>::get();179180			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);181			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);182183			<CollectionIndex<T>>::mutate(|n| *n += 1);184185			Self::deposit_event(Event::CollectionCreated {186				issuer: sender,187				collection_id: rmrk_collection_id,188			});189190			Ok(())191		}192193		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]194		#[transactional]195		pub fn destroy_collection(196			origin: OriginFor<T>,197			collection_id: RmrkCollectionId,198		) -> DispatchResult {199			let sender = ensure_signed(origin)?;200			let cross_sender = T::CrossAccountId::from_sub(sender.clone());201202			let collection = Self::get_typed_nft_collection(203				Self::unique_collection_id(collection_id)?,204				misc::CollectionType::Regular,205			)?;206207			ensure!(208				collection.total_supply() == 0,209				<Error<T>>::CollectionNotEmpty210			);211212			<PalletNft<T>>::destroy_collection(collection, &cross_sender)213				.map_err(Self::map_unique_err_to_proxy)?;214215			Self::deposit_event(Event::CollectionDestroyed {216				issuer: sender,217				collection_id,218			});219220			Ok(())221		}222223		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]224		#[transactional]225		pub fn change_collection_issuer(226			origin: OriginFor<T>,227			collection_id: RmrkCollectionId,228			new_issuer: <T::Lookup as StaticLookup>::Source,229		) -> DispatchResult {230			let sender = ensure_signed(origin)?;231232			let new_issuer = T::Lookup::lookup(new_issuer)?;233234			Self::change_collection_owner(235				Self::unique_collection_id(collection_id)?,236				misc::CollectionType::Regular,237				sender.clone(),238				new_issuer.clone(),239			)?;240241			Self::deposit_event(Event::IssuerChanged {242				old_issuer: sender,243				new_issuer,244				collection_id,245			});246247			Ok(())248		}249250		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251		#[transactional]252		pub fn lock_collection(253			origin: OriginFor<T>,254			collection_id: RmrkCollectionId,255		) -> DispatchResult {256			let sender = ensure_signed(origin)?;257			let cross_sender = T::CrossAccountId::from_sub(sender.clone());258259			let collection = Self::get_typed_nft_collection(260				Self::unique_collection_id(collection_id)?,261				misc::CollectionType::Regular,262			)?;263264			Self::check_collection_owner(&collection, &cross_sender)?;265266			let token_count = collection.total_supply();267268			let mut collection = collection.into_inner();269			collection.limits.token_limit = Some(token_count);270			collection.save()?;271272			Self::deposit_event(Event::CollectionLocked {273				issuer: sender,274				collection_id,275			});276277			Ok(())278		}279280		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]281		#[transactional]282		pub fn mint_nft(283			origin: OriginFor<T>,284			owner: T::AccountId,285			collection_id: RmrkCollectionId,286			recipient: Option<T::AccountId>,287			royalty_amount: Option<Permill>,288			metadata: RmrkString,289			transferable: bool,290		) -> DispatchResult {291			let sender = ensure_signed(origin)?;292			let sender = T::CrossAccountId::from_sub(sender);293			let cross_owner = T::CrossAccountId::from_sub(owner.clone());294295			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {296				recipient: recipient.unwrap_or_else(|| owner.clone()),297				amount,298			});299300			let collection = Self::get_typed_nft_collection(301				Self::unique_collection_id(collection_id)?,302				misc::CollectionType::Regular,303			)?;304305			let nft_id = Self::create_nft(306				&sender,307				&cross_owner,308				&collection,309				[310					Self::rmrk_property(TokenType, &NftType::Regular)?,311					Self::rmrk_property(Transferable, &transferable)?,312					Self::rmrk_property(PendingNftAccept, &false)?,313					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,314					Self::rmrk_property(Metadata, &metadata)?,315					Self::rmrk_property(Equipped, &false)?,316					Self::rmrk_property(317						ResourceCollection,318						&Self::init_collection(319							sender.clone(),320							CreateCollectionData {321								..Default::default()322							},323							[Self::rmrk_property(324								CollectionType,325								&misc::CollectionType::Resource,326							)?]327							.into_iter(),328						)?,329					)?, // todo possibly add limits to the collection if rmrk warrants them330					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,331				]332				.into_iter(),333			)334			.map_err(|err| match err {335				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),336				err => Self::map_unique_err_to_proxy(err),337			})?;338339			Self::deposit_event(Event::NftMinted {340				owner,341				collection_id,342				nft_id: nft_id.0,343			});344345			Ok(())346		}347348		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]349		#[transactional]350		pub fn burn_nft(351			origin: OriginFor<T>,352			collection_id: RmrkCollectionId,353			nft_id: RmrkNftId,354		) -> DispatchResult {355			let sender = ensure_signed(origin)?;356			let cross_sender = T::CrossAccountId::from_sub(sender.clone());357358			Self::destroy_nft(359				cross_sender,360				Self::unique_collection_id(collection_id)?,361				misc::CollectionType::Regular,362				nft_id.into(),363			)?;364365			Self::deposit_event(Event::NFTBurned {366				owner: sender,367				nft_id,368			});369370			Ok(())371		}372373		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]374		#[transactional]375		pub fn send(376			origin: OriginFor<T>,377			rmrk_collection_id: RmrkCollectionId,378			rmrk_nft_id: RmrkNftId,379			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,380		) -> DispatchResult {381			let sender = ensure_signed(origin.clone())?;382			let cross_sender = T::CrossAccountId::from_sub(sender);383384			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;385			let nft_id = rmrk_nft_id.into();386387			let token_data = <TokenData<T>>::get((collection_id, nft_id))388				.ok_or(<Error<T>>::NoAvailableNftId)?;389390			let from = token_data.owner;391392			let collection = Self::get_typed_nft_collection(393				collection_id,394				misc::CollectionType::Regular,395			)?;396397			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {398				return Err(<Error<T>>::NonTransferable.into());399			}400401			if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {402				return Err(<Error<T>>::NoPermission.into());403			}404405			let target_owner;406407			match new_owner {408				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {409					target_owner = T::CrossAccountId::from_sub(account_id);410				},411				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {412					let target_collection_id = Self::unique_collection_id(target_collection_id)?;413414					let target_nft_budget = budget::Value::new(NESTING_BUDGET);415416					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(417						target_collection_id,418						target_nft_id.into(),419						Some((collection_id, nft_id)),420						&target_nft_budget,421					).map_err(Self::map_unique_err_to_proxy)?;422423					let is_approval_required = cross_sender != target_nft_owner;424425					if is_approval_required {426						target_owner = target_nft_owner;427428						<PalletNft<T>>::set_scoped_token_property(429							collection.id,430							nft_id,431							PropertyScope::Rmrk,432							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,433						)?;434					} else {435						target_owner = T::CrossTokenAddressMapping::token_to_address(436							target_collection_id,437							target_nft_id.into(),438						);439					}440				}441			}442443			let src_nft_budget = budget::Value::new(NESTING_BUDGET);444445			<PalletNft<T>>::transfer_from(446				&collection,447				&cross_sender,448				&from,449				&target_owner,450				nft_id,451				&src_nft_budget452			).map_err(Self::map_unique_err_to_proxy)?;453454			Ok(())455		}456457		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]458		#[transactional]459		pub fn accept_nft(460			origin: OriginFor<T>,461			rmrk_collection_id: RmrkCollectionId,462			rmrk_nft_id: RmrkNftId,463			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,464		) -> DispatchResult {465			let sender = ensure_signed(origin.clone())?;466			let cross_sender = T::CrossAccountId::from_sub(sender);467468			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469			let nft_id = rmrk_nft_id.into();470471			let collection = Self::get_typed_nft_collection(472				collection_id,473				misc::CollectionType::Regular,474			)?;475476			let new_owner = match new_owner {477				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {478					T::CrossAccountId::from_sub(account_id)479				},480				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {481					let target_collection_id = Self::unique_collection_id(target_collection_id)?;482483					T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))484				}485			};486487			let budget = budget::Value::new(NESTING_BUDGET);488489			<PalletNft<T>>::transfer(490				&collection,491				&cross_sender,492				&new_owner,493				nft_id,494				&budget,495			).map_err(|err| {496				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {497					<Error<T>>::CannotAcceptNonOwnedNft.into()498				} else {499					Self::map_unique_err_to_proxy(err)500				}501			})?;502503			<PalletNft<T>>::set_scoped_token_property(504				collection.id,505				nft_id,506				PropertyScope::Rmrk,507				Self::rmrk_property(PendingNftAccept, &false)?,508			)?;509510			Ok(())511		}512513		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]514		#[transactional]515		pub fn set_property(516			origin: OriginFor<T>,517			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,518			maybe_nft_id: Option<RmrkNftId>,519			key: RmrkKeyString,520			value: RmrkValueString,521		) -> DispatchResult {522			let sender = ensure_signed(origin)?;523			let sender = T::CrossAccountId::from_sub(sender);524525			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;526			let budget = budget::Value::new(NESTING_BUDGET);527528			match maybe_nft_id {529				Some(nft_id) => {530					let token_id: TokenId = nft_id.into();531532					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;533					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;534535					<PalletNft<T>>::set_scoped_token_property(536						collection_id,537						token_id,538						PropertyScope::Rmrk,539						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,540					)?;541				}542				None => {543					let collection = Self::get_typed_nft_collection(544						collection_id,545						misc::CollectionType::Regular,546					)?;547548					Self::check_collection_owner(&collection, &sender)?;549550					<PalletCommon<T>>::set_scoped_collection_property(551						collection_id,552						PropertyScope::Rmrk,553						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,554					)?;555				}556			}557558			Self::deposit_event(Event::PropertySet {559				collection_id: rmrk_collection_id,560				maybe_nft_id,561				key,562				value,563			});564565			Ok(())566		}567568		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]569		#[transactional]570		pub fn add_basic_resource(571			origin: OriginFor<T>,572			collection_id: RmrkCollectionId,573			nft_id: RmrkNftId,574			resource: RmrkBasicResource,575		) -> DispatchResult {576			let sender = ensure_signed(origin.clone())?;577578			let resource_id = Self::resource_add(579				sender,580				Self::unique_collection_id(collection_id)?,581				nft_id.into(),582				[583					Self::rmrk_property(TokenType, &NftType::Resource)?,584					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,585					Self::rmrk_property(Src, &resource.src)?,586					Self::rmrk_property(Metadata, &resource.metadata)?,587					Self::rmrk_property(License, &resource.license)?,588					Self::rmrk_property(Thumb, &resource.thumb)?,589				]590				.into_iter(),591			)?;592593			Self::deposit_event(Event::ResourceAdded {594				nft_id,595				resource_id,596			});597			Ok(())598		}599600		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]601		#[transactional]602		pub fn add_composable_resource(603			origin: OriginFor<T>,604			collection_id: RmrkCollectionId,605			nft_id: RmrkNftId,606			_resource_id: RmrkBoundedResource,607			resource: RmrkComposableResource,608		) -> DispatchResult {609			let sender = ensure_signed(origin.clone())?;610611			let resource_id = Self::resource_add(612				sender,613				Self::unique_collection_id(collection_id)?,614				nft_id.into(),615				[616					Self::rmrk_property(TokenType, &NftType::Resource)?,617					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,618					Self::rmrk_property(Parts, &resource.parts)?,619					Self::rmrk_property(Base, &resource.base)?,620					Self::rmrk_property(Src, &resource.src)?,621					Self::rmrk_property(Metadata, &resource.metadata)?,622					Self::rmrk_property(License, &resource.license)?,623					Self::rmrk_property(Thumb, &resource.thumb)?,624				]625				.into_iter(),626			)?;627628			Self::deposit_event(Event::ResourceAdded {629				nft_id,630				resource_id,631			});632			Ok(())633		}634635		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]636		#[transactional]637		pub fn add_slot_resource(638			origin: OriginFor<T>,639			collection_id: RmrkCollectionId,640			nft_id: RmrkNftId,641			resource: RmrkSlotResource,642		) -> DispatchResult {643			let sender = ensure_signed(origin.clone())?;644645			let resource_id = Self::resource_add(646				sender,647				Self::unique_collection_id(collection_id)?,648				nft_id.into(),649				[650					Self::rmrk_property(TokenType, &NftType::Resource)?,651					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,652					Self::rmrk_property(Base, &resource.base)?,653					Self::rmrk_property(Src, &resource.src)?,654					Self::rmrk_property(Metadata, &resource.metadata)?,655					Self::rmrk_property(Slot, &resource.slot)?,656					Self::rmrk_property(License, &resource.license)?,657					Self::rmrk_property(Thumb, &resource.thumb)?,658				]659				.into_iter(),660			)?;661662			Self::deposit_event(Event::ResourceAdded {663				nft_id,664				resource_id,665			});666			Ok(())667		}668669		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]670		#[transactional]671		pub fn remove_resource(672			origin: OriginFor<T>,673			collection_id: RmrkCollectionId,674			nft_id: RmrkNftId,675			resource_id: RmrkResourceId,676		) -> DispatchResult {677			let sender = ensure_signed(origin.clone())?;678679			Self::resource_remove(680				sender,681				Self::unique_collection_id(collection_id)?,682				nft_id.into(),683				resource_id.into(),684			)?;685686			Self::deposit_event(Event::ResourceRemoval {687				nft_id,688				resource_id,689			});690			Ok(())691		}692	}693}694695impl<T: Config> Pallet<T> {696	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {697		let key = rmrk_key.to_key::<T>()?;698699		let scoped_key = PropertyScope::Rmrk700			.apply(key)701			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;702703		Ok(scoped_key)704	}705706	// todo think about renaming these707	pub fn rmrk_property<E: Encode>(708		rmrk_key: RmrkProperty,709		value: &E,710	) -> Result<Property, DispatchError> {711		let key = rmrk_key.to_key::<T>()?;712713		let value = value714			.encode()715			.try_into()716			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;717718		let property = Property { key, value };719720		Ok(property)721	}722723	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {724		vec.decode()725			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())726	}727728	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>729	where730		BoundedVec<u8, S>: TryFrom<Vec<u8>>,731	{732		vec.rebind()733			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())734	}735736	fn init_collection(737		sender: T::CrossAccountId,738		data: CreateCollectionData<T::AccountId>,739		properties: impl Iterator<Item = Property>,740	) -> Result<CollectionId, DispatchError> {741		let collection_id = <PalletNft<T>>::init_collection(sender, data);742743		if let Err(DispatchError::Arithmetic(_)) = &collection_id {744			return Err(<Error<T>>::NoAvailableCollectionId.into());745		}746747		<PalletCommon<T>>::set_scoped_collection_properties(748			collection_id?,749			PropertyScope::Rmrk,750			properties,751		)?;752753		collection_id754	}755756	pub fn create_nft(757		sender: &T::CrossAccountId,758		owner: &T::CrossAccountId,759		collection: &NonfungibleHandle<T>,760		properties: impl Iterator<Item = Property>,761	) -> Result<TokenId, DispatchError> {762		let data = CreateNftExData {763			properties: BoundedVec::default(),764			owner: owner.clone(),765		};766767		let budget = budget::Value::new(NESTING_BUDGET);768769		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;770771		let nft_id = <PalletNft<T>>::current_token_id(collection.id);772773		<PalletNft<T>>::set_scoped_token_properties(774			collection.id,775			nft_id,776			PropertyScope::Rmrk,777			properties,778		)?;779780		Ok(nft_id)781	}782783	fn destroy_nft(784		sender: T::CrossAccountId,785		collection_id: CollectionId,786		collection_type: misc::CollectionType,787		token_id: TokenId,788	) -> DispatchResult {789		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;790791		<PalletNft<T>>::burn(&collection, &sender, token_id)792			.map_err(Self::map_unique_err_to_proxy)?;793794		Ok(())795	}796797	fn resource_add(798		sender: T::AccountId,799		collection_id: CollectionId,800		token_id: TokenId,801		resource_properties: impl Iterator<Item = Property>,802	) -> Result<RmrkResourceId, DispatchError> {803		let collection =804			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;805		ensure!(collection.owner == sender, Error::<T>::NoPermission);806807		// Check NFT lock status // todo depends on market, maybe later808		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);809810		let sender = T::CrossAccountId::from_sub(sender);811		let budget = budget::Value::new(NESTING_BUDGET);812		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();813814		let resource_collection_id: CollectionId =815			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;816		let resource_collection =817			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;818819		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them820821		let resource_id = Self::create_nft(822			&sender, // todo owner of the nft?823			&sender,824			&resource_collection,825			resource_properties.chain(826				[827					Self::rmrk_property(PendingResourceAccept, &pending)?,828					Self::rmrk_property(PendingResourceRemoval, &false)?,829				]830				.into_iter(),831			),832		)833		.map_err(|err| match err {834			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),835			err => Self::map_unique_err_to_proxy(err),836		})?;837838		Ok(resource_id.0)839	}840841	fn resource_remove(842		sender: T::AccountId,843		collection_id: CollectionId,844		nft_id: TokenId,845		resource_id: TokenId,846	) -> DispatchResult {847		let collection =848			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;849		ensure!(collection.owner == sender, Error::<T>::NoPermission);850851		let resource_collection_id: CollectionId =852			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;853		let resource_collection =854			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;855		ensure!(856			<PalletNft<T>>::token_exists(&resource_collection, resource_id),857			Error::<T>::ResourceDoesntExist858		);859860		let budget = up_data_structs::budget::Value::new(10);861		let topmost_owner =862			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;863864		let sender = T::CrossAccountId::from_sub(sender);865		if topmost_owner == sender {866			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)867				.map_err(Self::map_unique_err_to_proxy)?;868		} else {869			<PalletNft<T>>::set_scoped_token_property(870				resource_collection_id,871				resource_id,872				PropertyScope::Rmrk,873				Self::rmrk_property(PendingResourceRemoval, &true)?,874			)?;875		}876877		Ok(())878	}879880	fn change_collection_owner(881		collection_id: CollectionId,882		collection_type: misc::CollectionType,883		sender: T::AccountId,884		new_owner: T::AccountId,885	) -> DispatchResult {886		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;887		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;888889		let mut collection = collection.into_inner();890891		collection.owner = new_owner;892		collection.save()893	}894895	fn check_collection_owner(896		collection: &NonfungibleHandle<T>,897		account: &T::CrossAccountId,898	) -> DispatchResult {899		collection900			.check_is_owner(account)901			.map_err(Self::map_unique_err_to_proxy)902	}903904	pub fn last_collection_idx() -> RmrkCollectionId {905		<CollectionIndex<T>>::get()906	}907908	pub fn unique_collection_id(909		rmrk_collection_id: RmrkCollectionId,910	) -> Result<CollectionId, DispatchError> {911		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)912			.map_err(|_| <Error<T>>::CollectionUnknown.into())913	}914915	pub fn rmrk_collection_id(916		unique_collection_id: CollectionId917	) -> Result<RmrkCollectionId, DispatchError> {918		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)919			.map_err(|_| <Error<T>>::CollectionUnknown.into())920	}921922	pub fn get_nft_collection(923		collection_id: CollectionId,924	) -> Result<NonfungibleHandle<T>, DispatchError> {925		let collection = <CollectionHandle<T>>::try_get(collection_id)926			.map_err(|_| <Error<T>>::CollectionUnknown)?;927928		match collection.mode {929			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),930			_ => Err(<Error<T>>::CollectionUnknown.into()),931		}932	}933934	pub fn collection_exists(collection_id: CollectionId) -> bool {935		<CollectionHandle<T>>::try_get(collection_id).is_ok()936	}937938	pub fn get_collection_property(939		collection_id: CollectionId,940		key: RmrkProperty,941	) -> Result<PropertyValue, DispatchError> {942		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)943			.get(&Self::rmrk_property_key(key)?)944			.ok_or(<Error<T>>::CollectionUnknown)?945			.clone();946947		Ok(collection_property)948	}949950	pub fn get_collection_property_decoded<V: Decode>(951		collection_id: CollectionId,952		key: RmrkProperty,953	) -> Result<V, DispatchError> {954		Self::decode_property(Self::get_collection_property(collection_id, key)?)955	}956957	pub fn get_collection_type(958		collection_id: CollectionId,959	) -> Result<misc::CollectionType, DispatchError> {960		Self::get_collection_property_decoded(collection_id, CollectionType)961			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())962	}963964	pub fn ensure_collection_type(965		collection_id: CollectionId,966		collection_type: misc::CollectionType,967	) -> DispatchResult {968		let actual_type = Self::get_collection_type(collection_id)?;969		ensure!(970			actual_type == collection_type,971			<CommonError<T>>::NoPermission972		);973974		Ok(())975	}976977	pub fn get_typed_nft_collection(978		collection_id: CollectionId,979		collection_type: misc::CollectionType,980	) -> Result<NonfungibleHandle<T>, DispatchError> {981		Self::ensure_collection_type(collection_id, collection_type)?;982983		Self::get_nft_collection(collection_id)984	}985986	pub fn get_typed_nft_collection_mapped(987		rmrk_collection_id: RmrkCollectionId,988		collection_type: misc::CollectionType,989	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {990		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;991992		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;993994		Ok((collection, unique_collection_id))995	}996997	pub fn get_nft_property(998		collection_id: CollectionId,999		nft_id: TokenId,1000		key: RmrkProperty,1001	) -> Result<PropertyValue, DispatchError> {1002		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1003			.get(&Self::rmrk_property_key(key)?)1004			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1005			.clone();10061007		Ok(nft_property)1008	}10091010	pub fn get_nft_property_decoded<V: Decode>(1011		collection_id: CollectionId,1012		nft_id: TokenId,1013		key: RmrkProperty,1014	) -> Result<V, DispatchError> {1015		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1016	}10171018	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1019		<TokenData<T>>::contains_key((collection_id, nft_id))1020	}10211022	pub fn get_nft_type(1023		collection_id: CollectionId,1024		token_id: TokenId,1025	) -> Result<NftType, DispatchError> {1026		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1027			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1028	}10291030	pub fn ensure_nft_type(1031		collection_id: CollectionId,1032		token_id: TokenId,1033		nft_type: NftType,1034	) -> DispatchResult {1035		let actual_type = Self::get_nft_type(collection_id, token_id)?;1036		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10371038		Ok(())1039	}10401041	pub fn ensure_nft_owner(1042		collection_id: CollectionId,1043		token_id: TokenId,1044		possible_owner: &T::CrossAccountId,1045		nesting_budget: &dyn budget::Budget1046	) -> DispatchResult {1047		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1048			possible_owner.clone(),1049			collection_id,1050			token_id,1051			None,1052			nesting_budget,1053		)?;10541055		ensure!(1056			is_owned,1057			<Error<T>>::NoPermission1058		);10591060		Ok(())1061	}10621063	pub fn filter_user_properties<Key, Value, R, Mapper>(1064		collection_id: CollectionId,1065		token_id: Option<TokenId>,1066		filter_keys: Option<Vec<RmrkPropertyKey>>,1067		mapper: Mapper,1068	) -> Result<Vec<R>, DispatchError>1069	where1070		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1071		Value: Decode + Default,1072		Mapper: Fn(Key, Value) -> R,1073	{1074		filter_keys1075			.map(|keys| {1076				let properties = keys1077					.into_iter()1078					.filter_map(|key| {1079						let key: Key = key.try_into().ok()?;10801081						let value = match token_id {1082							Some(token_id) => Self::get_nft_property_decoded(1083								collection_id,1084								token_id,1085								UserProperty(key.as_ref()),1086							),1087							None => Self::get_collection_property_decoded(1088								collection_id,1089								UserProperty(key.as_ref()),1090							),1091						}1092						.ok()?;10931094						Some(mapper(key, value))1095					})1096					.collect();10971098				Ok(properties)1099			})1100			.unwrap_or_else(|| {1101				let properties =1102					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11031104				Ok(properties)1105			})1106	}11071108	pub fn iterate_user_properties<Key, Value, R, Mapper>(1109		collection_id: CollectionId,1110		token_id: Option<TokenId>,1111		mapper: Mapper,1112	) -> Result<impl Iterator<Item = R>, DispatchError>1113	where1114		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1115		Value: Decode + Default,1116		Mapper: Fn(Key, Value) -> R,1117	{1118		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11191120		let properties = match token_id {1121			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1122			None => <PalletCommon<T>>::collection_properties(collection_id),1123		};11241125		let properties = properties.into_iter().filter_map(move |(key, value)| {1126			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11271128			let key: Key = key.to_vec().try_into().ok()?;1129			let value: Value = value.decode().ok()?;11301131			Some(mapper(key, value))1132		});11331134		Ok(properties)1135	}11361137	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1138		map_unique_err_to_proxy! {1139			match err {1140				CommonError::NoPermission => NoPermission,1141				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1142				CommonError::PublicMintingNotAllowed => NoPermission,1143				CommonError::TokenNotFound => NoAvailableNftId,1144				CommonError::ApprovedValueTooLow => NoPermission,1145				StructureError::TokenNotFound => NoAvailableNftId,1146				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1147			}1148		}1149	}1150}
after · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use 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::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136	}137138	#[pallet::call]139	impl<T: Config> Pallet<T> {140		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]141		#[transactional]142		pub fn create_collection(143			origin: OriginFor<T>,144			metadata: RmrkString,145			max: Option<u32>,146			symbol: RmrkCollectionSymbol,147		) -> DispatchResult {148			let sender = ensure_signed(origin)?;149150			let limits = CollectionLimits {151				owner_can_transfer: Some(false),152				token_limit: max,153				..Default::default()154			};155156			let data = CreateCollectionData {157				limits: Some(limits),158				token_prefix: symbol159					.into_inner()160					.try_into()161					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,162				permissions: Some(CollectionPermissions {163					nesting: Some(NestingRule::Owner),164					..Default::default()165				}),166				..Default::default()167			};168169			let unique_collection_id = Self::init_collection(170				T::CrossAccountId::from_sub(sender.clone()),171				data,172				[173					Self::rmrk_property(Metadata, &metadata)?,174					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,175				]176				.into_iter(),177			)?;178			let rmrk_collection_id = <CollectionIndex<T>>::get();179180			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);181			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);182183			<CollectionIndex<T>>::mutate(|n| *n += 1);184185			Self::deposit_event(Event::CollectionCreated {186				issuer: sender,187				collection_id: rmrk_collection_id,188			});189190			Ok(())191		}192193		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]194		#[transactional]195		pub fn destroy_collection(196			origin: OriginFor<T>,197			collection_id: RmrkCollectionId,198		) -> DispatchResult {199			let sender = ensure_signed(origin)?;200			let cross_sender = T::CrossAccountId::from_sub(sender.clone());201202			let collection = Self::get_typed_nft_collection(203				Self::unique_collection_id(collection_id)?,204				misc::CollectionType::Regular,205			)?;206207			<PalletNft<T>>::destroy_collection(collection, &cross_sender)208				.map_err(Self::map_unique_err_to_proxy)?;209210			Self::deposit_event(Event::CollectionDestroyed {211				issuer: sender,212				collection_id,213			});214215			Ok(())216		}217218		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]219		#[transactional]220		pub fn change_collection_issuer(221			origin: OriginFor<T>,222			collection_id: RmrkCollectionId,223			new_issuer: <T::Lookup as StaticLookup>::Source,224		) -> DispatchResult {225			let sender = ensure_signed(origin)?;226227			let new_issuer = T::Lookup::lookup(new_issuer)?;228229			Self::change_collection_owner(230				Self::unique_collection_id(collection_id)?,231				misc::CollectionType::Regular,232				sender.clone(),233				new_issuer.clone(),234			)?;235236			Self::deposit_event(Event::IssuerChanged {237				old_issuer: sender,238				new_issuer,239				collection_id,240			});241242			Ok(())243		}244245		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]246		#[transactional]247		pub fn lock_collection(248			origin: OriginFor<T>,249			collection_id: RmrkCollectionId,250		) -> DispatchResult {251			let sender = ensure_signed(origin)?;252			let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254			let collection = Self::get_typed_nft_collection(255				Self::unique_collection_id(collection_id)?,256				misc::CollectionType::Regular,257			)?;258259			Self::check_collection_owner(&collection, &cross_sender)?;260261			let token_count = collection.total_supply();262263			let mut collection = collection.into_inner();264			collection.limits.token_limit = Some(token_count);265			collection.save()?;266267			Self::deposit_event(Event::CollectionLocked {268				issuer: sender,269				collection_id,270			});271272			Ok(())273		}274275		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]276		#[transactional]277		pub fn mint_nft(278			origin: OriginFor<T>,279			owner: T::AccountId,280			collection_id: RmrkCollectionId,281			recipient: Option<T::AccountId>,282			royalty_amount: Option<Permill>,283			metadata: RmrkString,284			transferable: bool,285		) -> DispatchResult {286			let sender = ensure_signed(origin)?;287			let sender = T::CrossAccountId::from_sub(sender);288			let cross_owner = T::CrossAccountId::from_sub(owner.clone());289290			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {291				recipient: recipient.unwrap_or_else(|| owner.clone()),292				amount,293			});294295			let collection = Self::get_typed_nft_collection(296				Self::unique_collection_id(collection_id)?,297				misc::CollectionType::Regular,298			)?;299300			let nft_id = Self::create_nft(301				&sender,302				&cross_owner,303				&collection,304				[305					Self::rmrk_property(TokenType, &NftType::Regular)?,306					Self::rmrk_property(Transferable, &transferable)?,307					Self::rmrk_property(PendingNftAccept, &false)?,308					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,309					Self::rmrk_property(Metadata, &metadata)?,310					Self::rmrk_property(Equipped, &false)?,311					Self::rmrk_property(312						ResourceCollection,313						&Self::init_collection(314							sender.clone(),315							CreateCollectionData {316								..Default::default()317							},318							[Self::rmrk_property(319								CollectionType,320								&misc::CollectionType::Resource,321							)?]322							.into_iter(),323						)?,324					)?, // todo possibly add limits to the collection if rmrk warrants them325					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,326				]327				.into_iter(),328			)329			.map_err(|err| match err {330				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),331				err => Self::map_unique_err_to_proxy(err),332			})?;333334			Self::deposit_event(Event::NftMinted {335				owner,336				collection_id,337				nft_id: nft_id.0,338			});339340			Ok(())341		}342343		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]344		#[transactional]345		pub fn burn_nft(346			origin: OriginFor<T>,347			collection_id: RmrkCollectionId,348			nft_id: RmrkNftId,349		) -> DispatchResult {350			let sender = ensure_signed(origin)?;351			let cross_sender = T::CrossAccountId::from_sub(sender.clone());352353			Self::destroy_nft(354				cross_sender,355				Self::unique_collection_id(collection_id)?,356				misc::CollectionType::Regular,357				nft_id.into(),358			)?;359360			Self::deposit_event(Event::NFTBurned {361				owner: sender,362				nft_id,363			});364365			Ok(())366		}367368		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]369		#[transactional]370		pub fn send(371			origin: OriginFor<T>,372			rmrk_collection_id: RmrkCollectionId,373			rmrk_nft_id: RmrkNftId,374			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,375		) -> DispatchResult {376			let sender = ensure_signed(origin.clone())?;377			let cross_sender = T::CrossAccountId::from_sub(sender);378379			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;380			let nft_id = rmrk_nft_id.into();381382			let token_data = <TokenData<T>>::get((collection_id, nft_id))383				.ok_or(<Error<T>>::NoAvailableNftId)?;384385			let from = token_data.owner;386387			let collection = Self::get_typed_nft_collection(388				collection_id,389				misc::CollectionType::Regular,390			)?;391392			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393				return Err(<Error<T>>::NonTransferable.into());394			}395396			if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {397				return Err(<Error<T>>::NoPermission.into());398			}399400			let target_owner;401402			match new_owner {403				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {404					target_owner = T::CrossAccountId::from_sub(account_id);405				},406				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {407					let target_collection_id = Self::unique_collection_id(target_collection_id)?;408409					let target_nft_budget = budget::Value::new(NESTING_BUDGET);410411					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(412						target_collection_id,413						target_nft_id.into(),414						Some((collection_id, nft_id)),415						&target_nft_budget,416					).map_err(Self::map_unique_err_to_proxy)?;417418					let is_approval_required = cross_sender != target_nft_owner;419420					if is_approval_required {421						target_owner = target_nft_owner;422423						<PalletNft<T>>::set_scoped_token_property(424							collection.id,425							nft_id,426							PropertyScope::Rmrk,427							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,428						)?;429					} else {430						target_owner = T::CrossTokenAddressMapping::token_to_address(431							target_collection_id,432							target_nft_id.into(),433						);434					}435				}436			}437438			let src_nft_budget = budget::Value::new(NESTING_BUDGET);439440			<PalletNft<T>>::transfer_from(441				&collection,442				&cross_sender,443				&from,444				&target_owner,445				nft_id,446				&src_nft_budget447			).map_err(Self::map_unique_err_to_proxy)?;448449			Ok(())450		}451452		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453		#[transactional]454		pub fn accept_nft(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);462463			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464			let nft_id = rmrk_nft_id.into();465466			let collection = Self::get_typed_nft_collection(467				collection_id,468				misc::CollectionType::Regular,469			)?;470471			let new_owner = match new_owner {472				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {473					T::CrossAccountId::from_sub(account_id)474				},475				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {476					let target_collection_id = Self::unique_collection_id(target_collection_id)?;477478					T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))479				}480			};481482			let budget = budget::Value::new(NESTING_BUDGET);483484			<PalletNft<T>>::transfer(485				&collection,486				&cross_sender,487				&new_owner,488				nft_id,489				&budget,490			).map_err(|err| {491				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {492					<Error<T>>::CannotAcceptNonOwnedNft.into()493				} else {494					Self::map_unique_err_to_proxy(err)495				}496			})?;497498			<PalletNft<T>>::set_scoped_token_property(499				collection.id,500				nft_id,501				PropertyScope::Rmrk,502				Self::rmrk_property(PendingNftAccept, &false)?,503			)?;504505			Ok(())506		}507508		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]509		#[transactional]510		pub fn set_property(511			origin: OriginFor<T>,512			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,513			maybe_nft_id: Option<RmrkNftId>,514			key: RmrkKeyString,515			value: RmrkValueString,516		) -> DispatchResult {517			let sender = ensure_signed(origin)?;518			let sender = T::CrossAccountId::from_sub(sender);519520			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;521			let budget = budget::Value::new(NESTING_BUDGET);522523			match maybe_nft_id {524				Some(nft_id) => {525					let token_id: TokenId = nft_id.into();526527					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;528					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;529530					<PalletNft<T>>::set_scoped_token_property(531						collection_id,532						token_id,533						PropertyScope::Rmrk,534						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,535					)?;536				}537				None => {538					let collection = Self::get_typed_nft_collection(539						collection_id,540						misc::CollectionType::Regular,541					)?;542543					Self::check_collection_owner(&collection, &sender)?;544545					<PalletCommon<T>>::set_scoped_collection_property(546						collection_id,547						PropertyScope::Rmrk,548						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,549					)?;550				}551			}552553			Self::deposit_event(Event::PropertySet {554				collection_id: rmrk_collection_id,555				maybe_nft_id,556				key,557				value,558			});559560			Ok(())561		}562563		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]564		#[transactional]565		pub fn add_basic_resource(566			origin: OriginFor<T>,567			collection_id: RmrkCollectionId,568			nft_id: RmrkNftId,569			resource: RmrkBasicResource,570		) -> DispatchResult {571			let sender = ensure_signed(origin.clone())?;572573			let resource_id = Self::resource_add(574				sender,575				Self::unique_collection_id(collection_id)?,576				nft_id.into(),577				[578					Self::rmrk_property(TokenType, &NftType::Resource)?,579					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,580					Self::rmrk_property(Src, &resource.src)?,581					Self::rmrk_property(Metadata, &resource.metadata)?,582					Self::rmrk_property(License, &resource.license)?,583					Self::rmrk_property(Thumb, &resource.thumb)?,584				]585				.into_iter(),586			)?;587588			Self::deposit_event(Event::ResourceAdded {589				nft_id,590				resource_id,591			});592			Ok(())593		}594595		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]596		#[transactional]597		pub fn add_composable_resource(598			origin: OriginFor<T>,599			collection_id: RmrkCollectionId,600			nft_id: RmrkNftId,601			_resource_id: RmrkBoundedResource,602			resource: RmrkComposableResource,603		) -> DispatchResult {604			let sender = ensure_signed(origin.clone())?;605606			let resource_id = Self::resource_add(607				sender,608				Self::unique_collection_id(collection_id)?,609				nft_id.into(),610				[611					Self::rmrk_property(TokenType, &NftType::Resource)?,612					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,613					Self::rmrk_property(Parts, &resource.parts)?,614					Self::rmrk_property(Base, &resource.base)?,615					Self::rmrk_property(Src, &resource.src)?,616					Self::rmrk_property(Metadata, &resource.metadata)?,617					Self::rmrk_property(License, &resource.license)?,618					Self::rmrk_property(Thumb, &resource.thumb)?,619				]620				.into_iter(),621			)?;622623			Self::deposit_event(Event::ResourceAdded {624				nft_id,625				resource_id,626			});627			Ok(())628		}629630		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631		#[transactional]632		pub fn add_slot_resource(633			origin: OriginFor<T>,634			collection_id: RmrkCollectionId,635			nft_id: RmrkNftId,636			resource: RmrkSlotResource,637		) -> DispatchResult {638			let sender = ensure_signed(origin.clone())?;639640			let resource_id = Self::resource_add(641				sender,642				Self::unique_collection_id(collection_id)?,643				nft_id.into(),644				[645					Self::rmrk_property(TokenType, &NftType::Resource)?,646					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,647					Self::rmrk_property(Base, &resource.base)?,648					Self::rmrk_property(Src, &resource.src)?,649					Self::rmrk_property(Metadata, &resource.metadata)?,650					Self::rmrk_property(Slot, &resource.slot)?,651					Self::rmrk_property(License, &resource.license)?,652					Self::rmrk_property(Thumb, &resource.thumb)?,653				]654				.into_iter(),655			)?;656657			Self::deposit_event(Event::ResourceAdded {658				nft_id,659				resource_id,660			});661			Ok(())662		}663664		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]665		#[transactional]666		pub fn remove_resource(667			origin: OriginFor<T>,668			collection_id: RmrkCollectionId,669			nft_id: RmrkNftId,670			resource_id: RmrkResourceId,671		) -> DispatchResult {672			let sender = ensure_signed(origin.clone())?;673674			Self::resource_remove(675				sender,676				Self::unique_collection_id(collection_id)?,677				nft_id.into(),678				resource_id.into(),679			)?;680681			Self::deposit_event(Event::ResourceRemoval {682				nft_id,683				resource_id,684			});685			Ok(())686		}687	}688}689690impl<T: Config> Pallet<T> {691	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {692		let key = rmrk_key.to_key::<T>()?;693694		let scoped_key = PropertyScope::Rmrk695			.apply(key)696			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;697698		Ok(scoped_key)699	}700701	// todo think about renaming these702	pub fn rmrk_property<E: Encode>(703		rmrk_key: RmrkProperty,704		value: &E,705	) -> Result<Property, DispatchError> {706		let key = rmrk_key.to_key::<T>()?;707708		let value = value709			.encode()710			.try_into()711			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;712713		let property = Property { key, value };714715		Ok(property)716	}717718	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {719		vec.decode()720			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())721	}722723	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>724	where725		BoundedVec<u8, S>: TryFrom<Vec<u8>>,726	{727		vec.rebind()728			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())729	}730731	fn init_collection(732		sender: T::CrossAccountId,733		data: CreateCollectionData<T::AccountId>,734		properties: impl Iterator<Item = Property>,735	) -> Result<CollectionId, DispatchError> {736		let collection_id = <PalletNft<T>>::init_collection(sender, data);737738		if let Err(DispatchError::Arithmetic(_)) = &collection_id {739			return Err(<Error<T>>::NoAvailableCollectionId.into());740		}741742		<PalletCommon<T>>::set_scoped_collection_properties(743			collection_id?,744			PropertyScope::Rmrk,745			properties,746		)?;747748		collection_id749	}750751	pub fn create_nft(752		sender: &T::CrossAccountId,753		owner: &T::CrossAccountId,754		collection: &NonfungibleHandle<T>,755		properties: impl Iterator<Item = Property>,756	) -> Result<TokenId, DispatchError> {757		let data = CreateNftExData {758			properties: BoundedVec::default(),759			owner: owner.clone(),760		};761762		let budget = budget::Value::new(NESTING_BUDGET);763764		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;765766		let nft_id = <PalletNft<T>>::current_token_id(collection.id);767768		<PalletNft<T>>::set_scoped_token_properties(769			collection.id,770			nft_id,771			PropertyScope::Rmrk,772			properties,773		)?;774775		Ok(nft_id)776	}777778	fn destroy_nft(779		sender: T::CrossAccountId,780		collection_id: CollectionId,781		collection_type: misc::CollectionType,782		token_id: TokenId,783	) -> DispatchResult {784		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;785786		<PalletNft<T>>::burn(&collection, &sender, token_id)787			.map_err(Self::map_unique_err_to_proxy)?;788789		Ok(())790	}791792	fn resource_add(793		sender: T::AccountId,794		collection_id: CollectionId,795		token_id: TokenId,796		resource_properties: impl Iterator<Item = Property>,797	) -> Result<RmrkResourceId, DispatchError> {798		let collection =799			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;800		ensure!(collection.owner == sender, Error::<T>::NoPermission);801802		// Check NFT lock status // todo depends on market, maybe later803		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);804805		let sender = T::CrossAccountId::from_sub(sender);806		let budget = budget::Value::new(NESTING_BUDGET);807		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();808809		let resource_collection_id: CollectionId =810			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;811		let resource_collection =812			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;813814		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them815816		let resource_id = Self::create_nft(817			&sender, // todo owner of the nft?818			&sender,819			&resource_collection,820			resource_properties.chain(821				[822					Self::rmrk_property(PendingResourceAccept, &pending)?,823					Self::rmrk_property(PendingResourceRemoval, &false)?,824				]825				.into_iter(),826			),827		)828		.map_err(|err| match err {829			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),830			err => Self::map_unique_err_to_proxy(err),831		})?;832833		Ok(resource_id.0)834	}835836	fn resource_remove(837		sender: T::AccountId,838		collection_id: CollectionId,839		nft_id: TokenId,840		resource_id: TokenId,841	) -> DispatchResult {842		let collection =843			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;844		ensure!(collection.owner == sender, Error::<T>::NoPermission);845846		let resource_collection_id: CollectionId =847			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;848		let resource_collection =849			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;850		ensure!(851			<PalletNft<T>>::token_exists(&resource_collection, resource_id),852			Error::<T>::ResourceDoesntExist853		);854855		let budget = up_data_structs::budget::Value::new(10);856		let topmost_owner =857			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;858859		let sender = T::CrossAccountId::from_sub(sender);860		if topmost_owner == sender {861			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)862				.map_err(Self::map_unique_err_to_proxy)?;863		} else {864			<PalletNft<T>>::set_scoped_token_property(865				resource_collection_id,866				resource_id,867				PropertyScope::Rmrk,868				Self::rmrk_property(PendingResourceRemoval, &true)?,869			)?;870		}871872		Ok(())873	}874875	fn change_collection_owner(876		collection_id: CollectionId,877		collection_type: misc::CollectionType,878		sender: T::AccountId,879		new_owner: T::AccountId,880	) -> DispatchResult {881		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;882		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;883884		let mut collection = collection.into_inner();885886		collection.owner = new_owner;887		collection.save()888	}889890	fn check_collection_owner(891		collection: &NonfungibleHandle<T>,892		account: &T::CrossAccountId,893	) -> DispatchResult {894		collection895			.check_is_owner(account)896			.map_err(Self::map_unique_err_to_proxy)897	}898899	pub fn last_collection_idx() -> RmrkCollectionId {900		<CollectionIndex<T>>::get()901	}902903	pub fn unique_collection_id(904		rmrk_collection_id: RmrkCollectionId,905	) -> Result<CollectionId, DispatchError> {906		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)907			.map_err(|_| <Error<T>>::CollectionUnknown.into())908	}909910	pub fn rmrk_collection_id(911		unique_collection_id: CollectionId912	) -> Result<RmrkCollectionId, DispatchError> {913		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)914			.map_err(|_| <Error<T>>::CollectionUnknown.into())915	}916917	pub fn get_nft_collection(918		collection_id: CollectionId,919	) -> Result<NonfungibleHandle<T>, DispatchError> {920		let collection = <CollectionHandle<T>>::try_get(collection_id)921			.map_err(|_| <Error<T>>::CollectionUnknown)?;922923		match collection.mode {924			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),925			_ => Err(<Error<T>>::CollectionUnknown.into()),926		}927	}928929	pub fn collection_exists(collection_id: CollectionId) -> bool {930		<CollectionHandle<T>>::try_get(collection_id).is_ok()931	}932933	pub fn get_collection_property(934		collection_id: CollectionId,935		key: RmrkProperty,936	) -> Result<PropertyValue, DispatchError> {937		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)938			.get(&Self::rmrk_property_key(key)?)939			.ok_or(<Error<T>>::CollectionUnknown)?940			.clone();941942		Ok(collection_property)943	}944945	pub fn get_collection_property_decoded<V: Decode>(946		collection_id: CollectionId,947		key: RmrkProperty,948	) -> Result<V, DispatchError> {949		Self::decode_property(Self::get_collection_property(collection_id, key)?)950	}951952	pub fn get_collection_type(953		collection_id: CollectionId,954	) -> Result<misc::CollectionType, DispatchError> {955		Self::get_collection_property_decoded(collection_id, CollectionType)956			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())957	}958959	pub fn ensure_collection_type(960		collection_id: CollectionId,961		collection_type: misc::CollectionType,962	) -> DispatchResult {963		let actual_type = Self::get_collection_type(collection_id)?;964		ensure!(965			actual_type == collection_type,966			<CommonError<T>>::NoPermission967		);968969		Ok(())970	}971972	pub fn get_typed_nft_collection(973		collection_id: CollectionId,974		collection_type: misc::CollectionType,975	) -> Result<NonfungibleHandle<T>, DispatchError> {976		Self::ensure_collection_type(collection_id, collection_type)?;977978		Self::get_nft_collection(collection_id)979	}980981	pub fn get_typed_nft_collection_mapped(982		rmrk_collection_id: RmrkCollectionId,983		collection_type: misc::CollectionType,984	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {985		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;986987		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;988989		Ok((collection, unique_collection_id))990	}991992	pub fn get_nft_property(993		collection_id: CollectionId,994		nft_id: TokenId,995		key: RmrkProperty,996	) -> Result<PropertyValue, DispatchError> {997		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))998			.get(&Self::rmrk_property_key(key)?)999			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1000			.clone();10011002		Ok(nft_property)1003	}10041005	pub fn get_nft_property_decoded<V: Decode>(1006		collection_id: CollectionId,1007		nft_id: TokenId,1008		key: RmrkProperty,1009	) -> Result<V, DispatchError> {1010		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1011	}10121013	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1014		<TokenData<T>>::contains_key((collection_id, nft_id))1015	}10161017	pub fn get_nft_type(1018		collection_id: CollectionId,1019		token_id: TokenId,1020	) -> Result<NftType, DispatchError> {1021		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1022			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1023	}10241025	pub fn ensure_nft_type(1026		collection_id: CollectionId,1027		token_id: TokenId,1028		nft_type: NftType,1029	) -> DispatchResult {1030		let actual_type = Self::get_nft_type(collection_id, token_id)?;1031		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10321033		Ok(())1034	}10351036	pub fn ensure_nft_owner(1037		collection_id: CollectionId,1038		token_id: TokenId,1039		possible_owner: &T::CrossAccountId,1040		nesting_budget: &dyn budget::Budget1041	) -> DispatchResult {1042		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1043			possible_owner.clone(),1044			collection_id,1045			token_id,1046			None,1047			nesting_budget,1048		)?;10491050		ensure!(1051			is_owned,1052			<Error<T>>::NoPermission1053		);10541055		Ok(())1056	}10571058	pub fn filter_user_properties<Key, Value, R, Mapper>(1059		collection_id: CollectionId,1060		token_id: Option<TokenId>,1061		filter_keys: Option<Vec<RmrkPropertyKey>>,1062		mapper: Mapper,1063	) -> Result<Vec<R>, DispatchError>1064	where1065		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1066		Value: Decode + Default,1067		Mapper: Fn(Key, Value) -> R,1068	{1069		filter_keys1070			.map(|keys| {1071				let properties = keys1072					.into_iter()1073					.filter_map(|key| {1074						let key: Key = key.try_into().ok()?;10751076						let value = match token_id {1077							Some(token_id) => Self::get_nft_property_decoded(1078								collection_id,1079								token_id,1080								UserProperty(key.as_ref()),1081							),1082							None => Self::get_collection_property_decoded(1083								collection_id,1084								UserProperty(key.as_ref()),1085							),1086						}1087						.ok()?;10881089						Some(mapper(key, value))1090					})1091					.collect();10921093				Ok(properties)1094			})1095			.unwrap_or_else(|| {1096				let properties =1097					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10981099				Ok(properties)1100			})1101	}11021103	pub fn iterate_user_properties<Key, Value, R, Mapper>(1104		collection_id: CollectionId,1105		token_id: Option<TokenId>,1106		mapper: Mapper,1107	) -> Result<impl Iterator<Item = R>, DispatchError>1108	where1109		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1110		Value: Decode + Default,1111		Mapper: Fn(Key, Value) -> R,1112	{1113		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11141115		let properties = match token_id {1116			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1117			None => <PalletCommon<T>>::collection_properties(collection_id),1118		};11191120		let properties = properties.into_iter().filter_map(move |(key, value)| {1121			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11221123			let key: Key = key.to_vec().try_into().ok()?;1124			let value: Value = value.decode().ok()?;11251126			Some(mapper(key, value))1127		});11281129		Ok(properties)1130	}11311132	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1133		map_unique_err_to_proxy! {1134			match err {1135				CommonError::NoPermission => NoPermission,1136				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1137				CommonError::PublicMintingNotAllowed => NoPermission,1138				CommonError::TokenNotFound => NoAvailableNftId,1139				CommonError::ApprovedValueTooLow => NoPermission,1140				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1141				StructureError::TokenNotFound => NoAvailableNftId,1142				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1143			}1144		}1145	}1146}
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -3,7 +3,7 @@
 
 #[macro_export]
 macro_rules! map_unique_err_to_proxy {
-    (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ }) => {
+    (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {
         $(
             if $err == <$unique_err_ty<T>>::$unique_err.into() {
                 return <Error<T>>::$proxy_err.into()