git.delta.rocks / unique-network / refs/commits / 5847eccaa1a7

difftreelog

fix(rmrk) priority events

Daniel Shiposha2022-06-08parent: #2999bc8.patch.diff
in: master

1 file 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		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141	}142143	#[pallet::error]144	pub enum Error<T> {145		/* Unique-specific events */146		CorruptedCollectionType,147		NftTypeEncodeError,148		RmrkPropertyKeyIsTooLong,149		RmrkPropertyValueIsTooLong,150151		/* RMRK compatible events */152		CollectionNotEmpty,153		NoAvailableCollectionId,154		NoAvailableNftId,155		CollectionUnknown,156		NoPermission,157		NonTransferable,158		CollectionFullOrLocked,159		ResourceDoesntExist,160		CannotSendToDescendentOrSelf,161		CannotAcceptNonOwnedNft,162		CannotRejectNonOwnedNft,163		ResourceNotPending,164	}165166	#[pallet::call]167	impl<T: Config> Pallet<T> {168		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]169		#[transactional]170		pub fn create_collection(171			origin: OriginFor<T>,172			metadata: RmrkString,173			max: Option<u32>,174			symbol: RmrkCollectionSymbol,175		) -> DispatchResult {176			let sender = ensure_signed(origin)?;177178			let limits = CollectionLimits {179				owner_can_transfer: Some(false),180				token_limit: max,181				..Default::default()182			};183184			let data = CreateCollectionData {185				limits: Some(limits),186				token_prefix: symbol187					.into_inner()188					.try_into()189					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,190				permissions: Some(CollectionPermissions {191					nesting: Some(NestingRule::Owner),192					..Default::default()193				}),194				..Default::default()195			};196197			let unique_collection_id = Self::init_collection(198				T::CrossAccountId::from_sub(sender.clone()),199				data,200				[201					Self::rmrk_property(Metadata, &metadata)?,202					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,203				]204				.into_iter(),205			)?;206			let rmrk_collection_id = <CollectionIndex<T>>::get();207208			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);209			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);210211			<CollectionIndex<T>>::mutate(|n| *n += 1);212213			Self::deposit_event(Event::CollectionCreated {214				issuer: sender,215				collection_id: rmrk_collection_id,216			});217218			Ok(())219		}220221		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]222		#[transactional]223		pub fn destroy_collection(224			origin: OriginFor<T>,225			collection_id: RmrkCollectionId,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228			let cross_sender = T::CrossAccountId::from_sub(sender.clone());229230			let collection = Self::get_typed_nft_collection(231				Self::unique_collection_id(collection_id)?,232				misc::CollectionType::Regular,233			)?;234235			<PalletNft<T>>::destroy_collection(collection, &cross_sender)236				.map_err(Self::map_unique_err_to_proxy)?;237238			Self::deposit_event(Event::CollectionDestroyed {239				issuer: sender,240				collection_id,241			});242243			Ok(())244		}245246		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]247		#[transactional]248		pub fn change_collection_issuer(249			origin: OriginFor<T>,250			collection_id: RmrkCollectionId,251			new_issuer: <T::Lookup as StaticLookup>::Source,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254255			let new_issuer = T::Lookup::lookup(new_issuer)?;256257			Self::change_collection_owner(258				Self::unique_collection_id(collection_id)?,259				misc::CollectionType::Regular,260				sender.clone(),261				new_issuer.clone(),262			)?;263264			Self::deposit_event(Event::IssuerChanged {265				old_issuer: sender,266				new_issuer,267				collection_id,268			});269270			Ok(())271		}272273		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]274		#[transactional]275		pub fn lock_collection(276			origin: OriginFor<T>,277			collection_id: RmrkCollectionId,278		) -> DispatchResult {279			let sender = ensure_signed(origin)?;280			let cross_sender = T::CrossAccountId::from_sub(sender.clone());281282			let collection = Self::get_typed_nft_collection(283				Self::unique_collection_id(collection_id)?,284				misc::CollectionType::Regular,285			)?;286287			Self::check_collection_owner(&collection, &cross_sender)?;288289			let token_count = collection.total_supply();290291			let mut collection = collection.into_inner();292			collection.limits.token_limit = Some(token_count);293			collection.save()?;294295			Self::deposit_event(Event::CollectionLocked {296				issuer: sender,297				collection_id,298			});299300			Ok(())301		}302303		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304		#[transactional]305		pub fn mint_nft(306			origin: OriginFor<T>,307			owner: T::AccountId,308			collection_id: RmrkCollectionId,309			recipient: Option<T::AccountId>,310			royalty_amount: Option<Permill>,311			metadata: RmrkString,312			transferable: bool,313		) -> DispatchResult {314			let sender = ensure_signed(origin)?;315			let sender = T::CrossAccountId::from_sub(sender);316			let cross_owner = T::CrossAccountId::from_sub(owner.clone());317318			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {319				recipient: recipient.unwrap_or_else(|| owner.clone()),320				amount,321			});322323			let collection = Self::get_typed_nft_collection(324				Self::unique_collection_id(collection_id)?,325				misc::CollectionType::Regular,326			)?;327328			let nft_id = Self::create_nft(329				&sender,330				&cross_owner,331				&collection,332				[333					Self::rmrk_property(TokenType, &NftType::Regular)?,334					Self::rmrk_property(Transferable, &transferable)?,335					Self::rmrk_property(PendingNftAccept, &false)?,336					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,337					Self::rmrk_property(Metadata, &metadata)?,338					Self::rmrk_property(Equipped, &false)?,339					Self::rmrk_property(340						ResourceCollection,341						&Self::init_collection(342							sender.clone(),343							CreateCollectionData {344								..Default::default()345							},346							[Self::rmrk_property(347								CollectionType,348								&misc::CollectionType::Resource,349							)?]350							.into_iter(),351						)?,352					)?, // todo possibly add limits to the collection if rmrk warrants them353					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,354				]355				.into_iter(),356			)357			.map_err(|err| match err {358				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),359				err => Self::map_unique_err_to_proxy(err),360			})?;361362			Self::deposit_event(Event::NftMinted {363				owner,364				collection_id,365				nft_id: nft_id.0,366			});367368			Ok(())369		}370371		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]372		#[transactional]373		pub fn burn_nft(374			origin: OriginFor<T>,375			collection_id: RmrkCollectionId,376			nft_id: RmrkNftId,377		) -> DispatchResult {378			let sender = ensure_signed(origin)?;379			let cross_sender = T::CrossAccountId::from_sub(sender.clone());380381			Self::destroy_nft(382				cross_sender,383				Self::unique_collection_id(collection_id)?,384				nft_id.into(),385			)386			.map_err(Self::map_unique_err_to_proxy)?;387388			Self::deposit_event(Event::NFTBurned {389				owner: sender,390				nft_id,391			});392393			Ok(())394		}395396		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]397		#[transactional]398		pub fn send(399			origin: OriginFor<T>,400			rmrk_collection_id: RmrkCollectionId,401			rmrk_nft_id: RmrkNftId,402			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,403		) -> DispatchResult {404			let sender = ensure_signed(origin.clone())?;405			let cross_sender = T::CrossAccountId::from_sub(sender.clone());406407			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;408			let nft_id = rmrk_nft_id.into();409410			let token_data =411				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;412413			let from = token_data.owner;414415			let collection =416				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;417418			ensure!(419				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,420				<Error<T>>::NonTransferable421			);422423			ensure!(424				!Self::get_nft_property_decoded(425					collection_id,426					nft_id,427					RmrkProperty::PendingNftAccept428				)?,429				<Error<T>>::NoPermission430			);431432			let target_owner;433			let approval_required;434435			match new_owner {436				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {437					target_owner = T::CrossAccountId::from_sub(account_id.clone());438					approval_required = false;439				}440				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(441					target_collection_id,442					target_nft_id,443				) => {444					let target_collection_id = Self::unique_collection_id(target_collection_id)?;445446					let target_nft_budget = budget::Value::new(NESTING_BUDGET);447448					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(449						target_collection_id,450						target_nft_id.into(),451						Some((collection_id, nft_id)),452						&target_nft_budget,453					)454					.map_err(Self::map_unique_err_to_proxy)?;455456					approval_required = cross_sender != target_nft_owner;457458					if approval_required {459						target_owner = target_nft_owner;460461						<PalletNft<T>>::set_scoped_token_property(462							collection.id,463							nft_id,464							PropertyScope::Rmrk,465							Self::rmrk_property(PendingNftAccept, &approval_required)?,466						)?;467					} else {468						target_owner = T::CrossTokenAddressMapping::token_to_address(469							target_collection_id,470							target_nft_id.into(),471						);472					}473				}474			}475476			let src_nft_budget = budget::Value::new(NESTING_BUDGET);477478			<PalletNft<T>>::transfer_from(479				&collection,480				&cross_sender,481				&from,482				&target_owner,483				nft_id,484				&src_nft_budget,485			)486			.map_err(Self::map_unique_err_to_proxy)?;487488			Self::deposit_event(Event::NFTSent {489				sender,490				recipient: new_owner,491				collection_id: rmrk_collection_id,492				nft_id: rmrk_nft_id,493				approval_required,494			});495496			Ok(())497		}498499		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]500		#[transactional]501		pub fn accept_nft(502			origin: OriginFor<T>,503			rmrk_collection_id: RmrkCollectionId,504			rmrk_nft_id: RmrkNftId,505			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,506		) -> DispatchResult {507			let sender = ensure_signed(origin.clone())?;508			let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;511			let nft_id = rmrk_nft_id.into();512513			let collection =514				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;515516			let new_cross_owner = match new_owner {517				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {518					T::CrossAccountId::from_sub(account_id.clone())519				}520				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(521					target_collection_id,522					target_nft_id,523				) => {524					let target_collection_id = Self::unique_collection_id(target_collection_id)?;525526					T::CrossTokenAddressMapping::token_to_address(527						target_collection_id,528						TokenId(target_nft_id),529					)530				}531			};532533			let budget = budget::Value::new(NESTING_BUDGET);534535			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)536				.map_err(|err| {537					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {538						<Error<T>>::CannotAcceptNonOwnedNft.into()539					} else {540						Self::map_unique_err_to_proxy(err)541					}542				})?;543544			<PalletNft<T>>::set_scoped_token_property(545				collection.id,546				nft_id,547				PropertyScope::Rmrk,548				Self::rmrk_property(PendingNftAccept, &false)?,549			)?;550551			Self::deposit_event(Event::NFTAccepted {552				sender,553				recipient: new_owner,554				collection_id: rmrk_collection_id,555				nft_id: rmrk_nft_id,556			});557558			Ok(())559		}560561		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]562		#[transactional]563		pub fn reject_nft(564			origin: OriginFor<T>,565			rmrk_collection_id: RmrkCollectionId,566			rmrk_nft_id: RmrkNftId,567		) -> DispatchResult {568			let sender = ensure_signed(origin)?;569			let cross_sender = T::CrossAccountId::from_sub(sender.clone());570571			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;572			let nft_id = rmrk_nft_id.into();573574			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {575				if err == <CommonError<T>>::NoPermission.into()576					|| err == <CommonError<T>>::ApprovedValueTooLow.into()577				{578					<Error<T>>::CannotRejectNonOwnedNft.into()579				} else {580					Self::map_unique_err_to_proxy(err)581				}582			})?;583584			Self::deposit_event(Event::NFTRejected {585				sender,586				collection_id: rmrk_collection_id,587				nft_id: rmrk_nft_id,588			});589590			Ok(())591		}592593		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]594		#[transactional]595		pub fn accept_resource(596			origin: OriginFor<T>,597			rmrk_collection_id: RmrkCollectionId,598			rmrk_nft_id: RmrkNftId,599			rmrk_resource_id: RmrkResourceId,600		) -> DispatchResult {601			let sender = ensure_signed(origin)?;602			let cross_sender = T::CrossAccountId::from_sub(sender);603604			let collection_id = Self::unique_collection_id(rmrk_collection_id)605				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;606607			let nft_id = rmrk_nft_id.into();608			let resource_id = rmrk_resource_id.into();609610			let budget = budget::Value::new(NESTING_BUDGET);611612			let nft_owner =613				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)614					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;615616			let resource_collection_id: CollectionId =617				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)618					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;619620			let is_pending: bool = Self::get_nft_property_decoded(621				resource_collection_id,622				resource_id,623				PendingResourceAccept,624			)625			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;626627			ensure!(is_pending, <Error<T>>::ResourceNotPending);628629			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);630631			<PalletNft<T>>::set_scoped_token_property(632				resource_collection_id,633				rmrk_resource_id.into(),634				PropertyScope::Rmrk,635				Self::rmrk_property(PendingResourceAccept, &false)?,636			)?;637638			Self::deposit_event(Event::<T>::ResourceAccepted {639				nft_id: rmrk_nft_id,640				resource_id: rmrk_resource_id,641			});642643			Ok(())644		}645646		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]647		#[transactional]648		pub fn accept_resource_removal(649			origin: OriginFor<T>,650			rmrk_collection_id: RmrkCollectionId,651			rmrk_nft_id: RmrkNftId,652			rmrk_resource_id: RmrkResourceId,653		) -> DispatchResult {654			let sender = ensure_signed(origin)?;655			let cross_sender = T::CrossAccountId::from_sub(sender);656657			let collection_id = Self::unique_collection_id(rmrk_collection_id)658				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;659660			let nft_id = rmrk_nft_id.into();661			let resource_id = rmrk_resource_id.into();662663			let budget = budget::Value::new(NESTING_BUDGET);664665			let nft_owner =666				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)667					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;668669			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);670671			let resource_collection_id: CollectionId =672				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)673					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;674675			let is_pending: bool = Self::get_nft_property_decoded(676				resource_collection_id,677				resource_id,678				PendingResourceRemoval,679			)680			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;681682			ensure!(is_pending, <Error<T>>::ResourceNotPending);683684			let resource_collection = Self::get_typed_nft_collection(685				resource_collection_id,686				misc::CollectionType::Resource,687			)?;688689			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())690				.map_err(Self::map_unique_err_to_proxy)?;691692			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {693				nft_id: rmrk_nft_id,694				resource_id: rmrk_resource_id,695			});696697			Ok(())698		}699700		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]701		#[transactional]702		pub fn set_property(703			origin: OriginFor<T>,704			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,705			maybe_nft_id: Option<RmrkNftId>,706			key: RmrkKeyString,707			value: RmrkValueString,708		) -> DispatchResult {709			let sender = ensure_signed(origin)?;710			let sender = T::CrossAccountId::from_sub(sender);711712			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;713			let budget = budget::Value::new(NESTING_BUDGET);714715			match maybe_nft_id {716				Some(nft_id) => {717					let token_id: TokenId = nft_id.into();718719					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;720					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;721722					<PalletNft<T>>::set_scoped_token_property(723						collection_id,724						token_id,725						PropertyScope::Rmrk,726						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,727					)?;728				}729				None => {730					let collection = Self::get_typed_nft_collection(731						collection_id,732						misc::CollectionType::Regular,733					)?;734735					Self::check_collection_owner(&collection, &sender)?;736737					<PalletCommon<T>>::set_scoped_collection_property(738						collection_id,739						PropertyScope::Rmrk,740						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,741					)?;742				}743			}744745			Self::deposit_event(Event::PropertySet {746				collection_id: rmrk_collection_id,747				maybe_nft_id,748				key,749				value,750			});751752			Ok(())753		}754755		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]756		#[transactional]757		pub fn set_priority(758			origin: OriginFor<T>,759			rmrk_collection_id: RmrkCollectionId,760			rmrk_nft_id: RmrkNftId,761			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,762		) -> DispatchResult {763			let sender = ensure_signed(origin)?;764			let sender = T::CrossAccountId::from_sub(sender);765766			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;767			let nft_id = rmrk_nft_id.into();768			let budget = budget::Value::new(NESTING_BUDGET);769770			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;771			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;772773			<PalletNft<T>>::set_scoped_token_property(774				collection_id,775				nft_id,776				PropertyScope::Rmrk,777				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,778			)?;779780			Ok(())781		}782783		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]784		#[transactional]785		pub fn add_basic_resource(786			origin: OriginFor<T>,787			collection_id: RmrkCollectionId,788			nft_id: RmrkNftId,789			resource: RmrkBasicResource,790		) -> DispatchResult {791			let sender = ensure_signed(origin.clone())?;792793			let resource_id = Self::resource_add(794				sender,795				Self::unique_collection_id(collection_id)?,796				nft_id.into(),797				[798					Self::rmrk_property(TokenType, &NftType::Resource)?,799					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,800					Self::rmrk_property(Src, &resource.src)?,801					Self::rmrk_property(Metadata, &resource.metadata)?,802					Self::rmrk_property(License, &resource.license)?,803					Self::rmrk_property(Thumb, &resource.thumb)?,804				]805				.into_iter(),806			)?;807808			Self::deposit_event(Event::ResourceAdded {809				nft_id,810				resource_id,811			});812			Ok(())813		}814815		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]816		#[transactional]817		pub fn add_composable_resource(818			origin: OriginFor<T>,819			collection_id: RmrkCollectionId,820			nft_id: RmrkNftId,821			_resource_id: RmrkBoundedResource,822			resource: RmrkComposableResource,823		) -> DispatchResult {824			let sender = ensure_signed(origin.clone())?;825826			let resource_id = Self::resource_add(827				sender,828				Self::unique_collection_id(collection_id)?,829				nft_id.into(),830				[831					Self::rmrk_property(TokenType, &NftType::Resource)?,832					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,833					Self::rmrk_property(Parts, &resource.parts)?,834					Self::rmrk_property(Base, &resource.base)?,835					Self::rmrk_property(Src, &resource.src)?,836					Self::rmrk_property(Metadata, &resource.metadata)?,837					Self::rmrk_property(License, &resource.license)?,838					Self::rmrk_property(Thumb, &resource.thumb)?,839				]840				.into_iter(),841			)?;842843			Self::deposit_event(Event::ResourceAdded {844				nft_id,845				resource_id,846			});847			Ok(())848		}849850		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]851		#[transactional]852		pub fn add_slot_resource(853			origin: OriginFor<T>,854			collection_id: RmrkCollectionId,855			nft_id: RmrkNftId,856			resource: RmrkSlotResource,857		) -> DispatchResult {858			let sender = ensure_signed(origin.clone())?;859860			let resource_id = Self::resource_add(861				sender,862				Self::unique_collection_id(collection_id)?,863				nft_id.into(),864				[865					Self::rmrk_property(TokenType, &NftType::Resource)?,866					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,867					Self::rmrk_property(Base, &resource.base)?,868					Self::rmrk_property(Src, &resource.src)?,869					Self::rmrk_property(Metadata, &resource.metadata)?,870					Self::rmrk_property(Slot, &resource.slot)?,871					Self::rmrk_property(License, &resource.license)?,872					Self::rmrk_property(Thumb, &resource.thumb)?,873				]874				.into_iter(),875			)?;876877			Self::deposit_event(Event::ResourceAdded {878				nft_id,879				resource_id,880			});881			Ok(())882		}883884		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885		#[transactional]886		pub fn remove_resource(887			origin: OriginFor<T>,888			collection_id: RmrkCollectionId,889			nft_id: RmrkNftId,890			resource_id: RmrkResourceId,891		) -> DispatchResult {892			let sender = ensure_signed(origin.clone())?;893894			Self::resource_remove(895				sender,896				Self::unique_collection_id(collection_id)?,897				nft_id.into(),898				resource_id.into(),899			)?;900901			Self::deposit_event(Event::ResourceRemoval {902				nft_id,903				resource_id,904			});905			Ok(())906		}907	}908}909910impl<T: Config> Pallet<T> {911	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {912		let key = rmrk_key.to_key::<T>()?;913914		let scoped_key = PropertyScope::Rmrk915			.apply(key)916			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;917918		Ok(scoped_key)919	}920921	// todo think about renaming these922	pub fn rmrk_property<E: Encode>(923		rmrk_key: RmrkProperty,924		value: &E,925	) -> Result<Property, DispatchError> {926		let key = rmrk_key.to_key::<T>()?;927928		let value = value929			.encode()930			.try_into()931			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;932933		let property = Property { key, value };934935		Ok(property)936	}937938	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {939		vec.decode()940			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())941	}942943	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>944	where945		BoundedVec<u8, S>: TryFrom<Vec<u8>>,946	{947		vec.rebind()948			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())949	}950951	fn init_collection(952		sender: T::CrossAccountId,953		data: CreateCollectionData<T::AccountId>,954		properties: impl Iterator<Item = Property>,955	) -> Result<CollectionId, DispatchError> {956		let collection_id = <PalletNft<T>>::init_collection(sender, data);957958		if let Err(DispatchError::Arithmetic(_)) = &collection_id {959			return Err(<Error<T>>::NoAvailableCollectionId.into());960		}961962		<PalletCommon<T>>::set_scoped_collection_properties(963			collection_id?,964			PropertyScope::Rmrk,965			properties,966		)?;967968		collection_id969	}970971	pub fn create_nft(972		sender: &T::CrossAccountId,973		owner: &T::CrossAccountId,974		collection: &NonfungibleHandle<T>,975		properties: impl Iterator<Item = Property>,976	) -> Result<TokenId, DispatchError> {977		let data = CreateNftExData {978			properties: BoundedVec::default(),979			owner: owner.clone(),980		};981982		let budget = budget::Value::new(NESTING_BUDGET);983984		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;985986		let nft_id = <PalletNft<T>>::current_token_id(collection.id);987988		<PalletNft<T>>::set_scoped_token_properties(989			collection.id,990			nft_id,991			PropertyScope::Rmrk,992			properties,993		)?;994995		Ok(nft_id)996	}997998	fn destroy_nft(999		sender: T::CrossAccountId,1000		collection_id: CollectionId,1001		token_id: TokenId,1002	) -> DispatchResult {1003		let collection =1004			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10051006		let token_data =1007			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10081009		let from = token_data.owner;10101011		let budget = budget::Value::new(NESTING_BUDGET);10121013		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1014	}10151016	fn resource_add(1017		sender: T::AccountId,1018		collection_id: CollectionId,1019		token_id: TokenId,1020		resource_properties: impl Iterator<Item = Property>,1021	) -> Result<RmrkResourceId, DispatchError> {1022		let collection =1023			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1024		ensure!(collection.owner == sender, Error::<T>::NoPermission);10251026		let sender = T::CrossAccountId::from_sub(sender);1027		let budget = budget::Value::new(NESTING_BUDGET);10281029		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1030			.map_err(Self::map_unique_err_to_proxy)?;10311032		let pending = sender != nft_owner;10331034		let resource_collection_id: CollectionId =1035			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1036		let resource_collection =1037			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10381039		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10401041		let resource_id = Self::create_nft(1042			&sender,1043			&nft_owner,1044			&resource_collection,1045			resource_properties.chain(1046				[1047					Self::rmrk_property(PendingResourceAccept, &pending)?,1048					Self::rmrk_property(PendingResourceRemoval, &false)?,1049				]1050				.into_iter(),1051			),1052		)1053		.map_err(|err| match err {1054			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1055			err => Self::map_unique_err_to_proxy(err),1056		})?;10571058		Ok(resource_id.0)1059	}10601061	fn resource_remove(1062		sender: T::AccountId,1063		collection_id: CollectionId,1064		nft_id: TokenId,1065		resource_id: TokenId,1066	) -> DispatchResult {1067		let collection =1068			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1069		ensure!(collection.owner == sender, Error::<T>::NoPermission);10701071		let resource_collection_id: CollectionId =1072			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1073		let resource_collection =1074			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1075		ensure!(1076			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1077			Error::<T>::ResourceDoesntExist1078		);10791080		let budget = up_data_structs::budget::Value::new(10);1081		let topmost_owner =1082			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10831084		let sender = T::CrossAccountId::from_sub(sender);1085		if topmost_owner == sender {1086			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1087				.map_err(Self::map_unique_err_to_proxy)?;1088		} else {1089			<PalletNft<T>>::set_scoped_token_property(1090				resource_collection_id,1091				resource_id,1092				PropertyScope::Rmrk,1093				Self::rmrk_property(PendingResourceRemoval, &true)?,1094			)?;1095		}10961097		Ok(())1098	}10991100	fn change_collection_owner(1101		collection_id: CollectionId,1102		collection_type: misc::CollectionType,1103		sender: T::AccountId,1104		new_owner: T::AccountId,1105	) -> DispatchResult {1106		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1107		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11081109		let mut collection = collection.into_inner();11101111		collection.owner = new_owner;1112		collection.save()1113	}11141115	fn check_collection_owner(1116		collection: &NonfungibleHandle<T>,1117		account: &T::CrossAccountId,1118	) -> DispatchResult {1119		collection1120			.check_is_owner(account)1121			.map_err(Self::map_unique_err_to_proxy)1122	}11231124	pub fn last_collection_idx() -> RmrkCollectionId {1125		<CollectionIndex<T>>::get()1126	}11271128	pub fn unique_collection_id(1129		rmrk_collection_id: RmrkCollectionId,1130	) -> Result<CollectionId, DispatchError> {1131		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1132			.map_err(|_| <Error<T>>::CollectionUnknown.into())1133	}11341135	pub fn rmrk_collection_id(1136		unique_collection_id: CollectionId,1137	) -> Result<RmrkCollectionId, DispatchError> {1138		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1139			.map_err(|_| <Error<T>>::CollectionUnknown.into())1140	}11411142	pub fn get_nft_collection(1143		collection_id: CollectionId,1144	) -> Result<NonfungibleHandle<T>, DispatchError> {1145		let collection = <CollectionHandle<T>>::try_get(collection_id)1146			.map_err(|_| <Error<T>>::CollectionUnknown)?;11471148		match collection.mode {1149			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1150			_ => Err(<Error<T>>::CollectionUnknown.into()),1151		}1152	}11531154	pub fn collection_exists(collection_id: CollectionId) -> bool {1155		<CollectionHandle<T>>::try_get(collection_id).is_ok()1156	}11571158	pub fn get_collection_property(1159		collection_id: CollectionId,1160		key: RmrkProperty,1161	) -> Result<PropertyValue, DispatchError> {1162		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1163			.get(&Self::rmrk_property_key(key)?)1164			.ok_or(<Error<T>>::CollectionUnknown)?1165			.clone();11661167		Ok(collection_property)1168	}11691170	pub fn get_collection_property_decoded<V: Decode>(1171		collection_id: CollectionId,1172		key: RmrkProperty,1173	) -> Result<V, DispatchError> {1174		Self::decode_property(Self::get_collection_property(collection_id, key)?)1175	}11761177	pub fn get_collection_type(1178		collection_id: CollectionId,1179	) -> Result<misc::CollectionType, DispatchError> {1180		Self::get_collection_property_decoded(collection_id, CollectionType)1181			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1182	}11831184	pub fn ensure_collection_type(1185		collection_id: CollectionId,1186		collection_type: misc::CollectionType,1187	) -> DispatchResult {1188		let actual_type = Self::get_collection_type(collection_id)?;1189		ensure!(1190			actual_type == collection_type,1191			<CommonError<T>>::NoPermission1192		);11931194		Ok(())1195	}11961197	pub fn get_typed_nft_collection(1198		collection_id: CollectionId,1199		collection_type: misc::CollectionType,1200	) -> Result<NonfungibleHandle<T>, DispatchError> {1201		Self::ensure_collection_type(collection_id, collection_type)?;12021203		Self::get_nft_collection(collection_id)1204	}12051206	pub fn get_typed_nft_collection_mapped(1207		rmrk_collection_id: RmrkCollectionId,1208		collection_type: misc::CollectionType,1209	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1210		let unique_collection_id = match collection_type {1211			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1212			_ => rmrk_collection_id.into(),1213		};12141215		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12161217		Ok((collection, unique_collection_id))1218	}12191220	pub fn get_nft_property(1221		collection_id: CollectionId,1222		nft_id: TokenId,1223		key: RmrkProperty,1224	) -> Result<PropertyValue, DispatchError> {1225		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1226			.get(&Self::rmrk_property_key(key)?)1227			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1228			.clone();12291230		Ok(nft_property)1231	}12321233	pub fn get_nft_property_decoded<V: Decode>(1234		collection_id: CollectionId,1235		nft_id: TokenId,1236		key: RmrkProperty,1237	) -> Result<V, DispatchError> {1238		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1239	}12401241	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1242		<TokenData<T>>::contains_key((collection_id, nft_id))1243	}12441245	pub fn get_nft_type(1246		collection_id: CollectionId,1247		token_id: TokenId,1248	) -> Result<NftType, DispatchError> {1249		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1250			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1251	}12521253	pub fn ensure_nft_type(1254		collection_id: CollectionId,1255		token_id: TokenId,1256		nft_type: NftType,1257	) -> DispatchResult {1258		let actual_type = Self::get_nft_type(collection_id, token_id)?;1259		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12601261		Ok(())1262	}12631264	pub fn ensure_nft_owner(1265		collection_id: CollectionId,1266		token_id: TokenId,1267		possible_owner: &T::CrossAccountId,1268		nesting_budget: &dyn budget::Budget,1269	) -> DispatchResult {1270		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1271			possible_owner.clone(),1272			collection_id,1273			token_id,1274			None,1275			nesting_budget,1276		)1277		.map_err(Self::map_unique_err_to_proxy)?;12781279		ensure!(is_owned, <Error<T>>::NoPermission);12801281		Ok(())1282	}12831284	pub fn filter_user_properties<Key, Value, R, Mapper>(1285		collection_id: CollectionId,1286		token_id: Option<TokenId>,1287		filter_keys: Option<Vec<RmrkPropertyKey>>,1288		mapper: Mapper,1289	) -> Result<Vec<R>, DispatchError>1290	where1291		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1292		Value: Decode + Default,1293		Mapper: Fn(Key, Value) -> R,1294	{1295		filter_keys1296			.map(|keys| {1297				let properties = keys1298					.into_iter()1299					.filter_map(|key| {1300						let key: Key = key.try_into().ok()?;13011302						let value = match token_id {1303							Some(token_id) => Self::get_nft_property_decoded(1304								collection_id,1305								token_id,1306								UserProperty(key.as_ref()),1307							),1308							None => Self::get_collection_property_decoded(1309								collection_id,1310								UserProperty(key.as_ref()),1311							),1312						}1313						.ok()?;13141315						Some(mapper(key, value))1316					})1317					.collect();13181319				Ok(properties)1320			})1321			.unwrap_or_else(|| {1322				let properties =1323					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13241325				Ok(properties)1326			})1327	}13281329	pub fn iterate_user_properties<Key, Value, R, Mapper>(1330		collection_id: CollectionId,1331		token_id: Option<TokenId>,1332		mapper: Mapper,1333	) -> Result<impl Iterator<Item = R>, DispatchError>1334	where1335		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1336		Value: Decode + Default,1337		Mapper: Fn(Key, Value) -> R,1338	{1339		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13401341		let properties = match token_id {1342			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1343			None => <PalletCommon<T>>::collection_properties(collection_id),1344		};13451346		let properties = properties.into_iter().filter_map(move |(key, value)| {1347			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13481349			let key: Key = key.to_vec().try_into().ok()?;1350			let value: Value = value.decode().ok()?;13511352			Some(mapper(key, value))1353		});13541355		Ok(properties)1356	}13571358	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1359		map_unique_err_to_proxy! {1360			match err {1361				CommonError::NoPermission => NoPermission,1362				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1363				CommonError::PublicMintingNotAllowed => NoPermission,1364				CommonError::TokenNotFound => NoAvailableNftId,1365				CommonError::ApprovedValueTooLow => NoPermission,1366				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1367				StructureError::TokenNotFound => NoAvailableNftId,1368				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1369			}1370		}1371	}1372}
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		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141		PrioritySet {142			collection_id: RmrkCollectionId,143			nft_id: RmrkNftId,144		},145	}146147	#[pallet::error]148	pub enum Error<T> {149		/* Unique-specific events */150		CorruptedCollectionType,151		NftTypeEncodeError,152		RmrkPropertyKeyIsTooLong,153		RmrkPropertyValueIsTooLong,154155		/* RMRK compatible events */156		CollectionNotEmpty,157		NoAvailableCollectionId,158		NoAvailableNftId,159		CollectionUnknown,160		NoPermission,161		NonTransferable,162		CollectionFullOrLocked,163		ResourceDoesntExist,164		CannotSendToDescendentOrSelf,165		CannotAcceptNonOwnedNft,166		CannotRejectNonOwnedNft,167		ResourceNotPending,168	}169170	#[pallet::call]171	impl<T: Config> Pallet<T> {172		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]173		#[transactional]174		pub fn create_collection(175			origin: OriginFor<T>,176			metadata: RmrkString,177			max: Option<u32>,178			symbol: RmrkCollectionSymbol,179		) -> DispatchResult {180			let sender = ensure_signed(origin)?;181182			let limits = CollectionLimits {183				owner_can_transfer: Some(false),184				token_limit: max,185				..Default::default()186			};187188			let data = CreateCollectionData {189				limits: Some(limits),190				token_prefix: symbol191					.into_inner()192					.try_into()193					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,194				permissions: Some(CollectionPermissions {195					nesting: Some(NestingRule::Owner),196					..Default::default()197				}),198				..Default::default()199			};200201			let unique_collection_id = Self::init_collection(202				T::CrossAccountId::from_sub(sender.clone()),203				data,204				[205					Self::rmrk_property(Metadata, &metadata)?,206					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,207				]208				.into_iter(),209			)?;210			let rmrk_collection_id = <CollectionIndex<T>>::get();211212			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);213			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);214215			<CollectionIndex<T>>::mutate(|n| *n += 1);216217			Self::deposit_event(Event::CollectionCreated {218				issuer: sender,219				collection_id: rmrk_collection_id,220			});221222			Ok(())223		}224225		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]226		#[transactional]227		pub fn destroy_collection(228			origin: OriginFor<T>,229			collection_id: RmrkCollectionId,230		) -> DispatchResult {231			let sender = ensure_signed(origin)?;232			let cross_sender = T::CrossAccountId::from_sub(sender.clone());233234			let collection = Self::get_typed_nft_collection(235				Self::unique_collection_id(collection_id)?,236				misc::CollectionType::Regular,237			)?;238239			<PalletNft<T>>::destroy_collection(collection, &cross_sender)240				.map_err(Self::map_unique_err_to_proxy)?;241242			Self::deposit_event(Event::CollectionDestroyed {243				issuer: sender,244				collection_id,245			});246247			Ok(())248		}249250		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251		#[transactional]252		pub fn change_collection_issuer(253			origin: OriginFor<T>,254			collection_id: RmrkCollectionId,255			new_issuer: <T::Lookup as StaticLookup>::Source,256		) -> DispatchResult {257			let sender = ensure_signed(origin)?;258259			let new_issuer = T::Lookup::lookup(new_issuer)?;260261			Self::change_collection_owner(262				Self::unique_collection_id(collection_id)?,263				misc::CollectionType::Regular,264				sender.clone(),265				new_issuer.clone(),266			)?;267268			Self::deposit_event(Event::IssuerChanged {269				old_issuer: sender,270				new_issuer,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn lock_collection(280			origin: OriginFor<T>,281			collection_id: RmrkCollectionId,282		) -> DispatchResult {283			let sender = ensure_signed(origin)?;284			let cross_sender = T::CrossAccountId::from_sub(sender.clone());285286			let collection = Self::get_typed_nft_collection(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289			)?;290291			Self::check_collection_owner(&collection, &cross_sender)?;292293			let token_count = collection.total_supply();294295			let mut collection = collection.into_inner();296			collection.limits.token_limit = Some(token_count);297			collection.save()?;298299			Self::deposit_event(Event::CollectionLocked {300				issuer: sender,301				collection_id,302			});303304			Ok(())305		}306307		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]308		#[transactional]309		pub fn mint_nft(310			origin: OriginFor<T>,311			owner: T::AccountId,312			collection_id: RmrkCollectionId,313			recipient: Option<T::AccountId>,314			royalty_amount: Option<Permill>,315			metadata: RmrkString,316			transferable: bool,317		) -> DispatchResult {318			let sender = ensure_signed(origin)?;319			let sender = T::CrossAccountId::from_sub(sender);320			let cross_owner = T::CrossAccountId::from_sub(owner.clone());321322			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {323				recipient: recipient.unwrap_or_else(|| owner.clone()),324				amount,325			});326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331332			let nft_id = Self::create_nft(333				&sender,334				&cross_owner,335				&collection,336				[337					Self::rmrk_property(TokenType, &NftType::Regular)?,338					Self::rmrk_property(Transferable, &transferable)?,339					Self::rmrk_property(PendingNftAccept, &false)?,340					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,341					Self::rmrk_property(Metadata, &metadata)?,342					Self::rmrk_property(Equipped, &false)?,343					Self::rmrk_property(344						ResourceCollection,345						&Self::init_collection(346							sender.clone(),347							CreateCollectionData {348								..Default::default()349							},350							[Self::rmrk_property(351								CollectionType,352								&misc::CollectionType::Resource,353							)?]354							.into_iter(),355						)?,356					)?, // todo possibly add limits to the collection if rmrk warrants them357					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,358				]359				.into_iter(),360			)361			.map_err(|err| match err {362				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),363				err => Self::map_unique_err_to_proxy(err),364			})?;365366			Self::deposit_event(Event::NftMinted {367				owner,368				collection_id,369				nft_id: nft_id.0,370			});371372			Ok(())373		}374375		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]376		#[transactional]377		pub fn burn_nft(378			origin: OriginFor<T>,379			collection_id: RmrkCollectionId,380			nft_id: RmrkNftId,381		) -> DispatchResult {382			let sender = ensure_signed(origin)?;383			let cross_sender = T::CrossAccountId::from_sub(sender.clone());384385			Self::destroy_nft(386				cross_sender,387				Self::unique_collection_id(collection_id)?,388				nft_id.into(),389			)390			.map_err(Self::map_unique_err_to_proxy)?;391392			Self::deposit_event(Event::NFTBurned {393				owner: sender,394				nft_id,395			});396397			Ok(())398		}399400		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]401		#[transactional]402		pub fn send(403			origin: OriginFor<T>,404			rmrk_collection_id: RmrkCollectionId,405			rmrk_nft_id: RmrkNftId,406			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,407		) -> DispatchResult {408			let sender = ensure_signed(origin.clone())?;409			let cross_sender = T::CrossAccountId::from_sub(sender.clone());410411			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;412			let nft_id = rmrk_nft_id.into();413414			let token_data =415				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;416417			let from = token_data.owner;418419			let collection =420				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;421422			ensure!(423				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,424				<Error<T>>::NonTransferable425			);426427			ensure!(428				!Self::get_nft_property_decoded(429					collection_id,430					nft_id,431					RmrkProperty::PendingNftAccept432				)?,433				<Error<T>>::NoPermission434			);435436			let target_owner;437			let approval_required;438439			match new_owner {440				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {441					target_owner = T::CrossAccountId::from_sub(account_id.clone());442					approval_required = false;443				}444				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(445					target_collection_id,446					target_nft_id,447				) => {448					let target_collection_id = Self::unique_collection_id(target_collection_id)?;449450					let target_nft_budget = budget::Value::new(NESTING_BUDGET);451452					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(453						target_collection_id,454						target_nft_id.into(),455						Some((collection_id, nft_id)),456						&target_nft_budget,457					)458					.map_err(Self::map_unique_err_to_proxy)?;459460					approval_required = cross_sender != target_nft_owner;461462					if approval_required {463						target_owner = target_nft_owner;464465						<PalletNft<T>>::set_scoped_token_property(466							collection.id,467							nft_id,468							PropertyScope::Rmrk,469							Self::rmrk_property(PendingNftAccept, &approval_required)?,470						)?;471					} else {472						target_owner = T::CrossTokenAddressMapping::token_to_address(473							target_collection_id,474							target_nft_id.into(),475						);476					}477				}478			}479480			let src_nft_budget = budget::Value::new(NESTING_BUDGET);481482			<PalletNft<T>>::transfer_from(483				&collection,484				&cross_sender,485				&from,486				&target_owner,487				nft_id,488				&src_nft_budget,489			)490			.map_err(Self::map_unique_err_to_proxy)?;491492			Self::deposit_event(Event::NFTSent {493				sender,494				recipient: new_owner,495				collection_id: rmrk_collection_id,496				nft_id: rmrk_nft_id,497				approval_required,498			});499500			Ok(())501		}502503		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]504		#[transactional]505		pub fn accept_nft(506			origin: OriginFor<T>,507			rmrk_collection_id: RmrkCollectionId,508			rmrk_nft_id: RmrkNftId,509			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,510		) -> DispatchResult {511			let sender = ensure_signed(origin.clone())?;512			let cross_sender = T::CrossAccountId::from_sub(sender.clone());513514			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;515			let nft_id = rmrk_nft_id.into();516517			let collection =518				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;519520			let new_cross_owner = match new_owner {521				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {522					T::CrossAccountId::from_sub(account_id.clone())523				}524				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(525					target_collection_id,526					target_nft_id,527				) => {528					let target_collection_id = Self::unique_collection_id(target_collection_id)?;529530					T::CrossTokenAddressMapping::token_to_address(531						target_collection_id,532						TokenId(target_nft_id),533					)534				}535			};536537			let budget = budget::Value::new(NESTING_BUDGET);538539			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)540				.map_err(|err| {541					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {542						<Error<T>>::CannotAcceptNonOwnedNft.into()543					} else {544						Self::map_unique_err_to_proxy(err)545					}546				})?;547548			<PalletNft<T>>::set_scoped_token_property(549				collection.id,550				nft_id,551				PropertyScope::Rmrk,552				Self::rmrk_property(PendingNftAccept, &false)?,553			)?;554555			Self::deposit_event(Event::NFTAccepted {556				sender,557				recipient: new_owner,558				collection_id: rmrk_collection_id,559				nft_id: rmrk_nft_id,560			});561562			Ok(())563		}564565		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]566		#[transactional]567		pub fn reject_nft(568			origin: OriginFor<T>,569			rmrk_collection_id: RmrkCollectionId,570			rmrk_nft_id: RmrkNftId,571		) -> DispatchResult {572			let sender = ensure_signed(origin)?;573			let cross_sender = T::CrossAccountId::from_sub(sender.clone());574575			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;576			let nft_id = rmrk_nft_id.into();577578			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {579				if err == <CommonError<T>>::NoPermission.into()580					|| err == <CommonError<T>>::ApprovedValueTooLow.into()581				{582					<Error<T>>::CannotRejectNonOwnedNft.into()583				} else {584					Self::map_unique_err_to_proxy(err)585				}586			})?;587588			Self::deposit_event(Event::NFTRejected {589				sender,590				collection_id: rmrk_collection_id,591				nft_id: rmrk_nft_id,592			});593594			Ok(())595		}596597		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]598		#[transactional]599		pub fn accept_resource(600			origin: OriginFor<T>,601			rmrk_collection_id: RmrkCollectionId,602			rmrk_nft_id: RmrkNftId,603			rmrk_resource_id: RmrkResourceId,604		) -> DispatchResult {605			let sender = ensure_signed(origin)?;606			let cross_sender = T::CrossAccountId::from_sub(sender);607608			let collection_id = Self::unique_collection_id(rmrk_collection_id)609				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;610611			let nft_id = rmrk_nft_id.into();612			let resource_id = rmrk_resource_id.into();613614			let budget = budget::Value::new(NESTING_BUDGET);615616			let nft_owner =617				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)618					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;619620			let resource_collection_id: CollectionId =621				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)622					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;623624			let is_pending: bool = Self::get_nft_property_decoded(625				resource_collection_id,626				resource_id,627				PendingResourceAccept,628			)629			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;630631			ensure!(is_pending, <Error<T>>::ResourceNotPending);632633			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);634635			<PalletNft<T>>::set_scoped_token_property(636				resource_collection_id,637				rmrk_resource_id.into(),638				PropertyScope::Rmrk,639				Self::rmrk_property(PendingResourceAccept, &false)?,640			)?;641642			Self::deposit_event(Event::<T>::ResourceAccepted {643				nft_id: rmrk_nft_id,644				resource_id: rmrk_resource_id,645			});646647			Ok(())648		}649650		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]651		#[transactional]652		pub fn accept_resource_removal(653			origin: OriginFor<T>,654			rmrk_collection_id: RmrkCollectionId,655			rmrk_nft_id: RmrkNftId,656			rmrk_resource_id: RmrkResourceId,657		) -> DispatchResult {658			let sender = ensure_signed(origin)?;659			let cross_sender = T::CrossAccountId::from_sub(sender);660661			let collection_id = Self::unique_collection_id(rmrk_collection_id)662				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;663664			let nft_id = rmrk_nft_id.into();665			let resource_id = rmrk_resource_id.into();666667			let budget = budget::Value::new(NESTING_BUDGET);668669			let nft_owner =670				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)671					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;672673			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);674675			let resource_collection_id: CollectionId =676				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)677					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;678679			let is_pending: bool = Self::get_nft_property_decoded(680				resource_collection_id,681				resource_id,682				PendingResourceRemoval,683			)684			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;685686			ensure!(is_pending, <Error<T>>::ResourceNotPending);687688			let resource_collection = Self::get_typed_nft_collection(689				resource_collection_id,690				misc::CollectionType::Resource,691			)?;692693			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())694				.map_err(Self::map_unique_err_to_proxy)?;695696			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {697				nft_id: rmrk_nft_id,698				resource_id: rmrk_resource_id,699			});700701			Ok(())702		}703704		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]705		#[transactional]706		pub fn set_property(707			origin: OriginFor<T>,708			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,709			maybe_nft_id: Option<RmrkNftId>,710			key: RmrkKeyString,711			value: RmrkValueString,712		) -> DispatchResult {713			let sender = ensure_signed(origin)?;714			let sender = T::CrossAccountId::from_sub(sender);715716			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;717			let budget = budget::Value::new(NESTING_BUDGET);718719			match maybe_nft_id {720				Some(nft_id) => {721					let token_id: TokenId = nft_id.into();722723					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;724					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;725726					<PalletNft<T>>::set_scoped_token_property(727						collection_id,728						token_id,729						PropertyScope::Rmrk,730						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,731					)?;732				}733				None => {734					let collection = Self::get_typed_nft_collection(735						collection_id,736						misc::CollectionType::Regular,737					)?;738739					Self::check_collection_owner(&collection, &sender)?;740741					<PalletCommon<T>>::set_scoped_collection_property(742						collection_id,743						PropertyScope::Rmrk,744						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,745					)?;746				}747			}748749			Self::deposit_event(Event::PropertySet {750				collection_id: rmrk_collection_id,751				maybe_nft_id,752				key,753				value,754			});755756			Ok(())757		}758759		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]760		#[transactional]761		pub fn set_priority(762			origin: OriginFor<T>,763			rmrk_collection_id: RmrkCollectionId,764			rmrk_nft_id: RmrkNftId,765			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,766		) -> DispatchResult {767			let sender = ensure_signed(origin)?;768			let sender = T::CrossAccountId::from_sub(sender);769770			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;771			let nft_id = rmrk_nft_id.into();772			let budget = budget::Value::new(NESTING_BUDGET);773774			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;775			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;776777			<PalletNft<T>>::set_scoped_token_property(778				collection_id,779				nft_id,780				PropertyScope::Rmrk,781				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,782			)?;783784			Self::deposit_event(Event::<T>::PrioritySet {785				collection_id: rmrk_collection_id,786				nft_id: rmrk_nft_id,787			});788789			Ok(())790		}791792		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]793		#[transactional]794		pub fn add_basic_resource(795			origin: OriginFor<T>,796			collection_id: RmrkCollectionId,797			nft_id: RmrkNftId,798			resource: RmrkBasicResource,799		) -> DispatchResult {800			let sender = ensure_signed(origin.clone())?;801802			let resource_id = Self::resource_add(803				sender,804				Self::unique_collection_id(collection_id)?,805				nft_id.into(),806				[807					Self::rmrk_property(TokenType, &NftType::Resource)?,808					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,809					Self::rmrk_property(Src, &resource.src)?,810					Self::rmrk_property(Metadata, &resource.metadata)?,811					Self::rmrk_property(License, &resource.license)?,812					Self::rmrk_property(Thumb, &resource.thumb)?,813				]814				.into_iter(),815			)?;816817			Self::deposit_event(Event::ResourceAdded {818				nft_id,819				resource_id,820			});821			Ok(())822		}823824		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]825		#[transactional]826		pub fn add_composable_resource(827			origin: OriginFor<T>,828			collection_id: RmrkCollectionId,829			nft_id: RmrkNftId,830			_resource_id: RmrkBoundedResource,831			resource: RmrkComposableResource,832		) -> DispatchResult {833			let sender = ensure_signed(origin.clone())?;834835			let resource_id = Self::resource_add(836				sender,837				Self::unique_collection_id(collection_id)?,838				nft_id.into(),839				[840					Self::rmrk_property(TokenType, &NftType::Resource)?,841					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,842					Self::rmrk_property(Parts, &resource.parts)?,843					Self::rmrk_property(Base, &resource.base)?,844					Self::rmrk_property(Src, &resource.src)?,845					Self::rmrk_property(Metadata, &resource.metadata)?,846					Self::rmrk_property(License, &resource.license)?,847					Self::rmrk_property(Thumb, &resource.thumb)?,848				]849				.into_iter(),850			)?;851852			Self::deposit_event(Event::ResourceAdded {853				nft_id,854				resource_id,855			});856			Ok(())857		}858859		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]860		#[transactional]861		pub fn add_slot_resource(862			origin: OriginFor<T>,863			collection_id: RmrkCollectionId,864			nft_id: RmrkNftId,865			resource: RmrkSlotResource,866		) -> DispatchResult {867			let sender = ensure_signed(origin.clone())?;868869			let resource_id = Self::resource_add(870				sender,871				Self::unique_collection_id(collection_id)?,872				nft_id.into(),873				[874					Self::rmrk_property(TokenType, &NftType::Resource)?,875					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,876					Self::rmrk_property(Base, &resource.base)?,877					Self::rmrk_property(Src, &resource.src)?,878					Self::rmrk_property(Metadata, &resource.metadata)?,879					Self::rmrk_property(Slot, &resource.slot)?,880					Self::rmrk_property(License, &resource.license)?,881					Self::rmrk_property(Thumb, &resource.thumb)?,882				]883				.into_iter(),884			)?;885886			Self::deposit_event(Event::ResourceAdded {887				nft_id,888				resource_id,889			});890			Ok(())891		}892893		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]894		#[transactional]895		pub fn remove_resource(896			origin: OriginFor<T>,897			collection_id: RmrkCollectionId,898			nft_id: RmrkNftId,899			resource_id: RmrkResourceId,900		) -> DispatchResult {901			let sender = ensure_signed(origin.clone())?;902903			Self::resource_remove(904				sender,905				Self::unique_collection_id(collection_id)?,906				nft_id.into(),907				resource_id.into(),908			)?;909910			Self::deposit_event(Event::ResourceRemoval {911				nft_id,912				resource_id,913			});914			Ok(())915		}916	}917}918919impl<T: Config> Pallet<T> {920	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {921		let key = rmrk_key.to_key::<T>()?;922923		let scoped_key = PropertyScope::Rmrk924			.apply(key)925			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;926927		Ok(scoped_key)928	}929930	// todo think about renaming these931	pub fn rmrk_property<E: Encode>(932		rmrk_key: RmrkProperty,933		value: &E,934	) -> Result<Property, DispatchError> {935		let key = rmrk_key.to_key::<T>()?;936937		let value = value938			.encode()939			.try_into()940			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;941942		let property = Property { key, value };943944		Ok(property)945	}946947	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {948		vec.decode()949			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())950	}951952	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>953	where954		BoundedVec<u8, S>: TryFrom<Vec<u8>>,955	{956		vec.rebind()957			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())958	}959960	fn init_collection(961		sender: T::CrossAccountId,962		data: CreateCollectionData<T::AccountId>,963		properties: impl Iterator<Item = Property>,964	) -> Result<CollectionId, DispatchError> {965		let collection_id = <PalletNft<T>>::init_collection(sender, data);966967		if let Err(DispatchError::Arithmetic(_)) = &collection_id {968			return Err(<Error<T>>::NoAvailableCollectionId.into());969		}970971		<PalletCommon<T>>::set_scoped_collection_properties(972			collection_id?,973			PropertyScope::Rmrk,974			properties,975		)?;976977		collection_id978	}979980	pub fn create_nft(981		sender: &T::CrossAccountId,982		owner: &T::CrossAccountId,983		collection: &NonfungibleHandle<T>,984		properties: impl Iterator<Item = Property>,985	) -> Result<TokenId, DispatchError> {986		let data = CreateNftExData {987			properties: BoundedVec::default(),988			owner: owner.clone(),989		};990991		let budget = budget::Value::new(NESTING_BUDGET);992993		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;994995		let nft_id = <PalletNft<T>>::current_token_id(collection.id);996997		<PalletNft<T>>::set_scoped_token_properties(998			collection.id,999			nft_id,1000			PropertyScope::Rmrk,1001			properties,1002		)?;10031004		Ok(nft_id)1005	}10061007	fn destroy_nft(1008		sender: T::CrossAccountId,1009		collection_id: CollectionId,1010		token_id: TokenId,1011	) -> DispatchResult {1012		let collection =1013			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10141015		let token_data =1016			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10171018		let from = token_data.owner;10191020		let budget = budget::Value::new(NESTING_BUDGET);10211022		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1023	}10241025	fn resource_add(1026		sender: T::AccountId,1027		collection_id: CollectionId,1028		token_id: TokenId,1029		resource_properties: impl Iterator<Item = Property>,1030	) -> Result<RmrkResourceId, DispatchError> {1031		let collection =1032			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1033		ensure!(collection.owner == sender, Error::<T>::NoPermission);10341035		let sender = T::CrossAccountId::from_sub(sender);1036		let budget = budget::Value::new(NESTING_BUDGET);10371038		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1039			.map_err(Self::map_unique_err_to_proxy)?;10401041		let pending = sender != nft_owner;10421043		let resource_collection_id: CollectionId =1044			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1045		let resource_collection =1046			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10471048		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10491050		let resource_id = Self::create_nft(1051			&sender,1052			&nft_owner,1053			&resource_collection,1054			resource_properties.chain(1055				[1056					Self::rmrk_property(PendingResourceAccept, &pending)?,1057					Self::rmrk_property(PendingResourceRemoval, &false)?,1058				]1059				.into_iter(),1060			),1061		)1062		.map_err(|err| match err {1063			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1064			err => Self::map_unique_err_to_proxy(err),1065		})?;10661067		Ok(resource_id.0)1068	}10691070	fn resource_remove(1071		sender: T::AccountId,1072		collection_id: CollectionId,1073		nft_id: TokenId,1074		resource_id: TokenId,1075	) -> DispatchResult {1076		let collection =1077			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1078		ensure!(collection.owner == sender, Error::<T>::NoPermission);10791080		let resource_collection_id: CollectionId =1081			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1082		let resource_collection =1083			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1084		ensure!(1085			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1086			Error::<T>::ResourceDoesntExist1087		);10881089		let budget = up_data_structs::budget::Value::new(10);1090		let topmost_owner =1091			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10921093		let sender = T::CrossAccountId::from_sub(sender);1094		if topmost_owner == sender {1095			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1096				.map_err(Self::map_unique_err_to_proxy)?;1097		} else {1098			<PalletNft<T>>::set_scoped_token_property(1099				resource_collection_id,1100				resource_id,1101				PropertyScope::Rmrk,1102				Self::rmrk_property(PendingResourceRemoval, &true)?,1103			)?;1104		}11051106		Ok(())1107	}11081109	fn change_collection_owner(1110		collection_id: CollectionId,1111		collection_type: misc::CollectionType,1112		sender: T::AccountId,1113		new_owner: T::AccountId,1114	) -> DispatchResult {1115		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1116		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11171118		let mut collection = collection.into_inner();11191120		collection.owner = new_owner;1121		collection.save()1122	}11231124	fn check_collection_owner(1125		collection: &NonfungibleHandle<T>,1126		account: &T::CrossAccountId,1127	) -> DispatchResult {1128		collection1129			.check_is_owner(account)1130			.map_err(Self::map_unique_err_to_proxy)1131	}11321133	pub fn last_collection_idx() -> RmrkCollectionId {1134		<CollectionIndex<T>>::get()1135	}11361137	pub fn unique_collection_id(1138		rmrk_collection_id: RmrkCollectionId,1139	) -> Result<CollectionId, DispatchError> {1140		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1141			.map_err(|_| <Error<T>>::CollectionUnknown.into())1142	}11431144	pub fn rmrk_collection_id(1145		unique_collection_id: CollectionId,1146	) -> Result<RmrkCollectionId, DispatchError> {1147		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1148			.map_err(|_| <Error<T>>::CollectionUnknown.into())1149	}11501151	pub fn get_nft_collection(1152		collection_id: CollectionId,1153	) -> Result<NonfungibleHandle<T>, DispatchError> {1154		let collection = <CollectionHandle<T>>::try_get(collection_id)1155			.map_err(|_| <Error<T>>::CollectionUnknown)?;11561157		match collection.mode {1158			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1159			_ => Err(<Error<T>>::CollectionUnknown.into()),1160		}1161	}11621163	pub fn collection_exists(collection_id: CollectionId) -> bool {1164		<CollectionHandle<T>>::try_get(collection_id).is_ok()1165	}11661167	pub fn get_collection_property(1168		collection_id: CollectionId,1169		key: RmrkProperty,1170	) -> Result<PropertyValue, DispatchError> {1171		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1172			.get(&Self::rmrk_property_key(key)?)1173			.ok_or(<Error<T>>::CollectionUnknown)?1174			.clone();11751176		Ok(collection_property)1177	}11781179	pub fn get_collection_property_decoded<V: Decode>(1180		collection_id: CollectionId,1181		key: RmrkProperty,1182	) -> Result<V, DispatchError> {1183		Self::decode_property(Self::get_collection_property(collection_id, key)?)1184	}11851186	pub fn get_collection_type(1187		collection_id: CollectionId,1188	) -> Result<misc::CollectionType, DispatchError> {1189		Self::get_collection_property_decoded(collection_id, CollectionType)1190			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1191	}11921193	pub fn ensure_collection_type(1194		collection_id: CollectionId,1195		collection_type: misc::CollectionType,1196	) -> DispatchResult {1197		let actual_type = Self::get_collection_type(collection_id)?;1198		ensure!(1199			actual_type == collection_type,1200			<CommonError<T>>::NoPermission1201		);12021203		Ok(())1204	}12051206	pub fn get_typed_nft_collection(1207		collection_id: CollectionId,1208		collection_type: misc::CollectionType,1209	) -> Result<NonfungibleHandle<T>, DispatchError> {1210		Self::ensure_collection_type(collection_id, collection_type)?;12111212		Self::get_nft_collection(collection_id)1213	}12141215	pub fn get_typed_nft_collection_mapped(1216		rmrk_collection_id: RmrkCollectionId,1217		collection_type: misc::CollectionType,1218	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1219		let unique_collection_id = match collection_type {1220			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1221			_ => rmrk_collection_id.into(),1222		};12231224		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12251226		Ok((collection, unique_collection_id))1227	}12281229	pub fn get_nft_property(1230		collection_id: CollectionId,1231		nft_id: TokenId,1232		key: RmrkProperty,1233	) -> Result<PropertyValue, DispatchError> {1234		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1235			.get(&Self::rmrk_property_key(key)?)1236			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1237			.clone();12381239		Ok(nft_property)1240	}12411242	pub fn get_nft_property_decoded<V: Decode>(1243		collection_id: CollectionId,1244		nft_id: TokenId,1245		key: RmrkProperty,1246	) -> Result<V, DispatchError> {1247		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1248	}12491250	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1251		<TokenData<T>>::contains_key((collection_id, nft_id))1252	}12531254	pub fn get_nft_type(1255		collection_id: CollectionId,1256		token_id: TokenId,1257	) -> Result<NftType, DispatchError> {1258		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1259			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1260	}12611262	pub fn ensure_nft_type(1263		collection_id: CollectionId,1264		token_id: TokenId,1265		nft_type: NftType,1266	) -> DispatchResult {1267		let actual_type = Self::get_nft_type(collection_id, token_id)?;1268		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12691270		Ok(())1271	}12721273	pub fn ensure_nft_owner(1274		collection_id: CollectionId,1275		token_id: TokenId,1276		possible_owner: &T::CrossAccountId,1277		nesting_budget: &dyn budget::Budget,1278	) -> DispatchResult {1279		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1280			possible_owner.clone(),1281			collection_id,1282			token_id,1283			None,1284			nesting_budget,1285		)1286		.map_err(Self::map_unique_err_to_proxy)?;12871288		ensure!(is_owned, <Error<T>>::NoPermission);12891290		Ok(())1291	}12921293	pub fn filter_user_properties<Key, Value, R, Mapper>(1294		collection_id: CollectionId,1295		token_id: Option<TokenId>,1296		filter_keys: Option<Vec<RmrkPropertyKey>>,1297		mapper: Mapper,1298	) -> Result<Vec<R>, DispatchError>1299	where1300		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1301		Value: Decode + Default,1302		Mapper: Fn(Key, Value) -> R,1303	{1304		filter_keys1305			.map(|keys| {1306				let properties = keys1307					.into_iter()1308					.filter_map(|key| {1309						let key: Key = key.try_into().ok()?;13101311						let value = match token_id {1312							Some(token_id) => Self::get_nft_property_decoded(1313								collection_id,1314								token_id,1315								UserProperty(key.as_ref()),1316							),1317							None => Self::get_collection_property_decoded(1318								collection_id,1319								UserProperty(key.as_ref()),1320							),1321						}1322						.ok()?;13231324						Some(mapper(key, value))1325					})1326					.collect();13271328				Ok(properties)1329			})1330			.unwrap_or_else(|| {1331				let properties =1332					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13331334				Ok(properties)1335			})1336	}13371338	pub fn iterate_user_properties<Key, Value, R, Mapper>(1339		collection_id: CollectionId,1340		token_id: Option<TokenId>,1341		mapper: Mapper,1342	) -> Result<impl Iterator<Item = R>, DispatchError>1343	where1344		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1345		Value: Decode + Default,1346		Mapper: Fn(Key, Value) -> R,1347	{1348		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13491350		let properties = match token_id {1351			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1352			None => <PalletCommon<T>>::collection_properties(collection_id),1353		};13541355		let properties = properties.into_iter().filter_map(move |(key, value)| {1356			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13571358			let key: Key = key.to_vec().try_into().ok()?;1359			let value: Value = value.decode().ok()?;13601361			Some(mapper(key, value))1362		});13631364		Ok(properties)1365	}13661367	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1368		map_unique_err_to_proxy! {1369			match err {1370				CommonError::NoPermission => NoPermission,1371				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1372				CommonError::PublicMintingNotAllowed => NoPermission,1373				CommonError::TokenNotFound => NoAvailableNftId,1374				CommonError::ApprovedValueTooLow => NoPermission,1375				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1376				StructureError::TokenNotFound => NoAvailableNftId,1377				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1378			}1379		}1380	}1381}