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

difftreelog

feat(rmrk) add associated bases property

Daniel Shiposha2022-06-30parent: #51e15a0.patch.diff
in: master

2 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{20	pallet_prelude::*,21	transactional,22	BoundedVec,23	dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::btree_set::BTreeSet};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenMap = BTreeSet<PendingChild>;5960#[frame_support::pallet]61pub mod pallet {62	use super::*;63	use pallet_evm::account;6465	#[pallet::config]66	pub trait Config:67		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config68	{69		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;70		type WeightInfo: WeightInfo;71	}7273	#[pallet::storage]74	#[pallet::getter(fn collection_index)]75	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7677	#[pallet::storage]78	pub type UniqueCollectionId<T: Config> =79		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8081	#[pallet::pallet]82	#[pallet::generate_store(pub(super) trait Store)]83	pub struct Pallet<T>(_);8485	#[pallet::event]86	#[pallet::generate_deposit(pub(super) fn deposit_event)]87	pub enum Event<T: Config> {88		CollectionCreated {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		CollectionDestroyed {93			issuer: T::AccountId,94			collection_id: RmrkCollectionId,95		},96		IssuerChanged {97			old_issuer: T::AccountId,98			new_issuer: T::AccountId,99			collection_id: RmrkCollectionId,100		},101		CollectionLocked {102			issuer: T::AccountId,103			collection_id: RmrkCollectionId,104		},105		NftMinted {106			owner: T::AccountId,107			collection_id: RmrkCollectionId,108			nft_id: RmrkNftId,109		},110		NFTBurned {111			owner: T::AccountId,112			nft_id: RmrkNftId,113		},114		NFTSent {115			sender: T::AccountId,116			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,117			collection_id: RmrkCollectionId,118			nft_id: RmrkNftId,119			approval_required: bool,120		},121		NFTAccepted {122			sender: T::AccountId,123			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,124			collection_id: RmrkCollectionId,125			nft_id: RmrkNftId,126		},127		NFTRejected {128			sender: T::AccountId,129			collection_id: RmrkCollectionId,130			nft_id: RmrkNftId,131		},132		PropertySet {133			collection_id: RmrkCollectionId,134			maybe_nft_id: Option<RmrkNftId>,135			key: RmrkKeyString,136			value: RmrkValueString,137		},138		ResourceAdded {139			nft_id: RmrkNftId,140			resource_id: RmrkResourceId,141		},142		ResourceRemoval {143			nft_id: RmrkNftId,144			resource_id: RmrkResourceId,145		},146		ResourceAccepted {147			nft_id: RmrkNftId,148			resource_id: RmrkResourceId,149		},150		ResourceRemovalAccepted {151			nft_id: RmrkNftId,152			resource_id: RmrkResourceId,153		},154		PrioritySet {155			collection_id: RmrkCollectionId,156			nft_id: RmrkNftId,157		},158	}159160	#[pallet::error]161	pub enum Error<T> {162		/* Unique-specific events */163		CorruptedCollectionType,164		NftTypeEncodeError,165		RmrkPropertyKeyIsTooLong,166		RmrkPropertyValueIsTooLong,167		RmrkPropertyIsNotFound,168		UnableToDecodeRmrkData,169170		/* RMRK compatible events */171		CollectionNotEmpty,172		NoAvailableCollectionId,173		NoAvailableNftId,174		CollectionUnknown,175		NoPermission,176		NonTransferable,177		CollectionFullOrLocked,178		ResourceDoesntExist,179		CannotSendToDescendentOrSelf,180		CannotAcceptNonOwnedNft,181		CannotRejectNonOwnedNft,182		CannotRejectNonPendingNft,183		ResourceNotPending,184		NoAvailableResourceId,185	}186187	#[pallet::call]188	impl<T: Config> Pallet<T> {189		/// Create a collection190		#[transactional]191		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]192		pub fn create_collection(193			origin: OriginFor<T>,194			metadata: RmrkString,195			max: Option<u32>,196			symbol: RmrkCollectionSymbol,197		) -> DispatchResult {198			let sender = ensure_signed(origin)?;199200			let limits = CollectionLimits {201				owner_can_transfer: Some(false),202				token_limit: max,203				..Default::default()204			};205206			let data = CreateCollectionData {207				limits: Some(limits),208				token_prefix: symbol209					.into_inner()210					.try_into()211					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,212				permissions: Some(CollectionPermissions {213					nesting: Some(NestingPermissions {214						token_owner: true,215						collection_admin: false,216						restricted: None,217						#[cfg(feature = "runtime-benchmarks")]218						permissive: false,219					}),220					..Default::default()221				}),222				..Default::default()223			};224225			let unique_collection_id = Self::init_collection(226				T::CrossAccountId::from_sub(sender.clone()),227				data,228				[229					Self::rmrk_property(Metadata, &metadata)?,230					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,231				]232				.into_iter(),233			)?;234			let rmrk_collection_id = <CollectionIndex<T>>::get();235236			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);237238			<PalletCommon<T>>::set_scoped_collection_property(239				unique_collection_id,240				PropertyScope::Rmrk,241				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,242			)?;243244			<CollectionIndex<T>>::mutate(|n| *n += 1);245246			Self::deposit_event(Event::CollectionCreated {247				issuer: sender,248				collection_id: rmrk_collection_id,249			});250251			Ok(())252		}253254		/// destroy collection255		#[transactional]256		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]257		pub fn destroy_collection(258			origin: OriginFor<T>,259			collection_id: RmrkCollectionId,260		) -> DispatchResult {261			let sender = ensure_signed(origin)?;262			let cross_sender = T::CrossAccountId::from_sub(sender.clone());263264			let collection = Self::get_typed_nft_collection(265				Self::unique_collection_id(collection_id)?,266				misc::CollectionType::Regular,267			)?;268			collection.check_is_external()?;269270			<PalletNft<T>>::destroy_collection(collection, &cross_sender)271				.map_err(Self::map_unique_err_to_proxy)?;272273			Self::deposit_event(Event::CollectionDestroyed {274				issuer: sender,275				collection_id,276			});277278			Ok(())279		}280281		/// Change the issuer of a collection282		///283		/// Parameters:284		/// - `origin`: sender of the transaction285		/// - `collection_id`: collection id of the nft to change issuer of286		/// - `new_issuer`: Collection's new issuer287		#[transactional]288		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]289		pub fn change_collection_issuer(290			origin: OriginFor<T>,291			collection_id: RmrkCollectionId,292			new_issuer: <T::Lookup as StaticLookup>::Source,293		) -> DispatchResult {294			let sender = ensure_signed(origin)?;295296			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;297			collection.check_is_external()?;298299			let new_issuer = T::Lookup::lookup(new_issuer)?;300301			Self::change_collection_owner(302				Self::unique_collection_id(collection_id)?,303				misc::CollectionType::Regular,304				sender.clone(),305				new_issuer.clone(),306			)?;307308			Self::deposit_event(Event::IssuerChanged {309				old_issuer: sender,310				new_issuer,311				collection_id,312			});313314			Ok(())315		}316317		/// lock collection318		#[transactional]319		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]320		pub fn lock_collection(321			origin: OriginFor<T>,322			collection_id: RmrkCollectionId,323		) -> DispatchResult {324			let sender = ensure_signed(origin)?;325			let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331			collection.check_is_external()?;332333			Self::check_collection_owner(&collection, &cross_sender)?;334335			let token_count = collection.total_supply();336337			let mut collection = collection.into_inner();338			collection.limits.token_limit = Some(token_count);339			collection.save()?;340341			Self::deposit_event(Event::CollectionLocked {342				issuer: sender,343				collection_id,344			});345346			Ok(())347		}348349		/// Mints an NFT in the specified collection350		/// Sets metadata and the royalty attribute351		///352		/// Parameters:353		/// - `collection_id`: The class of the asset to be minted.354		/// - `nft_id`: The nft value of the asset to be minted.355		/// - `recipient`: Receiver of the royalty356		/// - `royalty`: Permillage reward from each trade for the Recipient357		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash358		/// - `transferable`: Ability to transfer this NFT359		#[transactional]360		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]361		pub fn mint_nft(362			origin: OriginFor<T>,363			owner: Option<T::AccountId>,364			collection_id: RmrkCollectionId,365			recipient: Option<T::AccountId>,366			royalty_amount: Option<Permill>,367			metadata: RmrkString,368			transferable: bool,369			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,370		) -> DispatchResult {371			let sender = ensure_signed(origin)?;372			let cross_sender = T::CrossAccountId::from_sub(sender.clone());373374			let owner = owner.unwrap_or(sender.clone());375			let cross_owner = T::CrossAccountId::from_sub(owner.clone());376377			let collection = Self::get_typed_nft_collection(378				Self::unique_collection_id(collection_id)?,379				misc::CollectionType::Regular,380			)?;381			collection.check_is_external()?;382383			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {384				recipient: recipient.unwrap_or_else(|| owner.clone()),385				amount,386			});387388			let nft_id = Self::create_nft(389				&cross_sender,390				&cross_owner,391				&collection,392				[393					Self::rmrk_property(TokenType, &NftType::Regular)?,394					Self::rmrk_property(Transferable, &transferable)?,395					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,396					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,397					Self::rmrk_property(Metadata, &metadata)?,398					Self::rmrk_property(Equipped, &false)?,399					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,400					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,401					Self::rmrk_property(PendingChildren, &PendingChildrenMap::new())?,402				]403				.into_iter(),404			)405			.map_err(|err| match err {406				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),407				err => Self::map_unique_err_to_proxy(err),408			})?;409410			if let Some(resources) = resources {411				for resource in resources {412					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;413				}414			}415416			Self::deposit_event(Event::NftMinted {417				owner,418				collection_id,419				nft_id: nft_id.0,420			});421422			Ok(())423		}424425		/// burn nft426		#[transactional]427		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]428		pub fn burn_nft(429			origin: OriginFor<T>,430			collection_id: RmrkCollectionId,431			nft_id: RmrkNftId,432			max_burns: u32,433		) -> DispatchResult {434			let sender = ensure_signed(origin)?;435			let cross_sender = T::CrossAccountId::from_sub(sender.clone());436437			let collection = Self::get_typed_nft_collection(438				Self::unique_collection_id(collection_id)?,439				misc::CollectionType::Regular,440			)?;441			collection.check_is_external()?;442443			Self::destroy_nft(444				cross_sender,445				Self::unique_collection_id(collection_id)?,446				nft_id.into(),447				max_burns,448				<Error<T>>::NoPermission,449			)450			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;451452			Self::deposit_event(Event::NFTBurned {453				owner: sender,454				nft_id,455			});456457			Ok(())458		}459460		/// Transfers a NFT from an Account or NFT A to another Account or NFT B461		///462		/// Parameters:463		/// - `origin`: sender of the transaction464		/// - `rmrk_collection_id`: collection id of the nft to be transferred465		/// - `rmrk_nft_id`: nft id of the nft to be transferred466		/// - `new_owner`: new owner of the nft which can be either an account or a NFT467		#[transactional]468		#[pallet::weight(<SelfWeightOf<T>>::send())]469		pub fn send(470			origin: OriginFor<T>,471			rmrk_collection_id: RmrkCollectionId,472			rmrk_nft_id: RmrkNftId,473			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,474		) -> DispatchResult {475			let sender = ensure_signed(origin.clone())?;476			let cross_sender = T::CrossAccountId::from_sub(sender.clone());477478			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;479			let nft_id = rmrk_nft_id.into();480481			let collection =482				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;483			collection.check_is_external()?;484485			let token_data =486				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;487488			let from = token_data.owner;489490			ensure!(491				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,492				<Error<T>>::NonTransferable493			);494495			ensure!(496				Self::get_nft_property_decoded::<Option<PendingTarget>>(497					collection_id,498					nft_id,499					RmrkProperty::PendingNftAccept500				)?.is_none(),501				<Error<T>>::NoPermission502			);503504			let target_owner;505			let approval_required;506507			match new_owner {508				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {509					target_owner = T::CrossAccountId::from_sub(account_id.clone());510					approval_required = false;511				}512				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(513					target_collection_id,514					target_nft_id,515				) => {516					let target_collection_id = Self::unique_collection_id(target_collection_id)?;517518					let target_nft_budget = budget::Value::new(NESTING_BUDGET);519520					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(521						target_collection_id,522						target_nft_id.into(),523						Some((collection_id, nft_id)),524						&target_nft_budget,525					)526					.map_err(Self::map_unique_err_to_proxy)?;527528					approval_required = cross_sender != target_nft_owner;529530					if approval_required {531						target_owner = target_nft_owner;532533						<PalletNft<T>>::set_scoped_token_property(534							collection.id,535							nft_id,536							PropertyScope::Rmrk,537							Self::rmrk_property::<Option<PendingTarget>>(538								PendingNftAccept,539								&Some((target_collection_id, target_nft_id.into()))540							)?,541						)?;542543						Self::insert_pending_child(544							(target_collection_id, target_nft_id.into()),545							(rmrk_collection_id, rmrk_nft_id),546						)?;547					} else {548						target_owner = T::CrossTokenAddressMapping::token_to_address(549							target_collection_id,550							target_nft_id.into(),551						);552					}553				}554			}555556			let src_nft_budget = budget::Value::new(NESTING_BUDGET);557558			<PalletNft<T>>::transfer_from(559				&collection,560				&cross_sender,561				&from,562				&target_owner,563				nft_id,564				&src_nft_budget,565			)566			.map_err(Self::map_unique_err_to_proxy)?;567568			Self::deposit_event(Event::NFTSent {569				sender,570				recipient: new_owner,571				collection_id: rmrk_collection_id,572				nft_id: rmrk_nft_id,573				approval_required,574			});575576			Ok(())577		}578579		/// Accepts an NFT sent from another account to self or owned NFT580		///581		/// Parameters:582		/// - `origin`: sender of the transaction583		/// - `rmrk_collection_id`: collection id of the nft to be accepted584		/// - `rmrk_nft_id`: nft id of the nft to be accepted585		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was586		///   sent to587		#[transactional]588		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]589		pub fn accept_nft(590			origin: OriginFor<T>,591			rmrk_collection_id: RmrkCollectionId,592			rmrk_nft_id: RmrkNftId,593			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,594		) -> DispatchResult {595			let sender = ensure_signed(origin.clone())?;596			let cross_sender = T::CrossAccountId::from_sub(sender.clone());597598			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;599			let nft_id = rmrk_nft_id.into();600601			let collection =602				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;603			collection.check_is_external()?;604605			let new_cross_owner = match new_owner {606				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {607					T::CrossAccountId::from_sub(account_id.clone())608				}609				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(610					target_collection_id,611					target_nft_id,612				) => {613					let target_collection_id = Self::unique_collection_id(target_collection_id)?;614615					T::CrossTokenAddressMapping::token_to_address(616						target_collection_id,617						TokenId(target_nft_id),618					)619				}620			};621622			let budget = budget::Value::new(NESTING_BUDGET);623624			<PalletNft<T>>::transfer(625				&collection,626				&cross_sender,627				&new_cross_owner,628				nft_id,629				&budget,630			)631			.map_err(|err| {632				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {633					<Error<T>>::CannotAcceptNonOwnedNft.into()634				} else {635					Self::map_unique_err_to_proxy(err)636				}637			})?;638639			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(640				collection_id,641				nft_id,642				RmrkProperty::PendingNftAccept643			)?;644645			if let Some(pending_target) = pending_target {646				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;647648				<PalletNft<T>>::set_scoped_token_property(649					collection.id,650					nft_id,651					PropertyScope::Rmrk,652					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,653				)?;654			}655656			Self::deposit_event(Event::NFTAccepted {657				sender,658				recipient: new_owner,659				collection_id: rmrk_collection_id,660				nft_id: rmrk_nft_id,661			});662663			Ok(())664		}665666		/// Rejects an NFT sent from another account to self or owned NFT667		///668		/// Parameters:669		/// - `origin`: sender of the transaction670		/// - `rmrk_collection_id`: collection id of the nft to be accepted671		/// - `rmrk_nft_id`: nft id of the nft to be accepted672		#[transactional]673		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]674		pub fn reject_nft(675			origin: OriginFor<T>,676			rmrk_collection_id: RmrkCollectionId,677			rmrk_nft_id: RmrkNftId,678		) -> DispatchResult {679			let sender = ensure_signed(origin)?;680			let cross_sender = T::CrossAccountId::from_sub(sender.clone());681682			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;683			let nft_id = rmrk_nft_id.into();684685			let collection =686				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;687			collection.check_is_external()?;688689			ensure!(690				<TokenData<T>>::get((collection_id, nft_id)).is_some(),691				<Error<T>>::NoAvailableNftId692			);693694695			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(696				collection_id,697				nft_id,698				RmrkProperty::PendingNftAccept699			)?;700701			match pending_target {702				Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,703				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),704			}705706			Self::destroy_nft(707				cross_sender,708				collection_id,709				nft_id,710				NESTING_BUDGET,711				<Error<T>>::CannotRejectNonOwnedNft,712			)713			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;714715			Self::deposit_event(Event::NFTRejected {716				sender,717				collection_id: rmrk_collection_id,718				nft_id: rmrk_nft_id,719			});720721			Ok(())722		}723724		/// accept the addition of a new resource to an existing NFT725		#[transactional]726		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]727		pub fn accept_resource(728			origin: OriginFor<T>,729			rmrk_collection_id: RmrkCollectionId,730			rmrk_nft_id: RmrkNftId,731			resource_id: RmrkResourceId,732		) -> DispatchResult {733			let sender = ensure_signed(origin)?;734			let cross_sender = T::CrossAccountId::from_sub(sender);735736			let collection_id = Self::unique_collection_id(rmrk_collection_id)737				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;738			let collection =739				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;740			collection.check_is_external()?;741742			let nft_id = rmrk_nft_id.into();743744			let budget = budget::Value::new(NESTING_BUDGET);745746			let nft_owner =747				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)748					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;749750			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {751				ensure!(res.pending, <Error<T>>::ResourceNotPending);752				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754				res.pending = false;755756				Ok(())757			})?;758759			Self::deposit_event(Event::<T>::ResourceAccepted {760				nft_id: rmrk_nft_id,761				resource_id,762			});763764			Ok(())765		}766767		/// accept the removal of a resource of an existing NFT768		#[transactional]769		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]770		pub fn accept_resource_removal(771			origin: OriginFor<T>,772			rmrk_collection_id: RmrkCollectionId,773			rmrk_nft_id: RmrkNftId,774			resource_id: RmrkResourceId,775		) -> DispatchResult {776			let sender = ensure_signed(origin)?;777			let cross_sender = T::CrossAccountId::from_sub(sender);778779			let collection_id = Self::unique_collection_id(rmrk_collection_id)780				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;781			let collection =782				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;783			collection.check_is_external()?;784785			let nft_id = rmrk_nft_id.into();786787			let budget = budget::Value::new(NESTING_BUDGET);788789			let nft_owner =790				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)791					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;792793			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);794795			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;796797			let resource_info = <PalletNft<T>>::token_aux_property((798				collection_id,799				nft_id,800				PropertyScope::Rmrk,801				resource_id_key.clone(),802			))803			.ok_or(<Error<T>>::ResourceDoesntExist)?;804805			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;806807			ensure!(808				resource_info.pending_removal,809				<Error<T>>::ResourceNotPending810			);811812			<PalletNft<T>>::remove_token_aux_property(813				collection_id,814				nft_id,815				PropertyScope::Rmrk,816				resource_id_key,817			);818819			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {820				nft_id: rmrk_nft_id,821				resource_id,822			});823824			Ok(())825		}826827		/// set a custom value on an NFT828		#[transactional]829		#[pallet::weight(<SelfWeightOf<T>>::set_property())]830		pub fn set_property(831			origin: OriginFor<T>,832			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,833			maybe_nft_id: Option<RmrkNftId>,834			key: RmrkKeyString,835			value: RmrkValueString,836		) -> DispatchResult {837			let sender = ensure_signed(origin)?;838			let sender = T::CrossAccountId::from_sub(sender);839840			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;841			let collection =842				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;843			collection.check_is_external()?;844845			let budget = budget::Value::new(NESTING_BUDGET);846847			match maybe_nft_id {848				Some(nft_id) => {849					let token_id: TokenId = nft_id.into();850851					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;852					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;853854					<PalletNft<T>>::set_scoped_token_property(855						collection_id,856						token_id,857						PropertyScope::Rmrk,858						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,859					)?;860				}861				None => {862					let collection = Self::get_typed_nft_collection(863						collection_id,864						misc::CollectionType::Regular,865					)?;866867					Self::check_collection_owner(&collection, &sender)?;868869					<PalletCommon<T>>::set_scoped_collection_property(870						collection_id,871						PropertyScope::Rmrk,872						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,873					)?;874				}875			}876877			Self::deposit_event(Event::PropertySet {878				collection_id: rmrk_collection_id,879				maybe_nft_id,880				key,881				value,882			});883884			Ok(())885		}886887		/// set a different order of resource priority888		#[transactional]889		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]890		pub fn set_priority(891			origin: OriginFor<T>,892			rmrk_collection_id: RmrkCollectionId,893			rmrk_nft_id: RmrkNftId,894			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,895		) -> DispatchResult {896			let sender = ensure_signed(origin)?;897			let sender = T::CrossAccountId::from_sub(sender);898899			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;900			let nft_id = rmrk_nft_id.into();901902			let collection =903				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;904			collection.check_is_external()?;905906			let budget = budget::Value::new(NESTING_BUDGET);907908			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;909			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;910911			<PalletNft<T>>::set_scoped_token_property(912				collection_id,913				nft_id,914				PropertyScope::Rmrk,915				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,916			)?;917918			Self::deposit_event(Event::<T>::PrioritySet {919				collection_id: rmrk_collection_id,920				nft_id: rmrk_nft_id,921			});922923			Ok(())924		}925926		/// Create basic resource927		#[transactional]928		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]929		pub fn add_basic_resource(930			origin: OriginFor<T>,931			rmrk_collection_id: RmrkCollectionId,932			nft_id: RmrkNftId,933			resource: RmrkBasicResource,934		) -> DispatchResult {935			let sender = ensure_signed(origin.clone())?;936937			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;938			let collection =939				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;940			collection.check_is_external()?;941942			let resource_id = Self::resource_add(943				sender,944				collection_id,945				nft_id.into(),946				RmrkResourceTypes::Basic(resource),947			)?;948949			Self::deposit_event(Event::ResourceAdded {950				nft_id,951				resource_id,952			});953			Ok(())954		}955956		/// Create composable resource957		#[transactional]958		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]959		pub fn add_composable_resource(960			origin: OriginFor<T>,961			rmrk_collection_id: RmrkCollectionId,962			nft_id: RmrkNftId,963			resource: RmrkComposableResource,964		) -> DispatchResult {965			let sender = ensure_signed(origin.clone())?;966967			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;968			let collection =969				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;970			collection.check_is_external()?;971972			let resource_id = Self::resource_add(973				sender,974				collection_id,975				nft_id.into(),976				RmrkResourceTypes::Composable(resource),977			)?;978979			Self::deposit_event(Event::ResourceAdded {980				nft_id,981				resource_id,982			});983			Ok(())984		}985986		/// Create slot resource987		#[transactional]988		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]989		pub fn add_slot_resource(990			origin: OriginFor<T>,991			rmrk_collection_id: RmrkCollectionId,992			nft_id: RmrkNftId,993			resource: RmrkSlotResource,994		) -> DispatchResult {995			let sender = ensure_signed(origin.clone())?;996997			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;998			let collection =999				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1000			collection.check_is_external()?;10011002			let resource_id = Self::resource_add(1003				sender,1004				collection_id,1005				nft_id.into(),1006				RmrkResourceTypes::Slot(resource),1007			)?;10081009			Self::deposit_event(Event::ResourceAdded {1010				nft_id,1011				resource_id,1012			});1013			Ok(())1014		}10151016		/// remove resource1017		#[transactional]1018		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1019		pub fn remove_resource(1020			origin: OriginFor<T>,1021			rmrk_collection_id: RmrkCollectionId,1022			nft_id: RmrkNftId,1023			resource_id: RmrkResourceId,1024		) -> DispatchResult {1025			let sender = ensure_signed(origin.clone())?;10261027			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1028			let collection =1029				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1030			collection.check_is_external()?;10311032			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10331034			Self::deposit_event(Event::ResourceRemoval {1035				nft_id,1036				resource_id,1037			});1038			Ok(())1039		}1040	}1041}10421043impl<T: Config> Pallet<T> {1044	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1045		let key = rmrk_key.to_key::<T>()?;10461047		let scoped_key = PropertyScope::Rmrk1048			.apply(key)1049			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10501051		Ok(scoped_key)1052	}10531054	// todo think about renaming these1055	pub fn rmrk_property<E: Encode>(1056		rmrk_key: RmrkProperty,1057		value: &E,1058	) -> Result<Property, DispatchError> {1059		let key = rmrk_key.to_key::<T>()?;10601061		let value = Self::encode_property(value)?;10621063		let property = Property { key, value };10641065		Ok(property)1066	}10671068	pub fn encode_property<E: Encode, S: Get<u32>>(1069		value: &E,1070	) -> Result<BoundedBytes<S>, DispatchError> {1071		let value = value1072			.encode()1073			.try_into()1074			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10751076		Ok(value)1077	}10781079	pub fn decode_property<D: Decode, S: Get<u32>>(1080		vec: &BoundedBytes<S>,1081	) -> Result<D, DispatchError> {1082		vec.decode()1083			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1084	}10851086	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1087	where1088		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1089	{1090		vec.rebind()1091			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1092	}10931094	fn init_collection(1095		sender: T::CrossAccountId,1096		data: CreateCollectionData<T::AccountId>,1097		properties: impl Iterator<Item = Property>,1098	) -> Result<CollectionId, DispatchError> {1099		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11001101		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1102			return Err(<Error<T>>::NoAvailableCollectionId.into());1103		}11041105		<PalletCommon<T>>::set_scoped_collection_properties(1106			collection_id?,1107			PropertyScope::Rmrk,1108			properties,1109		)?;11101111		collection_id1112	}11131114	pub fn create_nft(1115		sender: &T::CrossAccountId,1116		owner: &T::CrossAccountId,1117		collection: &NonfungibleHandle<T>,1118		properties: impl Iterator<Item = Property>,1119	) -> Result<TokenId, DispatchError> {1120		let data = CreateNftExData {1121			properties: BoundedVec::default(),1122			owner: owner.clone(),1123		};11241125		let budget = budget::Value::new(NESTING_BUDGET);11261127		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11281129		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11301131		<PalletNft<T>>::set_scoped_token_properties(1132			collection.id,1133			nft_id,1134			PropertyScope::Rmrk,1135			properties,1136		)?;11371138		Ok(nft_id)1139	}11401141	fn destroy_nft(1142		sender: T::CrossAccountId,1143		collection_id: CollectionId,1144		token_id: TokenId,1145		max_burns: u32,1146		error_if_not_owned: Error<T>,1147	) -> DispatchResultWithPostInfo {1148		let collection =1149			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11501151		let token_data =1152			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11531154		let from = token_data.owner;11551156		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11571158		ensure!(1159			<PalletStructure<T>>::check_indirectly_owned(1160				sender.clone(),1161				collection_id,1162				token_id,1163				None,1164				&owner_check_budget1165			)?,1166			error_if_not_owned,1167		);11681169		let burns_budget = budget::Value::new(max_burns);1170		let breadth_budget = budget::Value::new(max_burns);11711172		<PalletNft<T>>::burn_recursively(1173			&collection,1174			&from,1175			token_id,1176			&burns_budget,1177			&breadth_budget,1178		)1179	}11801181	fn insert_pending_child(1182		target: (CollectionId, TokenId),1183		child: (RmrkCollectionId, RmrkNftId),1184	) -> DispatchResult {1185		Self::mutate_pending_child(target, |pending_children| {1186			pending_children.insert(child);1187		})1188	}11891190	fn remove_pending_child(1191		target: (CollectionId, TokenId),1192		child: (RmrkCollectionId, RmrkNftId),1193	) -> DispatchResult {1194		Self::mutate_pending_child(target, |pending_children| {1195			pending_children.remove(&child);1196		})1197	}11981199	fn mutate_pending_child(1200		(target_collection_id, target_nft_id): (CollectionId, TokenId),1201		f: impl FnOnce(&mut PendingChildrenMap),1202	) -> DispatchResult {1203		<PalletNft<T>>::try_mutate_token_aux_property(1204			target_collection_id,1205			target_nft_id,1206			PropertyScope::Rmrk,1207			Self::rmrk_property_key(PendingChildren)?,1208			|pending_children| -> DispatchResult {1209				let mut map = match pending_children {1210					Some(map) => Self::decode_property(map)?,1211					None => PendingChildrenMap::new(),1212				};12131214				f(&mut map);12151216				*pending_children = Some(Self::encode_property(&map)?);12171218				Ok(())1219			},1220		)1221	}12221223	fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1224		let property = <PalletNft<T>>::token_aux_property((1225			collection_id,1226			nft_id,1227			PropertyScope::Rmrk,1228			Self::rmrk_property_key(PendingChildren)?1229		));12301231		let pending_children = match property {1232			Some(map) => Self::decode_property(&map)?,1233			None => PendingChildrenMap::new(),1234		};12351236		Ok(pending_children.into_iter())1237	}12381239	fn acquire_next_resource_id(1240		collection_id: CollectionId,1241		nft_id: TokenId,1242	) -> Result<RmrkResourceId, DispatchError> {1243		let resource_id: RmrkResourceId =1244			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12451246		let next_id = resource_id1247			.checked_add(1)1248			.ok_or(<Error<T>>::NoAvailableResourceId)?;12491250		<PalletNft<T>>::set_scoped_token_property(1251			collection_id,1252			nft_id,1253			PropertyScope::Rmrk,1254			Self::rmrk_property(NextResourceId, &next_id)?,1255		)?;12561257		Ok(resource_id)1258	}12591260	fn resource_add(1261		sender: T::AccountId,1262		collection_id: CollectionId,1263		nft_id: TokenId,1264		resource: RmrkResourceTypes,1265	) -> Result<RmrkResourceId, DispatchError> {1266		let collection =1267			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1268		ensure!(collection.owner == sender, Error::<T>::NoPermission);12691270		let sender = T::CrossAccountId::from_sub(sender);1271		let budget = budget::Value::new(NESTING_BUDGET);12721273		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1274			.map_err(Self::map_unique_err_to_proxy)?;12751276		let pending = sender != nft_owner;12771278		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;12791280		let resource_info = RmrkResourceInfo {1281			id,1282			resource,1283			pending,1284			pending_removal: false,1285		};12861287		<PalletNft<T>>::try_mutate_token_aux_property(1288			collection_id,1289			nft_id,1290			PropertyScope::Rmrk,1291			Self::rmrk_property_key(ResourceId(id))?,1292			|value| -> DispatchResult {1293				*value = Some(Self::encode_property(&resource_info)?);12941295				Ok(())1296			},1297		)?;12981299		Ok(id)1300	}13011302	fn resource_remove(1303		sender: T::AccountId,1304		collection_id: CollectionId,1305		nft_id: TokenId,1306		resource_id: RmrkResourceId,1307	) -> DispatchResult {1308		let collection =1309			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1310		ensure!(collection.owner == sender, Error::<T>::NoPermission);13111312		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1313		let scope = PropertyScope::Rmrk;13141315		ensure!(1316			<PalletNft<T>>::token_aux_property((1317				collection_id,1318				nft_id,1319				scope,1320				resource_id_key.clone()1321			))1322			.is_some(),1323			<Error<T>>::ResourceDoesntExist1324		);13251326		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1327		let topmost_owner =1328			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13291330		let sender = T::CrossAccountId::from_sub(sender);1331		if topmost_owner == sender {1332			<PalletNft<T>>::remove_token_aux_property(1333				collection_id,1334				nft_id,1335				PropertyScope::Rmrk,1336				Self::rmrk_property_key(ResourceId(resource_id))?,1337			);1338		} else {1339			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1340				res.pending_removal = true;13411342				Ok(())1343			})?;1344		}13451346		Ok(())1347	}13481349	fn try_mutate_resource_info(1350		collection_id: CollectionId,1351		nft_id: TokenId,1352		resource_id: RmrkResourceId,1353		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1354	) -> DispatchResult {1355		<PalletNft<T>>::try_mutate_token_aux_property(1356			collection_id,1357			nft_id,1358			PropertyScope::Rmrk,1359			Self::rmrk_property_key(ResourceId(resource_id))?,1360			|value| match value {1361				Some(value) => {1362					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;13631364					f(&mut resource_info)?;13651366					*value = Self::encode_property(&resource_info)?;13671368					Ok(())1369				}1370				None => Err(<Error<T>>::ResourceDoesntExist.into()),1371			},1372		)1373	}13741375	fn change_collection_owner(1376		collection_id: CollectionId,1377		collection_type: misc::CollectionType,1378		sender: T::AccountId,1379		new_owner: T::AccountId,1380	) -> DispatchResult {1381		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1382		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;13831384		let mut collection = collection.into_inner();13851386		collection.owner = new_owner;1387		collection.save()1388	}13891390	pub fn check_collection_owner(1391		collection: &NonfungibleHandle<T>,1392		account: &T::CrossAccountId,1393	) -> DispatchResult {1394		collection1395			.check_is_owner(account)1396			.map_err(Self::map_unique_err_to_proxy)1397	}13981399	pub fn last_collection_idx() -> RmrkCollectionId {1400		<CollectionIndex<T>>::get()1401	}14021403	pub fn unique_collection_id(1404		rmrk_collection_id: RmrkCollectionId,1405	) -> Result<CollectionId, DispatchError> {1406		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1407			.map_err(|_| <Error<T>>::CollectionUnknown.into())1408	}14091410	pub fn rmrk_collection_id(1411		unique_collection_id: CollectionId,1412	) -> Result<RmrkCollectionId, DispatchError> {1413		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1414	}14151416	pub fn get_nft_collection(1417		collection_id: CollectionId,1418	) -> Result<NonfungibleHandle<T>, DispatchError> {1419		let collection = <CollectionHandle<T>>::try_get(collection_id)1420			.map_err(|_| <Error<T>>::CollectionUnknown)?;14211422		match collection.mode {1423			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1424			_ => Err(<Error<T>>::CollectionUnknown.into()),1425		}1426	}14271428	pub fn collection_exists(collection_id: CollectionId) -> bool {1429		<CollectionHandle<T>>::try_get(collection_id).is_ok()1430	}14311432	pub fn get_collection_property(1433		collection_id: CollectionId,1434		key: RmrkProperty,1435	) -> Result<PropertyValue, DispatchError> {1436		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1437			.get(&Self::rmrk_property_key(key)?)1438			.ok_or(<Error<T>>::CollectionUnknown)?1439			.clone();14401441		Ok(collection_property)1442	}14431444	pub fn get_collection_property_decoded<V: Decode>(1445		collection_id: CollectionId,1446		key: RmrkProperty,1447	) -> Result<V, DispatchError> {1448		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1449	}14501451	pub fn get_collection_type(1452		collection_id: CollectionId,1453	) -> Result<misc::CollectionType, DispatchError> {1454		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1455			if err != <Error<T>>::CollectionUnknown.into() {1456				<Error<T>>::CorruptedCollectionType.into()1457			} else {1458				err1459			}1460		})1461	}14621463	pub fn ensure_collection_type(1464		collection_id: CollectionId,1465		collection_type: misc::CollectionType,1466	) -> DispatchResult {1467		let actual_type = Self::get_collection_type(collection_id)?;1468		ensure!(1469			actual_type == collection_type,1470			<CommonError<T>>::NoPermission1471		);14721473		Ok(())1474	}14751476	pub fn get_typed_nft_collection(1477		collection_id: CollectionId,1478		collection_type: misc::CollectionType,1479	) -> Result<NonfungibleHandle<T>, DispatchError> {1480		Self::ensure_collection_type(collection_id, collection_type)?;14811482		Self::get_nft_collection(collection_id)1483	}14841485	pub fn get_typed_nft_collection_mapped(1486		rmrk_collection_id: RmrkCollectionId,1487		collection_type: misc::CollectionType,1488	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1489		let unique_collection_id = match collection_type {1490			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1491			_ => rmrk_collection_id.into(),1492		};14931494		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14951496		Ok((collection, unique_collection_id))1497	}14981499	pub fn get_nft_property(1500		collection_id: CollectionId,1501		nft_id: TokenId,1502		key: RmrkProperty,1503	) -> Result<PropertyValue, DispatchError> {1504		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1505			.get(&Self::rmrk_property_key(key)?)1506			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1507			.clone();15081509		Ok(nft_property)1510	}15111512	pub fn get_nft_property_decoded<V: Decode>(1513		collection_id: CollectionId,1514		nft_id: TokenId,1515		key: RmrkProperty,1516	) -> Result<V, DispatchError> {1517		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1518	}15191520	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1521		<TokenData<T>>::contains_key((collection_id, nft_id))1522	}15231524	pub fn get_nft_type(1525		collection_id: CollectionId,1526		token_id: TokenId,1527	) -> Result<NftType, DispatchError> {1528		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1529			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1530	}15311532	pub fn ensure_nft_type(1533		collection_id: CollectionId,1534		token_id: TokenId,1535		nft_type: NftType,1536	) -> DispatchResult {1537		let actual_type = Self::get_nft_type(collection_id, token_id)?;1538		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);15391540		Ok(())1541	}15421543	pub fn ensure_nft_owner(1544		collection_id: CollectionId,1545		token_id: TokenId,1546		possible_owner: &T::CrossAccountId,1547		nesting_budget: &dyn budget::Budget,1548	) -> DispatchResult {1549		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1550			possible_owner.clone(),1551			collection_id,1552			token_id,1553			None,1554			nesting_budget,1555		)1556		.map_err(Self::map_unique_err_to_proxy)?;15571558		ensure!(is_owned, <Error<T>>::NoPermission);15591560		Ok(())1561	}15621563	pub fn filter_user_properties<Key, Value, R, Mapper>(1564		collection_id: CollectionId,1565		token_id: Option<TokenId>,1566		filter_keys: Option<Vec<RmrkPropertyKey>>,1567		mapper: Mapper,1568	) -> Result<Vec<R>, DispatchError>1569	where1570		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1571		Value: Decode + Default,1572		Mapper: Fn(Key, Value) -> R,1573	{1574		filter_keys1575			.map(|keys| {1576				let properties = keys1577					.into_iter()1578					.filter_map(|key| {1579						let key: Key = key.try_into().ok()?;15801581						let value = match token_id {1582							Some(token_id) => Self::get_nft_property_decoded(1583								collection_id,1584								token_id,1585								UserProperty(key.as_ref()),1586							),1587							None => Self::get_collection_property_decoded(1588								collection_id,1589								UserProperty(key.as_ref()),1590							),1591						}1592						.ok()?;15931594						Some(mapper(key, value))1595					})1596					.collect();15971598				Ok(properties)1599			})1600			.unwrap_or_else(|| {1601				let properties =1602					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16031604				Ok(properties)1605			})1606	}16071608	pub fn iterate_user_properties<Key, Value, R, Mapper>(1609		collection_id: CollectionId,1610		token_id: Option<TokenId>,1611		mapper: Mapper,1612	) -> Result<impl Iterator<Item = R>, DispatchError>1613	where1614		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1615		Value: Decode + Default,1616		Mapper: Fn(Key, Value) -> R,1617	{1618		let properties = match token_id {1619			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1620			None => <PalletCommon<T>>::collection_properties(collection_id),1621		};16221623		let properties = properties.into_iter().filter_map(move |(key, value)| {1624			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16251626			let key: Key = key.to_vec().try_into().ok()?;1627			let value: Value = value.decode().ok()?;16281629			Some(mapper(key, value))1630		});16311632		Ok(properties)1633	}16341635	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1636		map_unique_err_to_proxy! {1637			match err {1638				CommonError::NoPermission => NoPermission,1639				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1640				CommonError::PublicMintingNotAllowed => NoPermission,1641				CommonError::TokenNotFound => NoAvailableNftId,1642				CommonError::ApprovedValueTooLow => NoPermission,1643				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1644				StructureError::TokenNotFound => NoAvailableNftId,1645				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1646			}1647		}1648	}1649}
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::{20	pallet_prelude::*,21	transactional,22	BoundedVec,23	dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::{btree_set::BTreeSet, btree_map::BTreeMap}};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenSet = BTreeSet<PendingChild>;5960type BasesMap = BTreeMap<RmrkBaseId, u32>;6162#[frame_support::pallet]63pub mod pallet {64	use super::*;65	use pallet_evm::account;6667	#[pallet::config]68	pub trait Config:69		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config70	{71		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;72		type WeightInfo: WeightInfo;73	}7475	#[pallet::storage]76	#[pallet::getter(fn collection_index)]77	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7879	#[pallet::storage]80	pub type UniqueCollectionId<T: Config> =81		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8283	#[pallet::pallet]84	#[pallet::generate_store(pub(super) trait Store)]85	pub struct Pallet<T>(_);8687	#[pallet::event]88	#[pallet::generate_deposit(pub(super) fn deposit_event)]89	pub enum Event<T: Config> {90		CollectionCreated {91			issuer: T::AccountId,92			collection_id: RmrkCollectionId,93		},94		CollectionDestroyed {95			issuer: T::AccountId,96			collection_id: RmrkCollectionId,97		},98		IssuerChanged {99			old_issuer: T::AccountId,100			new_issuer: T::AccountId,101			collection_id: RmrkCollectionId,102		},103		CollectionLocked {104			issuer: T::AccountId,105			collection_id: RmrkCollectionId,106		},107		NftMinted {108			owner: T::AccountId,109			collection_id: RmrkCollectionId,110			nft_id: RmrkNftId,111		},112		NFTBurned {113			owner: T::AccountId,114			nft_id: RmrkNftId,115		},116		NFTSent {117			sender: T::AccountId,118			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,119			collection_id: RmrkCollectionId,120			nft_id: RmrkNftId,121			approval_required: bool,122		},123		NFTAccepted {124			sender: T::AccountId,125			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,126			collection_id: RmrkCollectionId,127			nft_id: RmrkNftId,128		},129		NFTRejected {130			sender: T::AccountId,131			collection_id: RmrkCollectionId,132			nft_id: RmrkNftId,133		},134		PropertySet {135			collection_id: RmrkCollectionId,136			maybe_nft_id: Option<RmrkNftId>,137			key: RmrkKeyString,138			value: RmrkValueString,139		},140		ResourceAdded {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		ResourceRemoval {145			nft_id: RmrkNftId,146			resource_id: RmrkResourceId,147		},148		ResourceAccepted {149			nft_id: RmrkNftId,150			resource_id: RmrkResourceId,151		},152		ResourceRemovalAccepted {153			nft_id: RmrkNftId,154			resource_id: RmrkResourceId,155		},156		PrioritySet {157			collection_id: RmrkCollectionId,158			nft_id: RmrkNftId,159		},160	}161162	#[pallet::error]163	pub enum Error<T> {164		/* Unique-specific events */165		CorruptedCollectionType,166		NftTypeEncodeError,167		RmrkPropertyKeyIsTooLong,168		RmrkPropertyValueIsTooLong,169		RmrkPropertyIsNotFound,170		UnableToDecodeRmrkData,171172		/* RMRK compatible events */173		CollectionNotEmpty,174		NoAvailableCollectionId,175		NoAvailableNftId,176		CollectionUnknown,177		NoPermission,178		NonTransferable,179		CollectionFullOrLocked,180		ResourceDoesntExist,181		CannotSendToDescendentOrSelf,182		CannotAcceptNonOwnedNft,183		CannotRejectNonOwnedNft,184		CannotRejectNonPendingNft,185		ResourceNotPending,186		NoAvailableResourceId,187	}188189	#[pallet::call]190	impl<T: Config> Pallet<T> {191		/// Create a collection192		#[transactional]193		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]194		pub fn create_collection(195			origin: OriginFor<T>,196			metadata: RmrkString,197			max: Option<u32>,198			symbol: RmrkCollectionSymbol,199		) -> DispatchResult {200			let sender = ensure_signed(origin)?;201202			let limits = CollectionLimits {203				owner_can_transfer: Some(false),204				token_limit: max,205				..Default::default()206			};207208			let data = CreateCollectionData {209				limits: Some(limits),210				token_prefix: symbol211					.into_inner()212					.try_into()213					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,214				permissions: Some(CollectionPermissions {215					nesting: Some(NestingPermissions {216						token_owner: true,217						collection_admin: false,218						restricted: None,219						#[cfg(feature = "runtime-benchmarks")]220						permissive: false,221					}),222					..Default::default()223				}),224				..Default::default()225			};226227			let unique_collection_id = Self::init_collection(228				T::CrossAccountId::from_sub(sender.clone()),229				data,230				[231					Self::rmrk_property(Metadata, &metadata)?,232					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,233				]234				.into_iter(),235			)?;236			let rmrk_collection_id = <CollectionIndex<T>>::get();237238			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);239240			<PalletCommon<T>>::set_scoped_collection_property(241				unique_collection_id,242				PropertyScope::Rmrk,243				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,244			)?;245246			<CollectionIndex<T>>::mutate(|n| *n += 1);247248			Self::deposit_event(Event::CollectionCreated {249				issuer: sender,250				collection_id: rmrk_collection_id,251			});252253			Ok(())254		}255256		/// destroy collection257		#[transactional]258		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]259		pub fn destroy_collection(260			origin: OriginFor<T>,261			collection_id: RmrkCollectionId,262		) -> DispatchResult {263			let sender = ensure_signed(origin)?;264			let cross_sender = T::CrossAccountId::from_sub(sender.clone());265266			let collection = Self::get_typed_nft_collection(267				Self::unique_collection_id(collection_id)?,268				misc::CollectionType::Regular,269			)?;270			collection.check_is_external()?;271272			<PalletNft<T>>::destroy_collection(collection, &cross_sender)273				.map_err(Self::map_unique_err_to_proxy)?;274275			Self::deposit_event(Event::CollectionDestroyed {276				issuer: sender,277				collection_id,278			});279280			Ok(())281		}282283		/// Change the issuer of a collection284		///285		/// Parameters:286		/// - `origin`: sender of the transaction287		/// - `collection_id`: collection id of the nft to change issuer of288		/// - `new_issuer`: Collection's new issuer289		#[transactional]290		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]291		pub fn change_collection_issuer(292			origin: OriginFor<T>,293			collection_id: RmrkCollectionId,294			new_issuer: <T::Lookup as StaticLookup>::Source,295		) -> DispatchResult {296			let sender = ensure_signed(origin)?;297298			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;299			collection.check_is_external()?;300301			let new_issuer = T::Lookup::lookup(new_issuer)?;302303			Self::change_collection_owner(304				Self::unique_collection_id(collection_id)?,305				misc::CollectionType::Regular,306				sender.clone(),307				new_issuer.clone(),308			)?;309310			Self::deposit_event(Event::IssuerChanged {311				old_issuer: sender,312				new_issuer,313				collection_id,314			});315316			Ok(())317		}318319		/// lock collection320		#[transactional]321		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]322		pub fn lock_collection(323			origin: OriginFor<T>,324			collection_id: RmrkCollectionId,325		) -> DispatchResult {326			let sender = ensure_signed(origin)?;327			let cross_sender = T::CrossAccountId::from_sub(sender.clone());328329			let collection = Self::get_typed_nft_collection(330				Self::unique_collection_id(collection_id)?,331				misc::CollectionType::Regular,332			)?;333			collection.check_is_external()?;334335			Self::check_collection_owner(&collection, &cross_sender)?;336337			let token_count = collection.total_supply();338339			let mut collection = collection.into_inner();340			collection.limits.token_limit = Some(token_count);341			collection.save()?;342343			Self::deposit_event(Event::CollectionLocked {344				issuer: sender,345				collection_id,346			});347348			Ok(())349		}350351		/// Mints an NFT in the specified collection352		/// Sets metadata and the royalty attribute353		///354		/// Parameters:355		/// - `collection_id`: The class of the asset to be minted.356		/// - `nft_id`: The nft value of the asset to be minted.357		/// - `recipient`: Receiver of the royalty358		/// - `royalty`: Permillage reward from each trade for the Recipient359		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash360		/// - `transferable`: Ability to transfer this NFT361		#[transactional]362		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]363		pub fn mint_nft(364			origin: OriginFor<T>,365			owner: Option<T::AccountId>,366			collection_id: RmrkCollectionId,367			recipient: Option<T::AccountId>,368			royalty_amount: Option<Permill>,369			metadata: RmrkString,370			transferable: bool,371			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,372		) -> DispatchResult {373			let sender = ensure_signed(origin)?;374			let cross_sender = T::CrossAccountId::from_sub(sender.clone());375376			let owner = owner.unwrap_or(sender.clone());377			let cross_owner = T::CrossAccountId::from_sub(owner.clone());378379			let collection = Self::get_typed_nft_collection(380				Self::unique_collection_id(collection_id)?,381				misc::CollectionType::Regular,382			)?;383			collection.check_is_external()?;384385			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {386				recipient: recipient.unwrap_or_else(|| owner.clone()),387				amount,388			});389390			let nft_id = Self::create_nft(391				&cross_sender,392				&cross_owner,393				&collection,394				[395					Self::rmrk_property(TokenType, &NftType::Regular)?,396					Self::rmrk_property(Transferable, &transferable)?,397					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,398					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,399					Self::rmrk_property(Metadata, &metadata)?,400					Self::rmrk_property(Equipped, &false)?,401					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,402					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,403					Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,404					Self::rmrk_property(AssociatedBases, &BasesMap::new())?,405				]406				.into_iter(),407			)408			.map_err(|err| match err {409				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),410				err => Self::map_unique_err_to_proxy(err),411			})?;412413			if let Some(resources) = resources {414				for resource in resources {415					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;416				}417			}418419			Self::deposit_event(Event::NftMinted {420				owner,421				collection_id,422				nft_id: nft_id.0,423			});424425			Ok(())426		}427428		/// burn nft429		#[transactional]430		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]431		pub fn burn_nft(432			origin: OriginFor<T>,433			collection_id: RmrkCollectionId,434			nft_id: RmrkNftId,435			max_burns: u32,436		) -> DispatchResult {437			let sender = ensure_signed(origin)?;438			let cross_sender = T::CrossAccountId::from_sub(sender.clone());439440			let collection = Self::get_typed_nft_collection(441				Self::unique_collection_id(collection_id)?,442				misc::CollectionType::Regular,443			)?;444			collection.check_is_external()?;445446			Self::destroy_nft(447				cross_sender,448				Self::unique_collection_id(collection_id)?,449				nft_id.into(),450				max_burns,451				<Error<T>>::NoPermission,452			)453			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;454455			Self::deposit_event(Event::NFTBurned {456				owner: sender,457				nft_id,458			});459460			Ok(())461		}462463		/// Transfers a NFT from an Account or NFT A to another Account or NFT B464		///465		/// Parameters:466		/// - `origin`: sender of the transaction467		/// - `rmrk_collection_id`: collection id of the nft to be transferred468		/// - `rmrk_nft_id`: nft id of the nft to be transferred469		/// - `new_owner`: new owner of the nft which can be either an account or a NFT470		#[transactional]471		#[pallet::weight(<SelfWeightOf<T>>::send())]472		pub fn send(473			origin: OriginFor<T>,474			rmrk_collection_id: RmrkCollectionId,475			rmrk_nft_id: RmrkNftId,476			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,477		) -> DispatchResult {478			let sender = ensure_signed(origin.clone())?;479			let cross_sender = T::CrossAccountId::from_sub(sender.clone());480481			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;482			let nft_id = rmrk_nft_id.into();483484			let collection =485				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;486			collection.check_is_external()?;487488			let token_data =489				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;490491			let from = token_data.owner;492493			ensure!(494				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,495				<Error<T>>::NonTransferable496			);497498			ensure!(499				Self::get_nft_property_decoded::<Option<PendingTarget>>(500					collection_id,501					nft_id,502					RmrkProperty::PendingNftAccept503				)?.is_none(),504				<Error<T>>::NoPermission505			);506507			let target_owner;508			let approval_required;509510			match new_owner {511				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {512					target_owner = T::CrossAccountId::from_sub(account_id.clone());513					approval_required = false;514				}515				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(516					target_collection_id,517					target_nft_id,518				) => {519					let target_collection_id = Self::unique_collection_id(target_collection_id)?;520521					let target_nft_budget = budget::Value::new(NESTING_BUDGET);522523					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(524						target_collection_id,525						target_nft_id.into(),526						Some((collection_id, nft_id)),527						&target_nft_budget,528					)529					.map_err(Self::map_unique_err_to_proxy)?;530531					approval_required = cross_sender != target_nft_owner;532533					if approval_required {534						target_owner = target_nft_owner;535536						<PalletNft<T>>::set_scoped_token_property(537							collection.id,538							nft_id,539							PropertyScope::Rmrk,540							Self::rmrk_property::<Option<PendingTarget>>(541								PendingNftAccept,542								&Some((target_collection_id, target_nft_id.into()))543							)?,544						)?;545546						Self::insert_pending_child(547							(target_collection_id, target_nft_id.into()),548							(rmrk_collection_id, rmrk_nft_id),549						)?;550					} else {551						target_owner = T::CrossTokenAddressMapping::token_to_address(552							target_collection_id,553							target_nft_id.into(),554						);555					}556				}557			}558559			let src_nft_budget = budget::Value::new(NESTING_BUDGET);560561			<PalletNft<T>>::transfer_from(562				&collection,563				&cross_sender,564				&from,565				&target_owner,566				nft_id,567				&src_nft_budget,568			)569			.map_err(Self::map_unique_err_to_proxy)?;570571			Self::deposit_event(Event::NFTSent {572				sender,573				recipient: new_owner,574				collection_id: rmrk_collection_id,575				nft_id: rmrk_nft_id,576				approval_required,577			});578579			Ok(())580		}581582		/// Accepts an NFT sent from another account to self or owned NFT583		///584		/// Parameters:585		/// - `origin`: sender of the transaction586		/// - `rmrk_collection_id`: collection id of the nft to be accepted587		/// - `rmrk_nft_id`: nft id of the nft to be accepted588		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was589		///   sent to590		#[transactional]591		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]592		pub fn accept_nft(593			origin: OriginFor<T>,594			rmrk_collection_id: RmrkCollectionId,595			rmrk_nft_id: RmrkNftId,596			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,597		) -> DispatchResult {598			let sender = ensure_signed(origin.clone())?;599			let cross_sender = T::CrossAccountId::from_sub(sender.clone());600601			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;602			let nft_id = rmrk_nft_id.into();603604			let collection =605				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;606			collection.check_is_external()?;607608			let new_cross_owner = match new_owner {609				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {610					T::CrossAccountId::from_sub(account_id.clone())611				}612				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(613					target_collection_id,614					target_nft_id,615				) => {616					let target_collection_id = Self::unique_collection_id(target_collection_id)?;617618					T::CrossTokenAddressMapping::token_to_address(619						target_collection_id,620						TokenId(target_nft_id),621					)622				}623			};624625			let budget = budget::Value::new(NESTING_BUDGET);626627			<PalletNft<T>>::transfer(628				&collection,629				&cross_sender,630				&new_cross_owner,631				nft_id,632				&budget,633			)634			.map_err(|err| {635				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {636					<Error<T>>::CannotAcceptNonOwnedNft.into()637				} else {638					Self::map_unique_err_to_proxy(err)639				}640			})?;641642			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(643				collection_id,644				nft_id,645				RmrkProperty::PendingNftAccept646			)?;647648			if let Some(pending_target) = pending_target {649				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;650651				<PalletNft<T>>::set_scoped_token_property(652					collection.id,653					nft_id,654					PropertyScope::Rmrk,655					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,656				)?;657			}658659			Self::deposit_event(Event::NFTAccepted {660				sender,661				recipient: new_owner,662				collection_id: rmrk_collection_id,663				nft_id: rmrk_nft_id,664			});665666			Ok(())667		}668669		/// Rejects an NFT sent from another account to self or owned NFT670		///671		/// Parameters:672		/// - `origin`: sender of the transaction673		/// - `rmrk_collection_id`: collection id of the nft to be accepted674		/// - `rmrk_nft_id`: nft id of the nft to be accepted675		#[transactional]676		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]677		pub fn reject_nft(678			origin: OriginFor<T>,679			rmrk_collection_id: RmrkCollectionId,680			rmrk_nft_id: RmrkNftId,681		) -> DispatchResult {682			let sender = ensure_signed(origin)?;683			let cross_sender = T::CrossAccountId::from_sub(sender.clone());684685			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;686			let nft_id = rmrk_nft_id.into();687688			let collection =689				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;690			collection.check_is_external()?;691692			ensure!(693				<TokenData<T>>::get((collection_id, nft_id)).is_some(),694				<Error<T>>::NoAvailableNftId695			);696697698			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(699				collection_id,700				nft_id,701				RmrkProperty::PendingNftAccept702			)?;703704			match pending_target {705				Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,706				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),707			}708709			Self::destroy_nft(710				cross_sender,711				collection_id,712				nft_id,713				NESTING_BUDGET,714				<Error<T>>::CannotRejectNonOwnedNft,715			)716			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;717718			Self::deposit_event(Event::NFTRejected {719				sender,720				collection_id: rmrk_collection_id,721				nft_id: rmrk_nft_id,722			});723724			Ok(())725		}726727		/// accept the addition of a new resource to an existing NFT728		#[transactional]729		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730		pub fn accept_resource(731			origin: OriginFor<T>,732			rmrk_collection_id: RmrkCollectionId,733			rmrk_nft_id: RmrkNftId,734			resource_id: RmrkResourceId,735		) -> DispatchResult {736			let sender = ensure_signed(origin)?;737			let cross_sender = T::CrossAccountId::from_sub(sender);738739			let collection_id = Self::unique_collection_id(rmrk_collection_id)740				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;741			let collection =742				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;743			collection.check_is_external()?;744745			let nft_id = rmrk_nft_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {754				ensure!(res.pending, <Error<T>>::ResourceNotPending);755				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);756757				res.pending = false;758759				Ok(())760			})?;761762			Self::deposit_event(Event::<T>::ResourceAccepted {763				nft_id: rmrk_nft_id,764				resource_id,765			});766767			Ok(())768		}769770		/// accept the removal of a resource of an existing NFT771		#[transactional]772		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773		pub fn accept_resource_removal(774			origin: OriginFor<T>,775			rmrk_collection_id: RmrkCollectionId,776			rmrk_nft_id: RmrkNftId,777			resource_id: RmrkResourceId,778		) -> DispatchResult {779			let sender = ensure_signed(origin)?;780			let cross_sender = T::CrossAccountId::from_sub(sender);781782			let collection_id = Self::unique_collection_id(rmrk_collection_id)783				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;784			let collection =785				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;786			collection.check_is_external()?;787788			let nft_id = rmrk_nft_id.into();789790			let budget = budget::Value::new(NESTING_BUDGET);791792			let nft_owner =793				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)794					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;795796			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);797798			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;799800			let resource_info = <PalletNft<T>>::token_aux_property((801				collection_id,802				nft_id,803				PropertyScope::Rmrk,804				resource_id_key.clone(),805			))806			.ok_or(<Error<T>>::ResourceDoesntExist)?;807808			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;809810			ensure!(811				resource_info.pending_removal,812				<Error<T>>::ResourceNotPending813			);814815			<PalletNft<T>>::remove_token_aux_property(816				collection_id,817				nft_id,818				PropertyScope::Rmrk,819				resource_id_key,820			);821822			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {823				let base_id = resource.base;824825				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;826			}827828			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {829				nft_id: rmrk_nft_id,830				resource_id,831			});832833			Ok(())834		}835836		/// set a custom value on an NFT837		#[transactional]838		#[pallet::weight(<SelfWeightOf<T>>::set_property())]839		pub fn set_property(840			origin: OriginFor<T>,841			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,842			maybe_nft_id: Option<RmrkNftId>,843			key: RmrkKeyString,844			value: RmrkValueString,845		) -> DispatchResult {846			let sender = ensure_signed(origin)?;847			let sender = T::CrossAccountId::from_sub(sender);848849			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;850			let collection =851				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;852			collection.check_is_external()?;853854			let budget = budget::Value::new(NESTING_BUDGET);855856			match maybe_nft_id {857				Some(nft_id) => {858					let token_id: TokenId = nft_id.into();859860					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;861					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;862863					<PalletNft<T>>::set_scoped_token_property(864						collection_id,865						token_id,866						PropertyScope::Rmrk,867						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,868					)?;869				}870				None => {871					let collection = Self::get_typed_nft_collection(872						collection_id,873						misc::CollectionType::Regular,874					)?;875876					Self::check_collection_owner(&collection, &sender)?;877878					<PalletCommon<T>>::set_scoped_collection_property(879						collection_id,880						PropertyScope::Rmrk,881						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,882					)?;883				}884			}885886			Self::deposit_event(Event::PropertySet {887				collection_id: rmrk_collection_id,888				maybe_nft_id,889				key,890				value,891			});892893			Ok(())894		}895896		/// set a different order of resource priority897		#[transactional]898		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]899		pub fn set_priority(900			origin: OriginFor<T>,901			rmrk_collection_id: RmrkCollectionId,902			rmrk_nft_id: RmrkNftId,903			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,904		) -> DispatchResult {905			let sender = ensure_signed(origin)?;906			let sender = T::CrossAccountId::from_sub(sender);907908			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;909			let nft_id = rmrk_nft_id.into();910911			let collection =912				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;913			collection.check_is_external()?;914915			let budget = budget::Value::new(NESTING_BUDGET);916917			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;918			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;919920			<PalletNft<T>>::set_scoped_token_property(921				collection_id,922				nft_id,923				PropertyScope::Rmrk,924				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,925			)?;926927			Self::deposit_event(Event::<T>::PrioritySet {928				collection_id: rmrk_collection_id,929				nft_id: rmrk_nft_id,930			});931932			Ok(())933		}934935		/// Create basic resource936		#[transactional]937		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938		pub fn add_basic_resource(939			origin: OriginFor<T>,940			rmrk_collection_id: RmrkCollectionId,941			nft_id: RmrkNftId,942			resource: RmrkBasicResource,943		) -> DispatchResult {944			let sender = ensure_signed(origin.clone())?;945946			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;947			let collection =948				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;949			collection.check_is_external()?;950951			let resource_id = Self::resource_add(952				sender,953				collection_id,954				nft_id.into(),955				RmrkResourceTypes::Basic(resource),956			)?;957958			Self::deposit_event(Event::ResourceAdded {959				nft_id,960				resource_id,961			});962			Ok(())963		}964965		/// Create composable resource966		#[transactional]967		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968		pub fn add_composable_resource(969			origin: OriginFor<T>,970			rmrk_collection_id: RmrkCollectionId,971			nft_id: RmrkNftId,972			resource: RmrkComposableResource,973		) -> DispatchResult {974			let sender = ensure_signed(origin.clone())?;975976			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;977			let collection =978				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;979			collection.check_is_external()?;980981			let base_id = resource.base;982983			let resource_id = Self::resource_add(984				sender,985				collection_id,986				nft_id.into(),987				RmrkResourceTypes::Composable(resource),988			)?;989990			<PalletNft<T>>::try_mutate_token_aux_property(991				collection_id,992				nft_id.into(),993				PropertyScope::Rmrk,994				Self::rmrk_property_key(AssociatedBases)?,995				|value| -> DispatchResult {996					let mut bases: BasesMap = match value {997						Some(value) => Self::decode_property(value)?,998						None => BasesMap::new()999					};1000	1001					*bases.entry(base_id).or_insert(0) += 1;1002	1003					*value = Some(Self::encode_property(&bases)?);1004					Ok(())1005				}1006			)?;10071008			Self::deposit_event(Event::ResourceAdded {1009				nft_id,1010				resource_id,1011			});1012			Ok(())1013		}10141015		/// Create slot resource1016		#[transactional]1017		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018		pub fn add_slot_resource(1019			origin: OriginFor<T>,1020			rmrk_collection_id: RmrkCollectionId,1021			nft_id: RmrkNftId,1022			resource: RmrkSlotResource,1023		) -> DispatchResult {1024			let sender = ensure_signed(origin.clone())?;10251026			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1027			let collection =1028				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1029			collection.check_is_external()?;10301031			let resource_id = Self::resource_add(1032				sender,1033				collection_id,1034				nft_id.into(),1035				RmrkResourceTypes::Slot(resource),1036			)?;10371038			Self::deposit_event(Event::ResourceAdded {1039				nft_id,1040				resource_id,1041			});1042			Ok(())1043		}10441045		/// remove resource1046		#[transactional]1047		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048		pub fn remove_resource(1049			origin: OriginFor<T>,1050			rmrk_collection_id: RmrkCollectionId,1051			nft_id: RmrkNftId,1052			resource_id: RmrkResourceId,1053		) -> DispatchResult {1054			let sender = ensure_signed(origin.clone())?;10551056			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1057			let collection =1058				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1059			collection.check_is_external()?;10601061			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10621063			Self::deposit_event(Event::ResourceRemoval {1064				nft_id,1065				resource_id,1066			});1067			Ok(())1068		}1069	}1070}10711072impl<T: Config> Pallet<T> {1073	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1074		let key = rmrk_key.to_key::<T>()?;10751076		let scoped_key = PropertyScope::Rmrk1077			.apply(key)1078			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10791080		Ok(scoped_key)1081	}10821083	// todo think about renaming these1084	pub fn rmrk_property<E: Encode>(1085		rmrk_key: RmrkProperty,1086		value: &E,1087	) -> Result<Property, DispatchError> {1088		let key = rmrk_key.to_key::<T>()?;10891090		let value = Self::encode_property(value)?;10911092		let property = Property { key, value };10931094		Ok(property)1095	}10961097	pub fn encode_property<E: Encode, S: Get<u32>>(1098		value: &E,1099	) -> Result<BoundedBytes<S>, DispatchError> {1100		let value = value1101			.encode()1102			.try_into()1103			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;11041105		Ok(value)1106	}11071108	pub fn decode_property<D: Decode, S: Get<u32>>(1109		vec: &BoundedBytes<S>,1110	) -> Result<D, DispatchError> {1111		vec.decode()1112			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113	}11141115	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116	where1117		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1118	{1119		vec.rebind()1120			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121	}11221123	fn init_collection(1124		sender: T::CrossAccountId,1125		data: CreateCollectionData<T::AccountId>,1126		properties: impl Iterator<Item = Property>,1127	) -> Result<CollectionId, DispatchError> {1128		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11291130		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1131			return Err(<Error<T>>::NoAvailableCollectionId.into());1132		}11331134		<PalletCommon<T>>::set_scoped_collection_properties(1135			collection_id?,1136			PropertyScope::Rmrk,1137			properties,1138		)?;11391140		collection_id1141	}11421143	pub fn create_nft(1144		sender: &T::CrossAccountId,1145		owner: &T::CrossAccountId,1146		collection: &NonfungibleHandle<T>,1147		properties: impl Iterator<Item = Property>,1148	) -> Result<TokenId, DispatchError> {1149		let data = CreateNftExData {1150			properties: BoundedVec::default(),1151			owner: owner.clone(),1152		};11531154		let budget = budget::Value::new(NESTING_BUDGET);11551156		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11571158		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11591160		<PalletNft<T>>::set_scoped_token_properties(1161			collection.id,1162			nft_id,1163			PropertyScope::Rmrk,1164			properties,1165		)?;11661167		Ok(nft_id)1168	}11691170	fn destroy_nft(1171		sender: T::CrossAccountId,1172		collection_id: CollectionId,1173		token_id: TokenId,1174		max_burns: u32,1175		error_if_not_owned: Error<T>,1176	) -> DispatchResultWithPostInfo {1177		let collection =1178			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11791180		let token_data =1181			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11821183		let from = token_data.owner;11841185		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11861187		ensure!(1188			<PalletStructure<T>>::check_indirectly_owned(1189				sender.clone(),1190				collection_id,1191				token_id,1192				None,1193				&owner_check_budget1194			)?,1195			error_if_not_owned,1196		);11971198		let burns_budget = budget::Value::new(max_burns);1199		let breadth_budget = budget::Value::new(max_burns);12001201		<PalletNft<T>>::burn_recursively(1202			&collection,1203			&from,1204			token_id,1205			&burns_budget,1206			&breadth_budget,1207		)1208	}12091210	fn insert_pending_child(1211		target: (CollectionId, TokenId),1212		child: (RmrkCollectionId, RmrkNftId),1213	) -> DispatchResult {1214		Self::mutate_pending_child(target, |pending_children| {1215			pending_children.insert(child);1216		})1217	}12181219	fn remove_pending_child(1220		target: (CollectionId, TokenId),1221		child: (RmrkCollectionId, RmrkNftId),1222	) -> DispatchResult {1223		Self::mutate_pending_child(target, |pending_children| {1224			pending_children.remove(&child);1225		})1226	}12271228	fn mutate_pending_child(1229		(target_collection_id, target_nft_id): (CollectionId, TokenId),1230		f: impl FnOnce(&mut PendingChildrenSet),1231	) -> DispatchResult {1232		<PalletNft<T>>::try_mutate_token_aux_property(1233			target_collection_id,1234			target_nft_id,1235			PropertyScope::Rmrk,1236			Self::rmrk_property_key(PendingChildren)?,1237			|pending_children| -> DispatchResult {1238				let mut map = match pending_children {1239					Some(map) => Self::decode_property(map)?,1240					None => PendingChildrenSet::new(),1241				};12421243				f(&mut map);12441245				*pending_children = Some(Self::encode_property(&map)?);12461247				Ok(())1248			},1249		)1250	}12511252	fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1253		let property = <PalletNft<T>>::token_aux_property((1254			collection_id,1255			nft_id,1256			PropertyScope::Rmrk,1257			Self::rmrk_property_key(PendingChildren)?1258		));12591260		let pending_children = match property {1261			Some(map) => Self::decode_property(&map)?,1262			None => PendingChildrenSet::new(),1263		};12641265		Ok(pending_children.into_iter())1266	}12671268	fn acquire_next_resource_id(1269		collection_id: CollectionId,1270		nft_id: TokenId,1271	) -> Result<RmrkResourceId, DispatchError> {1272		let resource_id: RmrkResourceId =1273			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12741275		let next_id = resource_id1276			.checked_add(1)1277			.ok_or(<Error<T>>::NoAvailableResourceId)?;12781279		<PalletNft<T>>::set_scoped_token_property(1280			collection_id,1281			nft_id,1282			PropertyScope::Rmrk,1283			Self::rmrk_property(NextResourceId, &next_id)?,1284		)?;12851286		Ok(resource_id)1287	}12881289	fn resource_add(1290		sender: T::AccountId,1291		collection_id: CollectionId,1292		nft_id: TokenId,1293		resource: RmrkResourceTypes,1294	) -> Result<RmrkResourceId, DispatchError> {1295		let collection =1296			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1297		ensure!(collection.owner == sender, Error::<T>::NoPermission);12981299		let sender = T::CrossAccountId::from_sub(sender);1300		let budget = budget::Value::new(NESTING_BUDGET);13011302		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1303			.map_err(Self::map_unique_err_to_proxy)?;13041305		let pending = sender != nft_owner;13061307		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13081309		let resource_info = RmrkResourceInfo {1310			id,1311			resource,1312			pending,1313			pending_removal: false,1314		};13151316		<PalletNft<T>>::try_mutate_token_aux_property(1317			collection_id,1318			nft_id,1319			PropertyScope::Rmrk,1320			Self::rmrk_property_key(ResourceId(id))?,1321			|value| -> DispatchResult {1322				*value = Some(Self::encode_property(&resource_info)?);13231324				Ok(())1325			},1326		)?;13271328		Ok(id)1329	}13301331	fn resource_remove(1332		sender: T::AccountId,1333		collection_id: CollectionId,1334		nft_id: TokenId,1335		resource_id: RmrkResourceId,1336	) -> DispatchResult {1337		let collection =1338			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1339		ensure!(collection.owner == sender, Error::<T>::NoPermission);13401341		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1342		let scope = PropertyScope::Rmrk;13431344		let resource = <PalletNft<T>>::token_aux_property((1345			collection_id,1346			nft_id,1347			scope,1348			resource_id_key.clone()1349		)).ok_or(<Error<T>>::ResourceDoesntExist)?;13501351		let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13521353		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1354		let topmost_owner =1355			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13561357		let sender = T::CrossAccountId::from_sub(sender);1358		if topmost_owner == sender {1359			<PalletNft<T>>::remove_token_aux_property(1360				collection_id,1361				nft_id,1362				PropertyScope::Rmrk,1363				Self::rmrk_property_key(ResourceId(resource_id))?,1364			);13651366			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1367				let base_id = resource.base;13681369				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1370			}1371		} else {1372			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1373				res.pending_removal = true;13741375				Ok(())1376			})?;1377		}13781379		Ok(())1380	}13811382	fn remove_associated_base_id(1383		collection_id: CollectionId,1384		nft_id: TokenId,1385		base_id: RmrkBaseId,1386	) -> DispatchResult {1387		<PalletNft<T>>::try_mutate_token_aux_property(1388			collection_id,1389			nft_id,1390			PropertyScope::Rmrk,1391			Self::rmrk_property_key(AssociatedBases)?,1392			|value| -> DispatchResult {1393				let mut bases: BasesMap = match value {1394					Some(value) => Self::decode_property(value)?,1395					None => BasesMap::new()1396				};13971398				let remaining = bases.get(&base_id);13991400				if let Some(remaining) = remaining {1401					if let Some(0) | None = remaining.checked_sub(1) {1402						bases.remove(&base_id);1403					}1404				}14051406				*value = Some(Self::encode_property(&bases)?);1407				Ok(())1408			}1409		)1410	}14111412	fn try_mutate_resource_info(1413		collection_id: CollectionId,1414		nft_id: TokenId,1415		resource_id: RmrkResourceId,1416		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1417	) -> DispatchResult {1418		<PalletNft<T>>::try_mutate_token_aux_property(1419			collection_id,1420			nft_id,1421			PropertyScope::Rmrk,1422			Self::rmrk_property_key(ResourceId(resource_id))?,1423			|value| match value {1424				Some(value) => {1425					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14261427					f(&mut resource_info)?;14281429					*value = Self::encode_property(&resource_info)?;14301431					Ok(())1432				}1433				None => Err(<Error<T>>::ResourceDoesntExist.into()),1434			},1435		)1436	}14371438	fn change_collection_owner(1439		collection_id: CollectionId,1440		collection_type: misc::CollectionType,1441		sender: T::AccountId,1442		new_owner: T::AccountId,1443	) -> DispatchResult {1444		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1445		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14461447		let mut collection = collection.into_inner();14481449		collection.owner = new_owner;1450		collection.save()1451	}14521453	pub fn check_collection_owner(1454		collection: &NonfungibleHandle<T>,1455		account: &T::CrossAccountId,1456	) -> DispatchResult {1457		collection1458			.check_is_owner(account)1459			.map_err(Self::map_unique_err_to_proxy)1460	}14611462	pub fn last_collection_idx() -> RmrkCollectionId {1463		<CollectionIndex<T>>::get()1464	}14651466	pub fn unique_collection_id(1467		rmrk_collection_id: RmrkCollectionId,1468	) -> Result<CollectionId, DispatchError> {1469		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1470			.map_err(|_| <Error<T>>::CollectionUnknown.into())1471	}14721473	pub fn rmrk_collection_id(1474		unique_collection_id: CollectionId,1475	) -> Result<RmrkCollectionId, DispatchError> {1476		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1477	}14781479	pub fn get_nft_collection(1480		collection_id: CollectionId,1481	) -> Result<NonfungibleHandle<T>, DispatchError> {1482		let collection = <CollectionHandle<T>>::try_get(collection_id)1483			.map_err(|_| <Error<T>>::CollectionUnknown)?;14841485		match collection.mode {1486			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1487			_ => Err(<Error<T>>::CollectionUnknown.into()),1488		}1489	}14901491	pub fn collection_exists(collection_id: CollectionId) -> bool {1492		<CollectionHandle<T>>::try_get(collection_id).is_ok()1493	}14941495	pub fn get_collection_property(1496		collection_id: CollectionId,1497		key: RmrkProperty,1498	) -> Result<PropertyValue, DispatchError> {1499		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1500			.get(&Self::rmrk_property_key(key)?)1501			.ok_or(<Error<T>>::CollectionUnknown)?1502			.clone();15031504		Ok(collection_property)1505	}15061507	pub fn get_collection_property_decoded<V: Decode>(1508		collection_id: CollectionId,1509		key: RmrkProperty,1510	) -> Result<V, DispatchError> {1511		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1512	}15131514	pub fn get_collection_type(1515		collection_id: CollectionId,1516	) -> Result<misc::CollectionType, DispatchError> {1517		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1518			if err != <Error<T>>::CollectionUnknown.into() {1519				<Error<T>>::CorruptedCollectionType.into()1520			} else {1521				err1522			}1523		})1524	}15251526	pub fn ensure_collection_type(1527		collection_id: CollectionId,1528		collection_type: misc::CollectionType,1529	) -> DispatchResult {1530		let actual_type = Self::get_collection_type(collection_id)?;1531		ensure!(1532			actual_type == collection_type,1533			<CommonError<T>>::NoPermission1534		);15351536		Ok(())1537	}15381539	pub fn get_typed_nft_collection(1540		collection_id: CollectionId,1541		collection_type: misc::CollectionType,1542	) -> Result<NonfungibleHandle<T>, DispatchError> {1543		Self::ensure_collection_type(collection_id, collection_type)?;15441545		Self::get_nft_collection(collection_id)1546	}15471548	pub fn get_typed_nft_collection_mapped(1549		rmrk_collection_id: RmrkCollectionId,1550		collection_type: misc::CollectionType,1551	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1552		let unique_collection_id = match collection_type {1553			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1554			_ => rmrk_collection_id.into(),1555		};15561557		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15581559		Ok((collection, unique_collection_id))1560	}15611562	pub fn get_nft_property(1563		collection_id: CollectionId,1564		nft_id: TokenId,1565		key: RmrkProperty,1566	) -> Result<PropertyValue, DispatchError> {1567		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1568			.get(&Self::rmrk_property_key(key)?)1569			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1570			.clone();15711572		Ok(nft_property)1573	}15741575	pub fn get_nft_property_decoded<V: Decode>(1576		collection_id: CollectionId,1577		nft_id: TokenId,1578		key: RmrkProperty,1579	) -> Result<V, DispatchError> {1580		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1581	}15821583	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1584		<TokenData<T>>::contains_key((collection_id, nft_id))1585	}15861587	pub fn get_nft_type(1588		collection_id: CollectionId,1589		token_id: TokenId,1590	) -> Result<NftType, DispatchError> {1591		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1592			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1593	}15941595	pub fn ensure_nft_type(1596		collection_id: CollectionId,1597		token_id: TokenId,1598		nft_type: NftType,1599	) -> DispatchResult {1600		let actual_type = Self::get_nft_type(collection_id, token_id)?;1601		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16021603		Ok(())1604	}16051606	pub fn ensure_nft_owner(1607		collection_id: CollectionId,1608		token_id: TokenId,1609		possible_owner: &T::CrossAccountId,1610		nesting_budget: &dyn budget::Budget,1611	) -> DispatchResult {1612		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1613			possible_owner.clone(),1614			collection_id,1615			token_id,1616			None,1617			nesting_budget,1618		)1619		.map_err(Self::map_unique_err_to_proxy)?;16201621		ensure!(is_owned, <Error<T>>::NoPermission);16221623		Ok(())1624	}16251626	pub fn filter_user_properties<Key, Value, R, Mapper>(1627		collection_id: CollectionId,1628		token_id: Option<TokenId>,1629		filter_keys: Option<Vec<RmrkPropertyKey>>,1630		mapper: Mapper,1631	) -> Result<Vec<R>, DispatchError>1632	where1633		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1634		Value: Decode + Default,1635		Mapper: Fn(Key, Value) -> R,1636	{1637		filter_keys1638			.map(|keys| {1639				let properties = keys1640					.into_iter()1641					.filter_map(|key| {1642						let key: Key = key.try_into().ok()?;16431644						let value = match token_id {1645							Some(token_id) => Self::get_nft_property_decoded(1646								collection_id,1647								token_id,1648								UserProperty(key.as_ref()),1649							),1650							None => Self::get_collection_property_decoded(1651								collection_id,1652								UserProperty(key.as_ref()),1653							),1654						}1655						.ok()?;16561657						Some(mapper(key, value))1658					})1659					.collect();16601661				Ok(properties)1662			})1663			.unwrap_or_else(|| {1664				let properties =1665					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16661667				Ok(properties)1668			})1669	}16701671	pub fn iterate_user_properties<Key, Value, R, Mapper>(1672		collection_id: CollectionId,1673		token_id: Option<TokenId>,1674		mapper: Mapper,1675	) -> Result<impl Iterator<Item = R>, DispatchError>1676	where1677		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1678		Value: Decode + Default,1679		Mapper: Fn(Key, Value) -> R,1680	{1681		let properties = match token_id {1682			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1683			None => <PalletCommon<T>>::collection_properties(collection_id),1684		};16851686		let properties = properties.into_iter().filter_map(move |(key, value)| {1687			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16881689			let key: Key = key.to_vec().try_into().ok()?;1690			let value: Value = value.decode().ok()?;16911692			Some(mapper(key, value))1693		});16941695		Ok(properties)1696	}16971698	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1699		map_unique_err_to_proxy! {1700			match err {1701				CommonError::NoPermission => NoPermission,1702				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1703				CommonError::PublicMintingNotAllowed => NoPermission,1704				CommonError::TokenNotFound => NoAvailableNftId,1705				CommonError::ApprovedValueTooLow => NoPermission,1706				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1707				StructureError::TokenNotFound => NoAvailableNftId,1708				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1709			}1710		}1711	}1712}
modifiedpallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -34,6 +34,7 @@
 	ResourceId(RmrkResourceId),
 	PendingNftAccept,
 	PendingChildren,
+	AssociatedBases,
 	Parts,
 	Base,
 	Src,
@@ -77,6 +78,7 @@
 			Self::ResourceId(id) => key!(RESOURCE_ID_PREFIX, id.to_le_bytes()),
 			Self::PendingNftAccept => key!("pending-nft-accept"),
 			Self::PendingChildren => key!("pending-children"),
+			Self::AssociatedBases => key!("assoc-bases"),
 			Self::Parts => key!("parts"),
 			Self::Base => key!("base"),
 			Self::Src => key!("src"),