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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs29.6 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;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	}136137	#[pallet::call]138	impl<T: Config> Pallet<T> {139		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]140		#[transactional]141		pub fn create_collection(142			origin: OriginFor<T>,143			metadata: RmrkString,144			max: Option<u32>,145			symbol: RmrkCollectionSymbol,146		) -> DispatchResult {147			let sender = ensure_signed(origin)?;148149			let limits = CollectionLimits {150				owner_can_transfer: Some(false),151				token_limit: max,152				..Default::default()153			};154155			let data = CreateCollectionData {156				limits: Some(limits),157				token_prefix: symbol158					.into_inner()159					.try_into()160					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,161				permissions: Some(CollectionPermissions {162					nesting: Some(NestingRule::Owner),163					..Default::default()164				}),165				..Default::default()166			};167168			let unique_collection_id = Self::init_collection(169				T::CrossAccountId::from_sub(sender.clone()),170				data,171				[172					Self::rmrk_property(Metadata, &metadata)?,173					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,174				]175				.into_iter(),176			)?;177			let rmrk_collection_id = <CollectionIndex<T>>::get();178179			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);180			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);181182			<CollectionIndex<T>>::mutate(|n| *n += 1);183184			Self::deposit_event(Event::CollectionCreated {185				issuer: sender,186				collection_id: rmrk_collection_id,187			});188189			Ok(())190		}191192		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]193		#[transactional]194		pub fn destroy_collection(195			origin: OriginFor<T>,196			collection_id: RmrkCollectionId,197		) -> DispatchResult {198			let sender = ensure_signed(origin)?;199			let cross_sender = T::CrossAccountId::from_sub(sender.clone());200201			let collection = Self::get_typed_nft_collection(202				Self::unique_collection_id(collection_id)?,203				misc::CollectionType::Regular,204			)?;205206			ensure!(207				collection.total_supply() == 0,208				<Error<T>>::CollectionNotEmpty209			);210211			<PalletNft<T>>::destroy_collection(collection, &cross_sender)212				.map_err(Self::map_unique_err_to_proxy)?;213214			Self::deposit_event(Event::CollectionDestroyed {215				issuer: sender,216				collection_id,217			});218219			Ok(())220		}221222		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]223		#[transactional]224		pub fn change_collection_issuer(225			origin: OriginFor<T>,226			collection_id: RmrkCollectionId,227			new_issuer: <T::Lookup as StaticLookup>::Source,228		) -> DispatchResult {229			let sender = ensure_signed(origin)?;230231			let new_issuer = T::Lookup::lookup(new_issuer)?;232233			Self::change_collection_owner(234				Self::unique_collection_id(collection_id)?,235				misc::CollectionType::Regular,236				sender.clone(),237				new_issuer.clone(),238			)?;239240			Self::deposit_event(Event::IssuerChanged {241				old_issuer: sender,242				new_issuer,243				collection_id,244			});245246			Ok(())247		}248249		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]250		#[transactional]251		pub fn lock_collection(252			origin: OriginFor<T>,253			collection_id: RmrkCollectionId,254		) -> DispatchResult {255			let sender = ensure_signed(origin)?;256			let cross_sender = T::CrossAccountId::from_sub(sender.clone());257258			let collection = Self::get_typed_nft_collection(259				Self::unique_collection_id(collection_id)?,260				misc::CollectionType::Regular,261			)?;262263			Self::check_collection_owner(&collection, &cross_sender)?;264265			let token_count = collection.total_supply();266267			let mut collection = collection.into_inner();268			collection.limits.token_limit = Some(token_count);269			collection.save()?;270271			Self::deposit_event(Event::CollectionLocked {272				issuer: sender,273				collection_id,274			});275276			Ok(())277		}278279		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]280		#[transactional]281		pub fn mint_nft(282			origin: OriginFor<T>,283			owner: T::AccountId,284			collection_id: RmrkCollectionId,285			recipient: Option<T::AccountId>,286			royalty_amount: Option<Permill>,287			metadata: RmrkString,288			transferable: bool,289		) -> DispatchResult {290			let sender = ensure_signed(origin)?;291			let sender = T::CrossAccountId::from_sub(sender);292			let cross_owner = T::CrossAccountId::from_sub(owner.clone());293294			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {295				recipient: recipient.unwrap_or_else(|| owner.clone()),296				amount,297			});298299			let collection = Self::get_typed_nft_collection(300				Self::unique_collection_id(collection_id)?,301				misc::CollectionType::Regular,302			)?;303304			let nft_id = Self::create_nft(305				&sender,306				&cross_owner,307				&collection,308				[309					Self::rmrk_property(TokenType, &NftType::Regular)?,310					Self::rmrk_property(Transferable, &transferable)?,311					Self::rmrk_property(PendingNftAccept, &false)?,312					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,313					Self::rmrk_property(Metadata, &metadata)?,314					Self::rmrk_property(Equipped, &false)?,315					Self::rmrk_property(316						ResourceCollection,317						&Self::init_collection(318							sender.clone(),319							CreateCollectionData {320								..Default::default()321							},322							[Self::rmrk_property(323								CollectionType,324								&misc::CollectionType::Resource,325							)?]326							.into_iter(),327						)?,328					)?, // todo possibly add limits to the collection if rmrk warrants them329					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,330				]331				.into_iter(),332			)333			.map_err(|err| match err {334				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),335				err => Self::map_unique_err_to_proxy(err),336			})?;337338			Self::deposit_event(Event::NftMinted {339				owner,340				collection_id,341				nft_id: nft_id.0,342			});343344			Ok(())345		}346347		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]348		#[transactional]349		pub fn burn_nft(350			origin: OriginFor<T>,351			collection_id: RmrkCollectionId,352			nft_id: RmrkNftId,353		) -> DispatchResult {354			let sender = ensure_signed(origin)?;355			let cross_sender = T::CrossAccountId::from_sub(sender.clone());356357			Self::destroy_nft(358				cross_sender,359				Self::unique_collection_id(collection_id)?,360				misc::CollectionType::Regular,361				nft_id.into(),362			)?;363364			Self::deposit_event(Event::NFTBurned {365				owner: sender,366				nft_id,367			});368369			Ok(())370		}371372		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]373		#[transactional]374		pub fn send(375			origin: OriginFor<T>,376			rmrk_collection_id: RmrkCollectionId,377			rmrk_nft_id: RmrkNftId,378			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,379		) -> DispatchResult {380			let sender = ensure_signed(origin.clone())?;381			let cross_sender = T::CrossAccountId::from_sub(sender);382383			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;384			let nft_id = rmrk_nft_id.into();385386			let token_data = <TokenData<T>>::get((collection_id, nft_id))387				.ok_or(<Error<T>>::NoAvailableNftId)?;388389			let from = token_data.owner;390391			let collection = Self::get_typed_nft_collection(392				collection_id,393				misc::CollectionType::Regular,394			)?;395396			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {397				return Err(<Error<T>>::NonTransferable.into());398			}399400			if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {401				return Err(<Error<T>>::NoPermission.into());402			}403404			let target_owner;405406			match new_owner {407				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408					target_owner = T::CrossAccountId::from_sub(account_id);409				},410				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {411					let target_collection_id = Self::unique_collection_id(target_collection_id)?;412413					let target_nft_budget = budget::Value::new(NESTING_BUDGET);414415					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(416						target_collection_id,417						target_nft_id.into(),418						Some((collection_id, nft_id)),419						&target_nft_budget,420					).map_err(Self::map_unique_err_to_proxy)?;421422					let is_approval_required = cross_sender != target_nft_owner;423424					if is_approval_required {425						target_owner = target_nft_owner;426427						<PalletNft<T>>::set_scoped_token_property(428							collection.id,429							nft_id,430							PropertyScope::Rmrk,431							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,432						)?;433					} else {434						target_owner = T::CrossTokenAddressMapping::token_to_address(435							target_collection_id,436							target_nft_id.into(),437						);438					}439				}440			}441442			let src_nft_budget = budget::Value::new(NESTING_BUDGET);443444			<PalletNft<T>>::transfer_from(445				&collection,446				&cross_sender,447				&from,448				&target_owner,449				nft_id,450				&src_nft_budget451			).map_err(Self::map_unique_err_to_proxy)?;452453			Ok(())454		}455456		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]457		#[transactional]458		pub fn set_property(459			origin: OriginFor<T>,460			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,461			maybe_nft_id: Option<RmrkNftId>,462			key: RmrkKeyString,463			value: RmrkValueString,464		) -> DispatchResult {465			let sender = ensure_signed(origin)?;466			let sender = T::CrossAccountId::from_sub(sender);467468			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469			let budget = budget::Value::new(NESTING_BUDGET);470471			match maybe_nft_id {472				Some(nft_id) => {473					let token_id: TokenId = nft_id.into();474475					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;476					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;477478					<PalletNft<T>>::set_scoped_token_property(479						collection_id,480						token_id,481						PropertyScope::Rmrk,482						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483					)?;484				}485				None => {486					let collection = Self::get_typed_nft_collection(487						collection_id,488						misc::CollectionType::Regular,489					)?;490491					Self::check_collection_owner(&collection, &sender)?;492493					<PalletCommon<T>>::set_scoped_collection_property(494						collection_id,495						PropertyScope::Rmrk,496						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,497					)?;498				}499			}500501			Self::deposit_event(Event::PropertySet {502				collection_id: rmrk_collection_id,503				maybe_nft_id,504				key,505				value,506			});507508			Ok(())509		}510511		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]512		#[transactional]513		pub fn add_basic_resource(514			origin: OriginFor<T>,515			collection_id: RmrkCollectionId,516			nft_id: RmrkNftId,517			resource: RmrkBasicResource,518		) -> DispatchResult {519			let sender = ensure_signed(origin.clone())?;520521			let resource_id = Self::resource_add(522				sender,523				Self::unique_collection_id(collection_id)?,524				nft_id.into(),525				[526					Self::rmrk_property(TokenType, &NftType::Resource)?,527					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,528					Self::rmrk_property(Src, &resource.src)?,529					Self::rmrk_property(Metadata, &resource.metadata)?,530					Self::rmrk_property(License, &resource.license)?,531					Self::rmrk_property(Thumb, &resource.thumb)?,532				]533				.into_iter(),534			)?;535536			Self::deposit_event(Event::ResourceAdded {537				nft_id,538				resource_id,539			});540			Ok(())541		}542543		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]544		#[transactional]545		pub fn add_composable_resource(546			origin: OriginFor<T>,547			collection_id: RmrkCollectionId,548			nft_id: RmrkNftId,549			_resource_id: RmrkBoundedResource,550			resource: RmrkComposableResource,551		) -> DispatchResult {552			let sender = ensure_signed(origin.clone())?;553554			let resource_id = Self::resource_add(555				sender,556				Self::unique_collection_id(collection_id)?,557				nft_id.into(),558				[559					Self::rmrk_property(TokenType, &NftType::Resource)?,560					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,561					Self::rmrk_property(Parts, &resource.parts)?,562					Self::rmrk_property(Base, &resource.base)?,563					Self::rmrk_property(Src, &resource.src)?,564					Self::rmrk_property(Metadata, &resource.metadata)?,565					Self::rmrk_property(License, &resource.license)?,566					Self::rmrk_property(Thumb, &resource.thumb)?,567				]568				.into_iter(),569			)?;570571			Self::deposit_event(Event::ResourceAdded {572				nft_id,573				resource_id,574			});575			Ok(())576		}577578		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]579		#[transactional]580		pub fn add_slot_resource(581			origin: OriginFor<T>,582			collection_id: RmrkCollectionId,583			nft_id: RmrkNftId,584			resource: RmrkSlotResource,585		) -> DispatchResult {586			let sender = ensure_signed(origin.clone())?;587588			let resource_id = Self::resource_add(589				sender,590				Self::unique_collection_id(collection_id)?,591				nft_id.into(),592				[593					Self::rmrk_property(TokenType, &NftType::Resource)?,594					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,595					Self::rmrk_property(Base, &resource.base)?,596					Self::rmrk_property(Src, &resource.src)?,597					Self::rmrk_property(Metadata, &resource.metadata)?,598					Self::rmrk_property(Slot, &resource.slot)?,599					Self::rmrk_property(License, &resource.license)?,600					Self::rmrk_property(Thumb, &resource.thumb)?,601				]602				.into_iter(),603			)?;604605			Self::deposit_event(Event::ResourceAdded {606				nft_id,607				resource_id,608			});609			Ok(())610		}611612		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]613		#[transactional]614		pub fn remove_resource(615			origin: OriginFor<T>,616			collection_id: RmrkCollectionId,617			nft_id: RmrkNftId,618			resource_id: RmrkResourceId,619		) -> DispatchResult {620			let sender = ensure_signed(origin.clone())?;621622			Self::resource_remove(623				sender,624				Self::unique_collection_id(collection_id)?,625				nft_id.into(),626				resource_id.into(),627			)?;628629			Self::deposit_event(Event::ResourceRemoval {630				nft_id,631				resource_id,632			});633			Ok(())634		}635	}636}637638impl<T: Config> Pallet<T> {639	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {640		let key = rmrk_key.to_key::<T>()?;641642		let scoped_key = PropertyScope::Rmrk643			.apply(key)644			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;645646		Ok(scoped_key)647	}648649	// todo think about renaming these650	pub fn rmrk_property<E: Encode>(651		rmrk_key: RmrkProperty,652		value: &E,653	) -> Result<Property, DispatchError> {654		let key = rmrk_key.to_key::<T>()?;655656		let value = value657			.encode()658			.try_into()659			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;660661		let property = Property { key, value };662663		Ok(property)664	}665666	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {667		vec.decode()668			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())669	}670671	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>672	where673		BoundedVec<u8, S>: TryFrom<Vec<u8>>,674	{675		vec.rebind()676			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())677	}678679	fn init_collection(680		sender: T::CrossAccountId,681		data: CreateCollectionData<T::AccountId>,682		properties: impl Iterator<Item = Property>,683	) -> Result<CollectionId, DispatchError> {684		let collection_id = <PalletNft<T>>::init_collection(sender, data);685686		if let Err(DispatchError::Arithmetic(_)) = &collection_id {687			return Err(<Error<T>>::NoAvailableCollectionId.into());688		}689690		<PalletCommon<T>>::set_scoped_collection_properties(691			collection_id?,692			PropertyScope::Rmrk,693			properties,694		)?;695696		collection_id697	}698699	pub fn create_nft(700		sender: &T::CrossAccountId,701		owner: &T::CrossAccountId,702		collection: &NonfungibleHandle<T>,703		properties: impl Iterator<Item = Property>,704	) -> Result<TokenId, DispatchError> {705		let data = CreateNftExData {706			properties: BoundedVec::default(),707			owner: owner.clone(),708		};709710		let budget = budget::Value::new(NESTING_BUDGET);711712		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;713714		let nft_id = <PalletNft<T>>::current_token_id(collection.id);715716		<PalletNft<T>>::set_scoped_token_properties(717			collection.id,718			nft_id,719			PropertyScope::Rmrk,720			properties,721		)?;722723		Ok(nft_id)724	}725726	fn destroy_nft(727		sender: T::CrossAccountId,728		collection_id: CollectionId,729		collection_type: misc::CollectionType,730		token_id: TokenId,731	) -> DispatchResult {732		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;733734		<PalletNft<T>>::burn(&collection, &sender, token_id)735			.map_err(Self::map_unique_err_to_proxy)?;736737		Ok(())738	}739740	fn resource_add(741		sender: T::AccountId,742		collection_id: CollectionId,743		token_id: TokenId,744		resource_properties: impl Iterator<Item = Property>,745	) -> Result<RmrkResourceId, DispatchError> {746		let collection =747			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;748		ensure!(collection.owner == sender, Error::<T>::NoPermission);749750		// Check NFT lock status // todo depends on market, maybe later751		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);752753		let sender = T::CrossAccountId::from_sub(sender);754		let budget = budget::Value::new(NESTING_BUDGET);755		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();756757		let resource_collection_id: CollectionId =758			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;759		let resource_collection =760			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;761762		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them763764		let resource_id = Self::create_nft(765			&sender, // todo owner of the nft?766			&sender,767			&resource_collection,768			resource_properties.chain(769				[770					Self::rmrk_property(PendingResourceAccept, &pending)?,771					Self::rmrk_property(PendingResourceRemoval, &false)?,772				]773				.into_iter(),774			),775		)776		.map_err(|err| match err {777			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),778			err => Self::map_unique_err_to_proxy(err),779		})?;780781		Ok(resource_id.0)782	}783784	fn resource_remove(785		sender: T::AccountId,786		collection_id: CollectionId,787		nft_id: TokenId,788		resource_id: TokenId,789	) -> DispatchResult {790		let collection =791			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;792		ensure!(collection.owner == sender, Error::<T>::NoPermission);793794		let resource_collection_id: CollectionId =795			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;796		let resource_collection =797			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;798		ensure!(799			<PalletNft<T>>::token_exists(&resource_collection, resource_id),800			Error::<T>::ResourceDoesntExist801		);802803		let budget = up_data_structs::budget::Value::new(10);804		let topmost_owner =805			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;806807		let sender = T::CrossAccountId::from_sub(sender);808		if topmost_owner == sender {809			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)810				.map_err(Self::map_unique_err_to_proxy)?;811		} else {812			<PalletNft<T>>::set_scoped_token_property(813				resource_collection_id,814				resource_id,815				PropertyScope::Rmrk,816				Self::rmrk_property(PendingResourceRemoval, &true)?,817			)?;818		}819820		Ok(())821	}822823	fn change_collection_owner(824		collection_id: CollectionId,825		collection_type: misc::CollectionType,826		sender: T::AccountId,827		new_owner: T::AccountId,828	) -> DispatchResult {829		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;830		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;831832		let mut collection = collection.into_inner();833834		collection.owner = new_owner;835		collection.save()836	}837838	fn check_collection_owner(839		collection: &NonfungibleHandle<T>,840		account: &T::CrossAccountId,841	) -> DispatchResult {842		collection843			.check_is_owner(account)844			.map_err(Self::map_unique_err_to_proxy)845	}846847	pub fn last_collection_idx() -> RmrkCollectionId {848		<CollectionIndex<T>>::get()849	}850851	pub fn unique_collection_id(852		rmrk_collection_id: RmrkCollectionId,853	) -> Result<CollectionId, DispatchError> {854		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)855			.map_err(|_| <Error<T>>::CollectionUnknown.into())856	}857858	pub fn rmrk_collection_id(859		unique_collection_id: CollectionId860	) -> Result<RmrkCollectionId, DispatchError> {861		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)862			.map_err(|_| <Error<T>>::CollectionUnknown.into())863	}864865	pub fn get_nft_collection(866		collection_id: CollectionId,867	) -> Result<NonfungibleHandle<T>, DispatchError> {868		let collection = <CollectionHandle<T>>::try_get(collection_id)869			.map_err(|_| <Error<T>>::CollectionUnknown)?;870871		match collection.mode {872			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),873			_ => Err(<Error<T>>::CollectionUnknown.into()),874		}875	}876877	pub fn collection_exists(collection_id: CollectionId) -> bool {878		<CollectionHandle<T>>::try_get(collection_id).is_ok()879	}880881	pub fn get_collection_property(882		collection_id: CollectionId,883		key: RmrkProperty,884	) -> Result<PropertyValue, DispatchError> {885		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)886			.get(&Self::rmrk_property_key(key)?)887			.ok_or(<Error<T>>::CollectionUnknown)?888			.clone();889890		Ok(collection_property)891	}892893	pub fn get_collection_property_decoded<V: Decode>(894		collection_id: CollectionId,895		key: RmrkProperty,896	) -> Result<V, DispatchError> {897		Self::decode_property(Self::get_collection_property(collection_id, key)?)898	}899900	pub fn get_collection_type(901		collection_id: CollectionId,902	) -> Result<misc::CollectionType, DispatchError> {903		Self::get_collection_property_decoded(collection_id, CollectionType)904			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())905	}906907	pub fn ensure_collection_type(908		collection_id: CollectionId,909		collection_type: misc::CollectionType,910	) -> DispatchResult {911		let actual_type = Self::get_collection_type(collection_id)?;912		ensure!(913			actual_type == collection_type,914			<CommonError<T>>::NoPermission915		);916917		Ok(())918	}919920	pub fn get_typed_nft_collection(921		collection_id: CollectionId,922		collection_type: misc::CollectionType,923	) -> Result<NonfungibleHandle<T>, DispatchError> {924		Self::ensure_collection_type(collection_id, collection_type)?;925926		Self::get_nft_collection(collection_id)927	}928929	pub fn get_typed_nft_collection_mapped(930		rmrk_collection_id: RmrkCollectionId,931		collection_type: misc::CollectionType,932	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {933		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;934935		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;936937		Ok((collection, unique_collection_id))938	}939940	pub fn get_nft_property(941		collection_id: CollectionId,942		nft_id: TokenId,943		key: RmrkProperty,944	) -> Result<PropertyValue, DispatchError> {945		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))946			.get(&Self::rmrk_property_key(key)?)947			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?948			.clone();949950		Ok(nft_property)951	}952953	pub fn get_nft_property_decoded<V: Decode>(954		collection_id: CollectionId,955		nft_id: TokenId,956		key: RmrkProperty,957	) -> Result<V, DispatchError> {958		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)959	}960961	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {962		<TokenData<T>>::contains_key((collection_id, nft_id))963	}964965	pub fn get_nft_type(966		collection_id: CollectionId,967		token_id: TokenId,968	) -> Result<NftType, DispatchError> {969		Self::get_nft_property_decoded(collection_id, token_id, TokenType)970			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())971	}972973	pub fn ensure_nft_type(974		collection_id: CollectionId,975		token_id: TokenId,976		nft_type: NftType,977	) -> DispatchResult {978		let actual_type = Self::get_nft_type(collection_id, token_id)?;979		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);980981		Ok(())982	}983984	pub fn ensure_nft_owner(985		collection_id: CollectionId,986		token_id: TokenId,987		possible_owner: &T::CrossAccountId,988		nesting_budget: &dyn budget::Budget989	) -> DispatchResult {990		let is_owned = <PalletStructure<T>>::check_indirectly_owned(991			possible_owner.clone(),992			collection_id,993			token_id,994			None,995			nesting_budget,996		)?;997998		ensure!(999			is_owned,1000			<Error<T>>::NoPermission1001		);10021003		Ok(())1004	}10051006	pub fn filter_user_properties<Key, Value, R, Mapper>(1007		collection_id: CollectionId,1008		token_id: Option<TokenId>,1009		filter_keys: Option<Vec<RmrkPropertyKey>>,1010		mapper: Mapper,1011	) -> Result<Vec<R>, DispatchError>1012	where1013		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1014		Value: Decode + Default,1015		Mapper: Fn(Key, Value) -> R,1016	{1017		filter_keys1018			.map(|keys| {1019				let properties = keys1020					.into_iter()1021					.filter_map(|key| {1022						let key: Key = key.try_into().ok()?;10231024						let value = match token_id {1025							Some(token_id) => Self::get_nft_property_decoded(1026								collection_id,1027								token_id,1028								UserProperty(key.as_ref()),1029							),1030							None => Self::get_collection_property_decoded(1031								collection_id,1032								UserProperty(key.as_ref()),1033							),1034						}1035						.ok()?;10361037						Some(mapper(key, value))1038					})1039					.collect();10401041				Ok(properties)1042			})1043			.unwrap_or_else(|| {1044				let properties =1045					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10461047				Ok(properties)1048			})1049	}10501051	pub fn iterate_user_properties<Key, Value, R, Mapper>(1052		collection_id: CollectionId,1053		token_id: Option<TokenId>,1054		mapper: Mapper,1055	) -> Result<impl Iterator<Item = R>, DispatchError>1056	where1057		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1058		Value: Decode + Default,1059		Mapper: Fn(Key, Value) -> R,1060	{1061		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10621063		let properties = match token_id {1064			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1065			None => <PalletCommon<T>>::collection_properties(collection_id),1066		};10671068		let properties = properties.into_iter().filter_map(move |(key, value)| {1069			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10701071			let key: Key = key.to_vec().try_into().ok()?;1072			let value: Value = value.decode().ok()?;10731074			Some(mapper(key, value))1075		});10761077		Ok(properties)1078	}10791080	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1081		map_unique_err_to_proxy! {1082			match err {1083				CommonError::NoPermission => NoPermission,1084				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1085				CommonError::PublicMintingNotAllowed => NoPermission,1086				CommonError::TokenNotFound => NoAvailableNftId,1087				CommonError::ApprovedValueTooLow => NoPermission,1088				StructureError::TokenNotFound => NoAvailableNftId,1089				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1090			}1091		}1092	}1093}