git.delta.rocks / unique-network / refs/commits / 2999bc8acc0d

difftreelog

fix(rmrk) accept/remove resources

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

1 file changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133	}134135	#[pallet::error]136	pub enum Error<T> {137		/* Unique-specific events */138		CorruptedCollectionType,139		NftTypeEncodeError,140		RmrkPropertyKeyIsTooLong,141		RmrkPropertyValueIsTooLong,142143		/* RMRK compatible events */144		CollectionNotEmpty,145		NoAvailableCollectionId,146		NoAvailableNftId,147		CollectionUnknown,148		NoPermission,149		NonTransferable,150		CollectionFullOrLocked,151		ResourceDoesntExist,152		CannotSendToDescendentOrSelf,153		CannotAcceptNonOwnedNft,154		CannotRejectNonOwnedNft,155		ResourceNotPending,156	}157158	#[pallet::call]159	impl<T: Config> Pallet<T> {160		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]161		#[transactional]162		pub fn create_collection(163			origin: OriginFor<T>,164			metadata: RmrkString,165			max: Option<u32>,166			symbol: RmrkCollectionSymbol,167		) -> DispatchResult {168			let sender = ensure_signed(origin)?;169170			let limits = CollectionLimits {171				owner_can_transfer: Some(false),172				token_limit: max,173				..Default::default()174			};175176			let data = CreateCollectionData {177				limits: Some(limits),178				token_prefix: symbol179					.into_inner()180					.try_into()181					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,182				permissions: Some(CollectionPermissions {183					nesting: Some(NestingRule::Owner),184					..Default::default()185				}),186				..Default::default()187			};188189			let unique_collection_id = Self::init_collection(190				T::CrossAccountId::from_sub(sender.clone()),191				data,192				[193					Self::rmrk_property(Metadata, &metadata)?,194					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,195				]196				.into_iter(),197			)?;198			let rmrk_collection_id = <CollectionIndex<T>>::get();199200			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);201			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);202203			<CollectionIndex<T>>::mutate(|n| *n += 1);204205			Self::deposit_event(Event::CollectionCreated {206				issuer: sender,207				collection_id: rmrk_collection_id,208			});209210			Ok(())211		}212213		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]214		#[transactional]215		pub fn destroy_collection(216			origin: OriginFor<T>,217			collection_id: RmrkCollectionId,218		) -> DispatchResult {219			let sender = ensure_signed(origin)?;220			let cross_sender = T::CrossAccountId::from_sub(sender.clone());221222			let collection = Self::get_typed_nft_collection(223				Self::unique_collection_id(collection_id)?,224				misc::CollectionType::Regular,225			)?;226227			<PalletNft<T>>::destroy_collection(collection, &cross_sender)228				.map_err(Self::map_unique_err_to_proxy)?;229230			Self::deposit_event(Event::CollectionDestroyed {231				issuer: sender,232				collection_id,233			});234235			Ok(())236		}237238		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]239		#[transactional]240		pub fn change_collection_issuer(241			origin: OriginFor<T>,242			collection_id: RmrkCollectionId,243			new_issuer: <T::Lookup as StaticLookup>::Source,244		) -> DispatchResult {245			let sender = ensure_signed(origin)?;246247			let new_issuer = T::Lookup::lookup(new_issuer)?;248249			Self::change_collection_owner(250				Self::unique_collection_id(collection_id)?,251				misc::CollectionType::Regular,252				sender.clone(),253				new_issuer.clone(),254			)?;255256			Self::deposit_event(Event::IssuerChanged {257				old_issuer: sender,258				new_issuer,259				collection_id,260			});261262			Ok(())263		}264265		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]266		#[transactional]267		pub fn lock_collection(268			origin: OriginFor<T>,269			collection_id: RmrkCollectionId,270		) -> DispatchResult {271			let sender = ensure_signed(origin)?;272			let cross_sender = T::CrossAccountId::from_sub(sender.clone());273274			let collection = Self::get_typed_nft_collection(275				Self::unique_collection_id(collection_id)?,276				misc::CollectionType::Regular,277			)?;278279			Self::check_collection_owner(&collection, &cross_sender)?;280281			let token_count = collection.total_supply();282283			let mut collection = collection.into_inner();284			collection.limits.token_limit = Some(token_count);285			collection.save()?;286287			Self::deposit_event(Event::CollectionLocked {288				issuer: sender,289				collection_id,290			});291292			Ok(())293		}294295		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]296		#[transactional]297		pub fn mint_nft(298			origin: OriginFor<T>,299			owner: T::AccountId,300			collection_id: RmrkCollectionId,301			recipient: Option<T::AccountId>,302			royalty_amount: Option<Permill>,303			metadata: RmrkString,304			transferable: bool,305		) -> DispatchResult {306			let sender = ensure_signed(origin)?;307			let sender = T::CrossAccountId::from_sub(sender);308			let cross_owner = T::CrossAccountId::from_sub(owner.clone());309310			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {311				recipient: recipient.unwrap_or_else(|| owner.clone()),312				amount,313			});314315			let collection = Self::get_typed_nft_collection(316				Self::unique_collection_id(collection_id)?,317				misc::CollectionType::Regular,318			)?;319320			let nft_id = Self::create_nft(321				&sender,322				&cross_owner,323				&collection,324				[325					Self::rmrk_property(TokenType, &NftType::Regular)?,326					Self::rmrk_property(Transferable, &transferable)?,327					Self::rmrk_property(PendingNftAccept, &false)?,328					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,329					Self::rmrk_property(Metadata, &metadata)?,330					Self::rmrk_property(Equipped, &false)?,331					Self::rmrk_property(332						ResourceCollection,333						&Self::init_collection(334							sender.clone(),335							CreateCollectionData {336								..Default::default()337							},338							[Self::rmrk_property(339								CollectionType,340								&misc::CollectionType::Resource,341							)?]342							.into_iter(),343						)?,344					)?, // todo possibly add limits to the collection if rmrk warrants them345					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,346				]347				.into_iter(),348			)349			.map_err(|err| match err {350				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),351				err => Self::map_unique_err_to_proxy(err),352			})?;353354			Self::deposit_event(Event::NftMinted {355				owner,356				collection_id,357				nft_id: nft_id.0,358			});359360			Ok(())361		}362363		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]364		#[transactional]365		pub fn burn_nft(366			origin: OriginFor<T>,367			collection_id: RmrkCollectionId,368			nft_id: RmrkNftId,369		) -> DispatchResult {370			let sender = ensure_signed(origin)?;371			let cross_sender = T::CrossAccountId::from_sub(sender.clone());372373			Self::destroy_nft(374				cross_sender,375				Self::unique_collection_id(collection_id)?,376				nft_id.into(),377			)378			.map_err(Self::map_unique_err_to_proxy)?;379380			Self::deposit_event(Event::NFTBurned {381				owner: sender,382				nft_id,383			});384385			Ok(())386		}387388		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]389		#[transactional]390		pub fn send(391			origin: OriginFor<T>,392			rmrk_collection_id: RmrkCollectionId,393			rmrk_nft_id: RmrkNftId,394			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,395		) -> DispatchResult {396			let sender = ensure_signed(origin.clone())?;397			let cross_sender = T::CrossAccountId::from_sub(sender.clone());398399			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;400			let nft_id = rmrk_nft_id.into();401402			let token_data =403				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;404405			let from = token_data.owner;406407			let collection =408				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;409410			ensure!(411				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,412				<Error<T>>::NonTransferable413			);414415			ensure!(416				!Self::get_nft_property_decoded(417					collection_id,418					nft_id,419					RmrkProperty::PendingNftAccept420				)?,421				<Error<T>>::NoPermission422			);423424			let target_owner;425			let approval_required;426427			match new_owner {428				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {429					target_owner = T::CrossAccountId::from_sub(account_id.clone());430					approval_required = false;431				}432				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(433					target_collection_id,434					target_nft_id,435				) => {436					let target_collection_id = Self::unique_collection_id(target_collection_id)?;437438					let target_nft_budget = budget::Value::new(NESTING_BUDGET);439440					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(441						target_collection_id,442						target_nft_id.into(),443						Some((collection_id, nft_id)),444						&target_nft_budget,445					)446					.map_err(Self::map_unique_err_to_proxy)?;447448					approval_required = cross_sender != target_nft_owner;449450					if approval_required {451						target_owner = target_nft_owner;452453						<PalletNft<T>>::set_scoped_token_property(454							collection.id,455							nft_id,456							PropertyScope::Rmrk,457							Self::rmrk_property(PendingNftAccept, &approval_required)?,458						)?;459					} else {460						target_owner = T::CrossTokenAddressMapping::token_to_address(461							target_collection_id,462							target_nft_id.into(),463						);464					}465				}466			}467468			let src_nft_budget = budget::Value::new(NESTING_BUDGET);469470			<PalletNft<T>>::transfer_from(471				&collection,472				&cross_sender,473				&from,474				&target_owner,475				nft_id,476				&src_nft_budget,477			)478			.map_err(Self::map_unique_err_to_proxy)?;479480			Self::deposit_event(Event::NFTSent {481				sender,482				recipient: new_owner,483				collection_id: rmrk_collection_id,484				nft_id: rmrk_nft_id,485				approval_required,486			});487488			Ok(())489		}490491		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]492		#[transactional]493		pub fn accept_nft(494			origin: OriginFor<T>,495			rmrk_collection_id: RmrkCollectionId,496			rmrk_nft_id: RmrkNftId,497			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,498		) -> DispatchResult {499			let sender = ensure_signed(origin.clone())?;500			let cross_sender = T::CrossAccountId::from_sub(sender.clone());501502			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;503			let nft_id = rmrk_nft_id.into();504505			let collection =506				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;507508			let new_cross_owner = match new_owner {509				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {510					T::CrossAccountId::from_sub(account_id.clone())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					T::CrossTokenAddressMapping::token_to_address(519						target_collection_id,520						TokenId(target_nft_id),521					)522				}523			};524525			let budget = budget::Value::new(NESTING_BUDGET);526527			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)528				.map_err(|err| {529					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {530						<Error<T>>::CannotAcceptNonOwnedNft.into()531					} else {532						Self::map_unique_err_to_proxy(err)533					}534				})?;535536			<PalletNft<T>>::set_scoped_token_property(537				collection.id,538				nft_id,539				PropertyScope::Rmrk,540				Self::rmrk_property(PendingNftAccept, &false)?,541			)?;542543			Self::deposit_event(Event::NFTAccepted {544				sender,545				recipient: new_owner,546				collection_id: rmrk_collection_id,547				nft_id: rmrk_nft_id,548			});549550			Ok(())551		}552553		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]554		#[transactional]555		pub fn reject_nft(556			origin: OriginFor<T>,557			rmrk_collection_id: RmrkCollectionId,558			rmrk_nft_id: RmrkNftId,559		) -> DispatchResult {560			let sender = ensure_signed(origin)?;561			let cross_sender = T::CrossAccountId::from_sub(sender.clone());562563			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;564			let nft_id = rmrk_nft_id.into();565566			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {567				if err == <CommonError<T>>::NoPermission.into()568					|| err == <CommonError<T>>::ApprovedValueTooLow.into()569				{570					<Error<T>>::CannotRejectNonOwnedNft.into()571				} else {572					Self::map_unique_err_to_proxy(err)573				}574			})?;575576			Self::deposit_event(Event::NFTRejected {577				sender,578				collection_id: rmrk_collection_id,579				nft_id: rmrk_nft_id,580			});581582			Ok(())583		}584585		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]586		#[transactional]587		pub fn accept_resource(588			origin: OriginFor<T>,589			rmrk_collection_id: RmrkCollectionId,590			rmrk_nft_id: RmrkNftId,591			rmrk_resource_id: RmrkResourceId,592		) -> DispatchResult {593			let sender = ensure_signed(origin)?;594			let cross_sender = T::CrossAccountId::from_sub(sender);595596			let collection_id = Self::unique_collection_id(rmrk_collection_id)597				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;598599			let nft_id = rmrk_nft_id.into();600			let resource_id = rmrk_resource_id.into();601602			let budget = budget::Value::new(NESTING_BUDGET);603604			let nft_owner =605				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)606					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;607608			let resource_collection_id: CollectionId =609				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)610					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;611612			let is_pending: bool = Self::get_nft_property_decoded(613				resource_collection_id,614				resource_id,615				PendingResourceAccept,616			)617			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;618619			ensure!(is_pending, <Error<T>>::ResourceNotPending);620621			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);622623			<PalletNft<T>>::set_scoped_token_property(624				resource_collection_id,625				rmrk_resource_id.into(),626				PropertyScope::Rmrk,627				Self::rmrk_property(PendingResourceAccept, &false)?,628			)?;629630			Ok(())631		}632633		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]634		#[transactional]635		pub fn accept_resource_removal(636			origin: OriginFor<T>,637			rmrk_collection_id: RmrkCollectionId,638			rmrk_nft_id: RmrkNftId,639			rmrk_resource_id: RmrkResourceId,640		) -> DispatchResult {641			let sender = ensure_signed(origin)?;642			let cross_sender = T::CrossAccountId::from_sub(sender);643644			let collection_id = Self::unique_collection_id(rmrk_collection_id)645				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;646647			let nft_id = rmrk_nft_id.into();648			let resource_id = rmrk_resource_id.into();649650			let budget = budget::Value::new(NESTING_BUDGET);651652			let nft_owner =653				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)654					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;655656			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);657658			let resource_collection_id: CollectionId =659				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)660					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;661662			let is_pending: bool = Self::get_nft_property_decoded(663				resource_collection_id,664				resource_id,665				PendingResourceRemoval,666			)667			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;668669			ensure!(is_pending, <Error<T>>::ResourceNotPending);670671			let resource_collection = Self::get_typed_nft_collection(672				resource_collection_id,673				misc::CollectionType::Resource,674			)?;675676			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())677				.map_err(Self::map_unique_err_to_proxy)?;678679			Ok(())680		}681682		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]683		#[transactional]684		pub fn set_property(685			origin: OriginFor<T>,686			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,687			maybe_nft_id: Option<RmrkNftId>,688			key: RmrkKeyString,689			value: RmrkValueString,690		) -> DispatchResult {691			let sender = ensure_signed(origin)?;692			let sender = T::CrossAccountId::from_sub(sender);693694			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;695			let budget = budget::Value::new(NESTING_BUDGET);696697			match maybe_nft_id {698				Some(nft_id) => {699					let token_id: TokenId = nft_id.into();700701					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;702					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;703704					<PalletNft<T>>::set_scoped_token_property(705						collection_id,706						token_id,707						PropertyScope::Rmrk,708						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,709					)?;710				}711				None => {712					let collection = Self::get_typed_nft_collection(713						collection_id,714						misc::CollectionType::Regular,715					)?;716717					Self::check_collection_owner(&collection, &sender)?;718719					<PalletCommon<T>>::set_scoped_collection_property(720						collection_id,721						PropertyScope::Rmrk,722						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,723					)?;724				}725			}726727			Self::deposit_event(Event::PropertySet {728				collection_id: rmrk_collection_id,729				maybe_nft_id,730				key,731				value,732			});733734			Ok(())735		}736737		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]738		#[transactional]739		pub fn set_priority(740			origin: OriginFor<T>,741			rmrk_collection_id: RmrkCollectionId,742			rmrk_nft_id: RmrkNftId,743			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,744		) -> DispatchResult {745			let sender = ensure_signed(origin)?;746			let sender = T::CrossAccountId::from_sub(sender);747748			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;749			let nft_id = rmrk_nft_id.into();750			let budget = budget::Value::new(NESTING_BUDGET);751752			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;753			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;754755			<PalletNft<T>>::set_scoped_token_property(756				collection_id,757				nft_id,758				PropertyScope::Rmrk,759				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,760			)?;761762			Ok(())763		}764765		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]766		#[transactional]767		pub fn add_basic_resource(768			origin: OriginFor<T>,769			collection_id: RmrkCollectionId,770			nft_id: RmrkNftId,771			resource: RmrkBasicResource,772		) -> DispatchResult {773			let sender = ensure_signed(origin.clone())?;774775			let resource_id = Self::resource_add(776				sender,777				Self::unique_collection_id(collection_id)?,778				nft_id.into(),779				[780					Self::rmrk_property(TokenType, &NftType::Resource)?,781					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,782					Self::rmrk_property(Src, &resource.src)?,783					Self::rmrk_property(Metadata, &resource.metadata)?,784					Self::rmrk_property(License, &resource.license)?,785					Self::rmrk_property(Thumb, &resource.thumb)?,786				]787				.into_iter(),788			)?;789790			Self::deposit_event(Event::ResourceAdded {791				nft_id,792				resource_id,793			});794			Ok(())795		}796797		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798		#[transactional]799		pub fn add_composable_resource(800			origin: OriginFor<T>,801			collection_id: RmrkCollectionId,802			nft_id: RmrkNftId,803			_resource_id: RmrkBoundedResource,804			resource: RmrkComposableResource,805		) -> DispatchResult {806			let sender = ensure_signed(origin.clone())?;807808			let resource_id = Self::resource_add(809				sender,810				Self::unique_collection_id(collection_id)?,811				nft_id.into(),812				[813					Self::rmrk_property(TokenType, &NftType::Resource)?,814					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,815					Self::rmrk_property(Parts, &resource.parts)?,816					Self::rmrk_property(Base, &resource.base)?,817					Self::rmrk_property(Src, &resource.src)?,818					Self::rmrk_property(Metadata, &resource.metadata)?,819					Self::rmrk_property(License, &resource.license)?,820					Self::rmrk_property(Thumb, &resource.thumb)?,821				]822				.into_iter(),823			)?;824825			Self::deposit_event(Event::ResourceAdded {826				nft_id,827				resource_id,828			});829			Ok(())830		}831832		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]833		#[transactional]834		pub fn add_slot_resource(835			origin: OriginFor<T>,836			collection_id: RmrkCollectionId,837			nft_id: RmrkNftId,838			resource: RmrkSlotResource,839		) -> DispatchResult {840			let sender = ensure_signed(origin.clone())?;841842			let resource_id = Self::resource_add(843				sender,844				Self::unique_collection_id(collection_id)?,845				nft_id.into(),846				[847					Self::rmrk_property(TokenType, &NftType::Resource)?,848					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,849					Self::rmrk_property(Base, &resource.base)?,850					Self::rmrk_property(Src, &resource.src)?,851					Self::rmrk_property(Metadata, &resource.metadata)?,852					Self::rmrk_property(Slot, &resource.slot)?,853					Self::rmrk_property(License, &resource.license)?,854					Self::rmrk_property(Thumb, &resource.thumb)?,855				]856				.into_iter(),857			)?;858859			Self::deposit_event(Event::ResourceAdded {860				nft_id,861				resource_id,862			});863			Ok(())864		}865866		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]867		#[transactional]868		pub fn remove_resource(869			origin: OriginFor<T>,870			collection_id: RmrkCollectionId,871			nft_id: RmrkNftId,872			resource_id: RmrkResourceId,873		) -> DispatchResult {874			let sender = ensure_signed(origin.clone())?;875876			Self::resource_remove(877				sender,878				Self::unique_collection_id(collection_id)?,879				nft_id.into(),880				resource_id.into(),881			)?;882883			Self::deposit_event(Event::ResourceRemoval {884				nft_id,885				resource_id,886			});887			Ok(())888		}889	}890}891892impl<T: Config> Pallet<T> {893	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {894		let key = rmrk_key.to_key::<T>()?;895896		let scoped_key = PropertyScope::Rmrk897			.apply(key)898			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;899900		Ok(scoped_key)901	}902903	// todo think about renaming these904	pub fn rmrk_property<E: Encode>(905		rmrk_key: RmrkProperty,906		value: &E,907	) -> Result<Property, DispatchError> {908		let key = rmrk_key.to_key::<T>()?;909910		let value = value911			.encode()912			.try_into()913			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;914915		let property = Property { key, value };916917		Ok(property)918	}919920	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {921		vec.decode()922			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())923	}924925	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>926	where927		BoundedVec<u8, S>: TryFrom<Vec<u8>>,928	{929		vec.rebind()930			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())931	}932933	fn init_collection(934		sender: T::CrossAccountId,935		data: CreateCollectionData<T::AccountId>,936		properties: impl Iterator<Item = Property>,937	) -> Result<CollectionId, DispatchError> {938		let collection_id = <PalletNft<T>>::init_collection(sender, data);939940		if let Err(DispatchError::Arithmetic(_)) = &collection_id {941			return Err(<Error<T>>::NoAvailableCollectionId.into());942		}943944		<PalletCommon<T>>::set_scoped_collection_properties(945			collection_id?,946			PropertyScope::Rmrk,947			properties,948		)?;949950		collection_id951	}952953	pub fn create_nft(954		sender: &T::CrossAccountId,955		owner: &T::CrossAccountId,956		collection: &NonfungibleHandle<T>,957		properties: impl Iterator<Item = Property>,958	) -> Result<TokenId, DispatchError> {959		let data = CreateNftExData {960			properties: BoundedVec::default(),961			owner: owner.clone(),962		};963964		let budget = budget::Value::new(NESTING_BUDGET);965966		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;967968		let nft_id = <PalletNft<T>>::current_token_id(collection.id);969970		<PalletNft<T>>::set_scoped_token_properties(971			collection.id,972			nft_id,973			PropertyScope::Rmrk,974			properties,975		)?;976977		Ok(nft_id)978	}979980	fn destroy_nft(981		sender: T::CrossAccountId,982		collection_id: CollectionId,983		token_id: TokenId,984	) -> DispatchResult {985		let collection =986			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987988		let token_data =989			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;990991		let from = token_data.owner;992993		let budget = budget::Value::new(NESTING_BUDGET);994995		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)996	}997998	fn resource_add(999		sender: T::AccountId,1000		collection_id: CollectionId,1001		token_id: TokenId,1002		resource_properties: impl Iterator<Item = Property>,1003	) -> Result<RmrkResourceId, DispatchError> {1004		let collection =1005			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1006		ensure!(collection.owner == sender, Error::<T>::NoPermission);10071008		let sender = T::CrossAccountId::from_sub(sender);1009		let budget = budget::Value::new(NESTING_BUDGET);10101011		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1012			.map_err(Self::map_unique_err_to_proxy)?;10131014		let pending = sender != nft_owner;10151016		let resource_collection_id: CollectionId =1017			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1018		let resource_collection =1019			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10201021		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10221023		let resource_id = Self::create_nft(1024			&sender,1025			&nft_owner,1026			&resource_collection,1027			resource_properties.chain(1028				[1029					Self::rmrk_property(PendingResourceAccept, &pending)?,1030					Self::rmrk_property(PendingResourceRemoval, &false)?,1031				]1032				.into_iter(),1033			),1034		)1035		.map_err(|err| match err {1036			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1037			err => Self::map_unique_err_to_proxy(err),1038		})?;10391040		Ok(resource_id.0)1041	}10421043	fn resource_remove(1044		sender: T::AccountId,1045		collection_id: CollectionId,1046		nft_id: TokenId,1047		resource_id: TokenId,1048	) -> DispatchResult {1049		let collection =1050			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1051		ensure!(collection.owner == sender, Error::<T>::NoPermission);10521053		let resource_collection_id: CollectionId =1054			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1055		let resource_collection =1056			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1057		ensure!(1058			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1059			Error::<T>::ResourceDoesntExist1060		);10611062		let budget = up_data_structs::budget::Value::new(10);1063		let topmost_owner =1064			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10651066		let sender = T::CrossAccountId::from_sub(sender);1067		if topmost_owner == sender {1068			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1069				.map_err(Self::map_unique_err_to_proxy)?;1070		} else {1071			<PalletNft<T>>::set_scoped_token_property(1072				resource_collection_id,1073				resource_id,1074				PropertyScope::Rmrk,1075				Self::rmrk_property(PendingResourceRemoval, &true)?,1076			)?;1077		}10781079		Ok(())1080	}10811082	fn change_collection_owner(1083		collection_id: CollectionId,1084		collection_type: misc::CollectionType,1085		sender: T::AccountId,1086		new_owner: T::AccountId,1087	) -> DispatchResult {1088		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1089		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10901091		let mut collection = collection.into_inner();10921093		collection.owner = new_owner;1094		collection.save()1095	}10961097	fn check_collection_owner(1098		collection: &NonfungibleHandle<T>,1099		account: &T::CrossAccountId,1100	) -> DispatchResult {1101		collection1102			.check_is_owner(account)1103			.map_err(Self::map_unique_err_to_proxy)1104	}11051106	pub fn last_collection_idx() -> RmrkCollectionId {1107		<CollectionIndex<T>>::get()1108	}11091110	pub fn unique_collection_id(1111		rmrk_collection_id: RmrkCollectionId,1112	) -> Result<CollectionId, DispatchError> {1113		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1114			.map_err(|_| <Error<T>>::CollectionUnknown.into())1115	}11161117	pub fn rmrk_collection_id(1118		unique_collection_id: CollectionId,1119	) -> Result<RmrkCollectionId, DispatchError> {1120		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1121			.map_err(|_| <Error<T>>::CollectionUnknown.into())1122	}11231124	pub fn get_nft_collection(1125		collection_id: CollectionId,1126	) -> Result<NonfungibleHandle<T>, DispatchError> {1127		let collection = <CollectionHandle<T>>::try_get(collection_id)1128			.map_err(|_| <Error<T>>::CollectionUnknown)?;11291130		match collection.mode {1131			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1132			_ => Err(<Error<T>>::CollectionUnknown.into()),1133		}1134	}11351136	pub fn collection_exists(collection_id: CollectionId) -> bool {1137		<CollectionHandle<T>>::try_get(collection_id).is_ok()1138	}11391140	pub fn get_collection_property(1141		collection_id: CollectionId,1142		key: RmrkProperty,1143	) -> Result<PropertyValue, DispatchError> {1144		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1145			.get(&Self::rmrk_property_key(key)?)1146			.ok_or(<Error<T>>::CollectionUnknown)?1147			.clone();11481149		Ok(collection_property)1150	}11511152	pub fn get_collection_property_decoded<V: Decode>(1153		collection_id: CollectionId,1154		key: RmrkProperty,1155	) -> Result<V, DispatchError> {1156		Self::decode_property(Self::get_collection_property(collection_id, key)?)1157	}11581159	pub fn get_collection_type(1160		collection_id: CollectionId,1161	) -> Result<misc::CollectionType, DispatchError> {1162		Self::get_collection_property_decoded(collection_id, CollectionType)1163			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1164	}11651166	pub fn ensure_collection_type(1167		collection_id: CollectionId,1168		collection_type: misc::CollectionType,1169	) -> DispatchResult {1170		let actual_type = Self::get_collection_type(collection_id)?;1171		ensure!(1172			actual_type == collection_type,1173			<CommonError<T>>::NoPermission1174		);11751176		Ok(())1177	}11781179	pub fn get_typed_nft_collection(1180		collection_id: CollectionId,1181		collection_type: misc::CollectionType,1182	) -> Result<NonfungibleHandle<T>, DispatchError> {1183		Self::ensure_collection_type(collection_id, collection_type)?;11841185		Self::get_nft_collection(collection_id)1186	}11871188	pub fn get_typed_nft_collection_mapped(1189		rmrk_collection_id: RmrkCollectionId,1190		collection_type: misc::CollectionType,1191	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1192		let unique_collection_id = match collection_type {1193			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1194			_ => rmrk_collection_id.into(),1195		};11961197		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11981199		Ok((collection, unique_collection_id))1200	}12011202	pub fn get_nft_property(1203		collection_id: CollectionId,1204		nft_id: TokenId,1205		key: RmrkProperty,1206	) -> Result<PropertyValue, DispatchError> {1207		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1208			.get(&Self::rmrk_property_key(key)?)1209			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1210			.clone();12111212		Ok(nft_property)1213	}12141215	pub fn get_nft_property_decoded<V: Decode>(1216		collection_id: CollectionId,1217		nft_id: TokenId,1218		key: RmrkProperty,1219	) -> Result<V, DispatchError> {1220		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1221	}12221223	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1224		<TokenData<T>>::contains_key((collection_id, nft_id))1225	}12261227	pub fn get_nft_type(1228		collection_id: CollectionId,1229		token_id: TokenId,1230	) -> Result<NftType, DispatchError> {1231		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1232			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1233	}12341235	pub fn ensure_nft_type(1236		collection_id: CollectionId,1237		token_id: TokenId,1238		nft_type: NftType,1239	) -> DispatchResult {1240		let actual_type = Self::get_nft_type(collection_id, token_id)?;1241		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12421243		Ok(())1244	}12451246	pub fn ensure_nft_owner(1247		collection_id: CollectionId,1248		token_id: TokenId,1249		possible_owner: &T::CrossAccountId,1250		nesting_budget: &dyn budget::Budget,1251	) -> DispatchResult {1252		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1253			possible_owner.clone(),1254			collection_id,1255			token_id,1256			None,1257			nesting_budget,1258		)1259		.map_err(Self::map_unique_err_to_proxy)?;12601261		ensure!(is_owned, <Error<T>>::NoPermission);12621263		Ok(())1264	}12651266	pub fn filter_user_properties<Key, Value, R, Mapper>(1267		collection_id: CollectionId,1268		token_id: Option<TokenId>,1269		filter_keys: Option<Vec<RmrkPropertyKey>>,1270		mapper: Mapper,1271	) -> Result<Vec<R>, DispatchError>1272	where1273		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1274		Value: Decode + Default,1275		Mapper: Fn(Key, Value) -> R,1276	{1277		filter_keys1278			.map(|keys| {1279				let properties = keys1280					.into_iter()1281					.filter_map(|key| {1282						let key: Key = key.try_into().ok()?;12831284						let value = match token_id {1285							Some(token_id) => Self::get_nft_property_decoded(1286								collection_id,1287								token_id,1288								UserProperty(key.as_ref()),1289							),1290							None => Self::get_collection_property_decoded(1291								collection_id,1292								UserProperty(key.as_ref()),1293							),1294						}1295						.ok()?;12961297						Some(mapper(key, value))1298					})1299					.collect();13001301				Ok(properties)1302			})1303			.unwrap_or_else(|| {1304				let properties =1305					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13061307				Ok(properties)1308			})1309	}13101311	pub fn iterate_user_properties<Key, Value, R, Mapper>(1312		collection_id: CollectionId,1313		token_id: Option<TokenId>,1314		mapper: Mapper,1315	) -> Result<impl Iterator<Item = R>, DispatchError>1316	where1317		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1318		Value: Decode + Default,1319		Mapper: Fn(Key, Value) -> R,1320	{1321		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13221323		let properties = match token_id {1324			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1325			None => <PalletCommon<T>>::collection_properties(collection_id),1326		};13271328		let properties = properties.into_iter().filter_map(move |(key, value)| {1329			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13301331			let key: Key = key.to_vec().try_into().ok()?;1332			let value: Value = value.decode().ok()?;13331334			Some(mapper(key, value))1335		});13361337		Ok(properties)1338	}13391340	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1341		map_unique_err_to_proxy! {1342			match err {1343				CommonError::NoPermission => NoPermission,1344				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1345				CommonError::PublicMintingNotAllowed => NoPermission,1346				CommonError::TokenNotFound => NoAvailableNftId,1347				CommonError::ApprovedValueTooLow => NoPermission,1348				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1349				StructureError::TokenNotFound => NoAvailableNftId,1350				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1351			}1352		}1353	}1354}
after · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141	}142143	#[pallet::error]144	pub enum Error<T> {145		/* Unique-specific events */146		CorruptedCollectionType,147		NftTypeEncodeError,148		RmrkPropertyKeyIsTooLong,149		RmrkPropertyValueIsTooLong,150151		/* RMRK compatible events */152		CollectionNotEmpty,153		NoAvailableCollectionId,154		NoAvailableNftId,155		CollectionUnknown,156		NoPermission,157		NonTransferable,158		CollectionFullOrLocked,159		ResourceDoesntExist,160		CannotSendToDescendentOrSelf,161		CannotAcceptNonOwnedNft,162		CannotRejectNonOwnedNft,163		ResourceNotPending,164	}165166	#[pallet::call]167	impl<T: Config> Pallet<T> {168		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]169		#[transactional]170		pub fn create_collection(171			origin: OriginFor<T>,172			metadata: RmrkString,173			max: Option<u32>,174			symbol: RmrkCollectionSymbol,175		) -> DispatchResult {176			let sender = ensure_signed(origin)?;177178			let limits = CollectionLimits {179				owner_can_transfer: Some(false),180				token_limit: max,181				..Default::default()182			};183184			let data = CreateCollectionData {185				limits: Some(limits),186				token_prefix: symbol187					.into_inner()188					.try_into()189					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,190				permissions: Some(CollectionPermissions {191					nesting: Some(NestingRule::Owner),192					..Default::default()193				}),194				..Default::default()195			};196197			let unique_collection_id = Self::init_collection(198				T::CrossAccountId::from_sub(sender.clone()),199				data,200				[201					Self::rmrk_property(Metadata, &metadata)?,202					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,203				]204				.into_iter(),205			)?;206			let rmrk_collection_id = <CollectionIndex<T>>::get();207208			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);209			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);210211			<CollectionIndex<T>>::mutate(|n| *n += 1);212213			Self::deposit_event(Event::CollectionCreated {214				issuer: sender,215				collection_id: rmrk_collection_id,216			});217218			Ok(())219		}220221		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]222		#[transactional]223		pub fn destroy_collection(224			origin: OriginFor<T>,225			collection_id: RmrkCollectionId,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228			let cross_sender = T::CrossAccountId::from_sub(sender.clone());229230			let collection = Self::get_typed_nft_collection(231				Self::unique_collection_id(collection_id)?,232				misc::CollectionType::Regular,233			)?;234235			<PalletNft<T>>::destroy_collection(collection, &cross_sender)236				.map_err(Self::map_unique_err_to_proxy)?;237238			Self::deposit_event(Event::CollectionDestroyed {239				issuer: sender,240				collection_id,241			});242243			Ok(())244		}245246		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]247		#[transactional]248		pub fn change_collection_issuer(249			origin: OriginFor<T>,250			collection_id: RmrkCollectionId,251			new_issuer: <T::Lookup as StaticLookup>::Source,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254255			let new_issuer = T::Lookup::lookup(new_issuer)?;256257			Self::change_collection_owner(258				Self::unique_collection_id(collection_id)?,259				misc::CollectionType::Regular,260				sender.clone(),261				new_issuer.clone(),262			)?;263264			Self::deposit_event(Event::IssuerChanged {265				old_issuer: sender,266				new_issuer,267				collection_id,268			});269270			Ok(())271		}272273		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]274		#[transactional]275		pub fn lock_collection(276			origin: OriginFor<T>,277			collection_id: RmrkCollectionId,278		) -> DispatchResult {279			let sender = ensure_signed(origin)?;280			let cross_sender = T::CrossAccountId::from_sub(sender.clone());281282			let collection = Self::get_typed_nft_collection(283				Self::unique_collection_id(collection_id)?,284				misc::CollectionType::Regular,285			)?;286287			Self::check_collection_owner(&collection, &cross_sender)?;288289			let token_count = collection.total_supply();290291			let mut collection = collection.into_inner();292			collection.limits.token_limit = Some(token_count);293			collection.save()?;294295			Self::deposit_event(Event::CollectionLocked {296				issuer: sender,297				collection_id,298			});299300			Ok(())301		}302303		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304		#[transactional]305		pub fn mint_nft(306			origin: OriginFor<T>,307			owner: T::AccountId,308			collection_id: RmrkCollectionId,309			recipient: Option<T::AccountId>,310			royalty_amount: Option<Permill>,311			metadata: RmrkString,312			transferable: bool,313		) -> DispatchResult {314			let sender = ensure_signed(origin)?;315			let sender = T::CrossAccountId::from_sub(sender);316			let cross_owner = T::CrossAccountId::from_sub(owner.clone());317318			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {319				recipient: recipient.unwrap_or_else(|| owner.clone()),320				amount,321			});322323			let collection = Self::get_typed_nft_collection(324				Self::unique_collection_id(collection_id)?,325				misc::CollectionType::Regular,326			)?;327328			let nft_id = Self::create_nft(329				&sender,330				&cross_owner,331				&collection,332				[333					Self::rmrk_property(TokenType, &NftType::Regular)?,334					Self::rmrk_property(Transferable, &transferable)?,335					Self::rmrk_property(PendingNftAccept, &false)?,336					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,337					Self::rmrk_property(Metadata, &metadata)?,338					Self::rmrk_property(Equipped, &false)?,339					Self::rmrk_property(340						ResourceCollection,341						&Self::init_collection(342							sender.clone(),343							CreateCollectionData {344								..Default::default()345							},346							[Self::rmrk_property(347								CollectionType,348								&misc::CollectionType::Resource,349							)?]350							.into_iter(),351						)?,352					)?, // todo possibly add limits to the collection if rmrk warrants them353					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,354				]355				.into_iter(),356			)357			.map_err(|err| match err {358				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),359				err => Self::map_unique_err_to_proxy(err),360			})?;361362			Self::deposit_event(Event::NftMinted {363				owner,364				collection_id,365				nft_id: nft_id.0,366			});367368			Ok(())369		}370371		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]372		#[transactional]373		pub fn burn_nft(374			origin: OriginFor<T>,375			collection_id: RmrkCollectionId,376			nft_id: RmrkNftId,377		) -> DispatchResult {378			let sender = ensure_signed(origin)?;379			let cross_sender = T::CrossAccountId::from_sub(sender.clone());380381			Self::destroy_nft(382				cross_sender,383				Self::unique_collection_id(collection_id)?,384				nft_id.into(),385			)386			.map_err(Self::map_unique_err_to_proxy)?;387388			Self::deposit_event(Event::NFTBurned {389				owner: sender,390				nft_id,391			});392393			Ok(())394		}395396		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]397		#[transactional]398		pub fn send(399			origin: OriginFor<T>,400			rmrk_collection_id: RmrkCollectionId,401			rmrk_nft_id: RmrkNftId,402			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,403		) -> DispatchResult {404			let sender = ensure_signed(origin.clone())?;405			let cross_sender = T::CrossAccountId::from_sub(sender.clone());406407			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;408			let nft_id = rmrk_nft_id.into();409410			let token_data =411				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;412413			let from = token_data.owner;414415			let collection =416				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;417418			ensure!(419				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,420				<Error<T>>::NonTransferable421			);422423			ensure!(424				!Self::get_nft_property_decoded(425					collection_id,426					nft_id,427					RmrkProperty::PendingNftAccept428				)?,429				<Error<T>>::NoPermission430			);431432			let target_owner;433			let approval_required;434435			match new_owner {436				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {437					target_owner = T::CrossAccountId::from_sub(account_id.clone());438					approval_required = false;439				}440				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(441					target_collection_id,442					target_nft_id,443				) => {444					let target_collection_id = Self::unique_collection_id(target_collection_id)?;445446					let target_nft_budget = budget::Value::new(NESTING_BUDGET);447448					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(449						target_collection_id,450						target_nft_id.into(),451						Some((collection_id, nft_id)),452						&target_nft_budget,453					)454					.map_err(Self::map_unique_err_to_proxy)?;455456					approval_required = cross_sender != target_nft_owner;457458					if approval_required {459						target_owner = target_nft_owner;460461						<PalletNft<T>>::set_scoped_token_property(462							collection.id,463							nft_id,464							PropertyScope::Rmrk,465							Self::rmrk_property(PendingNftAccept, &approval_required)?,466						)?;467					} else {468						target_owner = T::CrossTokenAddressMapping::token_to_address(469							target_collection_id,470							target_nft_id.into(),471						);472					}473				}474			}475476			let src_nft_budget = budget::Value::new(NESTING_BUDGET);477478			<PalletNft<T>>::transfer_from(479				&collection,480				&cross_sender,481				&from,482				&target_owner,483				nft_id,484				&src_nft_budget,485			)486			.map_err(Self::map_unique_err_to_proxy)?;487488			Self::deposit_event(Event::NFTSent {489				sender,490				recipient: new_owner,491				collection_id: rmrk_collection_id,492				nft_id: rmrk_nft_id,493				approval_required,494			});495496			Ok(())497		}498499		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]500		#[transactional]501		pub fn accept_nft(502			origin: OriginFor<T>,503			rmrk_collection_id: RmrkCollectionId,504			rmrk_nft_id: RmrkNftId,505			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,506		) -> DispatchResult {507			let sender = ensure_signed(origin.clone())?;508			let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;511			let nft_id = rmrk_nft_id.into();512513			let collection =514				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;515516			let new_cross_owner = match new_owner {517				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {518					T::CrossAccountId::from_sub(account_id.clone())519				}520				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(521					target_collection_id,522					target_nft_id,523				) => {524					let target_collection_id = Self::unique_collection_id(target_collection_id)?;525526					T::CrossTokenAddressMapping::token_to_address(527						target_collection_id,528						TokenId(target_nft_id),529					)530				}531			};532533			let budget = budget::Value::new(NESTING_BUDGET);534535			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)536				.map_err(|err| {537					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {538						<Error<T>>::CannotAcceptNonOwnedNft.into()539					} else {540						Self::map_unique_err_to_proxy(err)541					}542				})?;543544			<PalletNft<T>>::set_scoped_token_property(545				collection.id,546				nft_id,547				PropertyScope::Rmrk,548				Self::rmrk_property(PendingNftAccept, &false)?,549			)?;550551			Self::deposit_event(Event::NFTAccepted {552				sender,553				recipient: new_owner,554				collection_id: rmrk_collection_id,555				nft_id: rmrk_nft_id,556			});557558			Ok(())559		}560561		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]562		#[transactional]563		pub fn reject_nft(564			origin: OriginFor<T>,565			rmrk_collection_id: RmrkCollectionId,566			rmrk_nft_id: RmrkNftId,567		) -> DispatchResult {568			let sender = ensure_signed(origin)?;569			let cross_sender = T::CrossAccountId::from_sub(sender.clone());570571			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;572			let nft_id = rmrk_nft_id.into();573574			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {575				if err == <CommonError<T>>::NoPermission.into()576					|| err == <CommonError<T>>::ApprovedValueTooLow.into()577				{578					<Error<T>>::CannotRejectNonOwnedNft.into()579				} else {580					Self::map_unique_err_to_proxy(err)581				}582			})?;583584			Self::deposit_event(Event::NFTRejected {585				sender,586				collection_id: rmrk_collection_id,587				nft_id: rmrk_nft_id,588			});589590			Ok(())591		}592593		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]594		#[transactional]595		pub fn accept_resource(596			origin: OriginFor<T>,597			rmrk_collection_id: RmrkCollectionId,598			rmrk_nft_id: RmrkNftId,599			rmrk_resource_id: RmrkResourceId,600		) -> DispatchResult {601			let sender = ensure_signed(origin)?;602			let cross_sender = T::CrossAccountId::from_sub(sender);603604			let collection_id = Self::unique_collection_id(rmrk_collection_id)605				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;606607			let nft_id = rmrk_nft_id.into();608			let resource_id = rmrk_resource_id.into();609610			let budget = budget::Value::new(NESTING_BUDGET);611612			let nft_owner =613				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)614					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;615616			let resource_collection_id: CollectionId =617				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)618					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;619620			let is_pending: bool = Self::get_nft_property_decoded(621				resource_collection_id,622				resource_id,623				PendingResourceAccept,624			)625			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;626627			ensure!(is_pending, <Error<T>>::ResourceNotPending);628629			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);630631			<PalletNft<T>>::set_scoped_token_property(632				resource_collection_id,633				rmrk_resource_id.into(),634				PropertyScope::Rmrk,635				Self::rmrk_property(PendingResourceAccept, &false)?,636			)?;637638			Self::deposit_event(Event::<T>::ResourceAccepted {639				nft_id: rmrk_nft_id,640				resource_id: rmrk_resource_id,641			});642643			Ok(())644		}645646		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]647		#[transactional]648		pub fn accept_resource_removal(649			origin: OriginFor<T>,650			rmrk_collection_id: RmrkCollectionId,651			rmrk_nft_id: RmrkNftId,652			rmrk_resource_id: RmrkResourceId,653		) -> DispatchResult {654			let sender = ensure_signed(origin)?;655			let cross_sender = T::CrossAccountId::from_sub(sender);656657			let collection_id = Self::unique_collection_id(rmrk_collection_id)658				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;659660			let nft_id = rmrk_nft_id.into();661			let resource_id = rmrk_resource_id.into();662663			let budget = budget::Value::new(NESTING_BUDGET);664665			let nft_owner =666				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)667					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;668669			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);670671			let resource_collection_id: CollectionId =672				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)673					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;674675			let is_pending: bool = Self::get_nft_property_decoded(676				resource_collection_id,677				resource_id,678				PendingResourceRemoval,679			)680			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;681682			ensure!(is_pending, <Error<T>>::ResourceNotPending);683684			let resource_collection = Self::get_typed_nft_collection(685				resource_collection_id,686				misc::CollectionType::Resource,687			)?;688689			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())690				.map_err(Self::map_unique_err_to_proxy)?;691692			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {693				nft_id: rmrk_nft_id,694				resource_id: rmrk_resource_id,695			});696697			Ok(())698		}699700		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]701		#[transactional]702		pub fn set_property(703			origin: OriginFor<T>,704			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,705			maybe_nft_id: Option<RmrkNftId>,706			key: RmrkKeyString,707			value: RmrkValueString,708		) -> DispatchResult {709			let sender = ensure_signed(origin)?;710			let sender = T::CrossAccountId::from_sub(sender);711712			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;713			let budget = budget::Value::new(NESTING_BUDGET);714715			match maybe_nft_id {716				Some(nft_id) => {717					let token_id: TokenId = nft_id.into();718719					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;720					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;721722					<PalletNft<T>>::set_scoped_token_property(723						collection_id,724						token_id,725						PropertyScope::Rmrk,726						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,727					)?;728				}729				None => {730					let collection = Self::get_typed_nft_collection(731						collection_id,732						misc::CollectionType::Regular,733					)?;734735					Self::check_collection_owner(&collection, &sender)?;736737					<PalletCommon<T>>::set_scoped_collection_property(738						collection_id,739						PropertyScope::Rmrk,740						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,741					)?;742				}743			}744745			Self::deposit_event(Event::PropertySet {746				collection_id: rmrk_collection_id,747				maybe_nft_id,748				key,749				value,750			});751752			Ok(())753		}754755		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]756		#[transactional]757		pub fn set_priority(758			origin: OriginFor<T>,759			rmrk_collection_id: RmrkCollectionId,760			rmrk_nft_id: RmrkNftId,761			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,762		) -> DispatchResult {763			let sender = ensure_signed(origin)?;764			let sender = T::CrossAccountId::from_sub(sender);765766			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;767			let nft_id = rmrk_nft_id.into();768			let budget = budget::Value::new(NESTING_BUDGET);769770			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;771			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;772773			<PalletNft<T>>::set_scoped_token_property(774				collection_id,775				nft_id,776				PropertyScope::Rmrk,777				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,778			)?;779780			Ok(())781		}782783		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]784		#[transactional]785		pub fn add_basic_resource(786			origin: OriginFor<T>,787			collection_id: RmrkCollectionId,788			nft_id: RmrkNftId,789			resource: RmrkBasicResource,790		) -> DispatchResult {791			let sender = ensure_signed(origin.clone())?;792793			let resource_id = Self::resource_add(794				sender,795				Self::unique_collection_id(collection_id)?,796				nft_id.into(),797				[798					Self::rmrk_property(TokenType, &NftType::Resource)?,799					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,800					Self::rmrk_property(Src, &resource.src)?,801					Self::rmrk_property(Metadata, &resource.metadata)?,802					Self::rmrk_property(License, &resource.license)?,803					Self::rmrk_property(Thumb, &resource.thumb)?,804				]805				.into_iter(),806			)?;807808			Self::deposit_event(Event::ResourceAdded {809				nft_id,810				resource_id,811			});812			Ok(())813		}814815		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]816		#[transactional]817		pub fn add_composable_resource(818			origin: OriginFor<T>,819			collection_id: RmrkCollectionId,820			nft_id: RmrkNftId,821			_resource_id: RmrkBoundedResource,822			resource: RmrkComposableResource,823		) -> DispatchResult {824			let sender = ensure_signed(origin.clone())?;825826			let resource_id = Self::resource_add(827				sender,828				Self::unique_collection_id(collection_id)?,829				nft_id.into(),830				[831					Self::rmrk_property(TokenType, &NftType::Resource)?,832					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,833					Self::rmrk_property(Parts, &resource.parts)?,834					Self::rmrk_property(Base, &resource.base)?,835					Self::rmrk_property(Src, &resource.src)?,836					Self::rmrk_property(Metadata, &resource.metadata)?,837					Self::rmrk_property(License, &resource.license)?,838					Self::rmrk_property(Thumb, &resource.thumb)?,839				]840				.into_iter(),841			)?;842843			Self::deposit_event(Event::ResourceAdded {844				nft_id,845				resource_id,846			});847			Ok(())848		}849850		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]851		#[transactional]852		pub fn add_slot_resource(853			origin: OriginFor<T>,854			collection_id: RmrkCollectionId,855			nft_id: RmrkNftId,856			resource: RmrkSlotResource,857		) -> DispatchResult {858			let sender = ensure_signed(origin.clone())?;859860			let resource_id = Self::resource_add(861				sender,862				Self::unique_collection_id(collection_id)?,863				nft_id.into(),864				[865					Self::rmrk_property(TokenType, &NftType::Resource)?,866					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,867					Self::rmrk_property(Base, &resource.base)?,868					Self::rmrk_property(Src, &resource.src)?,869					Self::rmrk_property(Metadata, &resource.metadata)?,870					Self::rmrk_property(Slot, &resource.slot)?,871					Self::rmrk_property(License, &resource.license)?,872					Self::rmrk_property(Thumb, &resource.thumb)?,873				]874				.into_iter(),875			)?;876877			Self::deposit_event(Event::ResourceAdded {878				nft_id,879				resource_id,880			});881			Ok(())882		}883884		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885		#[transactional]886		pub fn remove_resource(887			origin: OriginFor<T>,888			collection_id: RmrkCollectionId,889			nft_id: RmrkNftId,890			resource_id: RmrkResourceId,891		) -> DispatchResult {892			let sender = ensure_signed(origin.clone())?;893894			Self::resource_remove(895				sender,896				Self::unique_collection_id(collection_id)?,897				nft_id.into(),898				resource_id.into(),899			)?;900901			Self::deposit_event(Event::ResourceRemoval {902				nft_id,903				resource_id,904			});905			Ok(())906		}907	}908}909910impl<T: Config> Pallet<T> {911	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {912		let key = rmrk_key.to_key::<T>()?;913914		let scoped_key = PropertyScope::Rmrk915			.apply(key)916			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;917918		Ok(scoped_key)919	}920921	// todo think about renaming these922	pub fn rmrk_property<E: Encode>(923		rmrk_key: RmrkProperty,924		value: &E,925	) -> Result<Property, DispatchError> {926		let key = rmrk_key.to_key::<T>()?;927928		let value = value929			.encode()930			.try_into()931			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;932933		let property = Property { key, value };934935		Ok(property)936	}937938	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {939		vec.decode()940			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())941	}942943	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>944	where945		BoundedVec<u8, S>: TryFrom<Vec<u8>>,946	{947		vec.rebind()948			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())949	}950951	fn init_collection(952		sender: T::CrossAccountId,953		data: CreateCollectionData<T::AccountId>,954		properties: impl Iterator<Item = Property>,955	) -> Result<CollectionId, DispatchError> {956		let collection_id = <PalletNft<T>>::init_collection(sender, data);957958		if let Err(DispatchError::Arithmetic(_)) = &collection_id {959			return Err(<Error<T>>::NoAvailableCollectionId.into());960		}961962		<PalletCommon<T>>::set_scoped_collection_properties(963			collection_id?,964			PropertyScope::Rmrk,965			properties,966		)?;967968		collection_id969	}970971	pub fn create_nft(972		sender: &T::CrossAccountId,973		owner: &T::CrossAccountId,974		collection: &NonfungibleHandle<T>,975		properties: impl Iterator<Item = Property>,976	) -> Result<TokenId, DispatchError> {977		let data = CreateNftExData {978			properties: BoundedVec::default(),979			owner: owner.clone(),980		};981982		let budget = budget::Value::new(NESTING_BUDGET);983984		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;985986		let nft_id = <PalletNft<T>>::current_token_id(collection.id);987988		<PalletNft<T>>::set_scoped_token_properties(989			collection.id,990			nft_id,991			PropertyScope::Rmrk,992			properties,993		)?;994995		Ok(nft_id)996	}997998	fn destroy_nft(999		sender: T::CrossAccountId,1000		collection_id: CollectionId,1001		token_id: TokenId,1002	) -> DispatchResult {1003		let collection =1004			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10051006		let token_data =1007			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10081009		let from = token_data.owner;10101011		let budget = budget::Value::new(NESTING_BUDGET);10121013		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1014	}10151016	fn resource_add(1017		sender: T::AccountId,1018		collection_id: CollectionId,1019		token_id: TokenId,1020		resource_properties: impl Iterator<Item = Property>,1021	) -> Result<RmrkResourceId, DispatchError> {1022		let collection =1023			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1024		ensure!(collection.owner == sender, Error::<T>::NoPermission);10251026		let sender = T::CrossAccountId::from_sub(sender);1027		let budget = budget::Value::new(NESTING_BUDGET);10281029		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1030			.map_err(Self::map_unique_err_to_proxy)?;10311032		let pending = sender != nft_owner;10331034		let resource_collection_id: CollectionId =1035			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1036		let resource_collection =1037			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10381039		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10401041		let resource_id = Self::create_nft(1042			&sender,1043			&nft_owner,1044			&resource_collection,1045			resource_properties.chain(1046				[1047					Self::rmrk_property(PendingResourceAccept, &pending)?,1048					Self::rmrk_property(PendingResourceRemoval, &false)?,1049				]1050				.into_iter(),1051			),1052		)1053		.map_err(|err| match err {1054			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1055			err => Self::map_unique_err_to_proxy(err),1056		})?;10571058		Ok(resource_id.0)1059	}10601061	fn resource_remove(1062		sender: T::AccountId,1063		collection_id: CollectionId,1064		nft_id: TokenId,1065		resource_id: TokenId,1066	) -> DispatchResult {1067		let collection =1068			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1069		ensure!(collection.owner == sender, Error::<T>::NoPermission);10701071		let resource_collection_id: CollectionId =1072			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1073		let resource_collection =1074			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1075		ensure!(1076			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1077			Error::<T>::ResourceDoesntExist1078		);10791080		let budget = up_data_structs::budget::Value::new(10);1081		let topmost_owner =1082			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10831084		let sender = T::CrossAccountId::from_sub(sender);1085		if topmost_owner == sender {1086			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1087				.map_err(Self::map_unique_err_to_proxy)?;1088		} else {1089			<PalletNft<T>>::set_scoped_token_property(1090				resource_collection_id,1091				resource_id,1092				PropertyScope::Rmrk,1093				Self::rmrk_property(PendingResourceRemoval, &true)?,1094			)?;1095		}10961097		Ok(())1098	}10991100	fn change_collection_owner(1101		collection_id: CollectionId,1102		collection_type: misc::CollectionType,1103		sender: T::AccountId,1104		new_owner: T::AccountId,1105	) -> DispatchResult {1106		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1107		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11081109		let mut collection = collection.into_inner();11101111		collection.owner = new_owner;1112		collection.save()1113	}11141115	fn check_collection_owner(1116		collection: &NonfungibleHandle<T>,1117		account: &T::CrossAccountId,1118	) -> DispatchResult {1119		collection1120			.check_is_owner(account)1121			.map_err(Self::map_unique_err_to_proxy)1122	}11231124	pub fn last_collection_idx() -> RmrkCollectionId {1125		<CollectionIndex<T>>::get()1126	}11271128	pub fn unique_collection_id(1129		rmrk_collection_id: RmrkCollectionId,1130	) -> Result<CollectionId, DispatchError> {1131		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1132			.map_err(|_| <Error<T>>::CollectionUnknown.into())1133	}11341135	pub fn rmrk_collection_id(1136		unique_collection_id: CollectionId,1137	) -> Result<RmrkCollectionId, DispatchError> {1138		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1139			.map_err(|_| <Error<T>>::CollectionUnknown.into())1140	}11411142	pub fn get_nft_collection(1143		collection_id: CollectionId,1144	) -> Result<NonfungibleHandle<T>, DispatchError> {1145		let collection = <CollectionHandle<T>>::try_get(collection_id)1146			.map_err(|_| <Error<T>>::CollectionUnknown)?;11471148		match collection.mode {1149			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1150			_ => Err(<Error<T>>::CollectionUnknown.into()),1151		}1152	}11531154	pub fn collection_exists(collection_id: CollectionId) -> bool {1155		<CollectionHandle<T>>::try_get(collection_id).is_ok()1156	}11571158	pub fn get_collection_property(1159		collection_id: CollectionId,1160		key: RmrkProperty,1161	) -> Result<PropertyValue, DispatchError> {1162		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1163			.get(&Self::rmrk_property_key(key)?)1164			.ok_or(<Error<T>>::CollectionUnknown)?1165			.clone();11661167		Ok(collection_property)1168	}11691170	pub fn get_collection_property_decoded<V: Decode>(1171		collection_id: CollectionId,1172		key: RmrkProperty,1173	) -> Result<V, DispatchError> {1174		Self::decode_property(Self::get_collection_property(collection_id, key)?)1175	}11761177	pub fn get_collection_type(1178		collection_id: CollectionId,1179	) -> Result<misc::CollectionType, DispatchError> {1180		Self::get_collection_property_decoded(collection_id, CollectionType)1181			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1182	}11831184	pub fn ensure_collection_type(1185		collection_id: CollectionId,1186		collection_type: misc::CollectionType,1187	) -> DispatchResult {1188		let actual_type = Self::get_collection_type(collection_id)?;1189		ensure!(1190			actual_type == collection_type,1191			<CommonError<T>>::NoPermission1192		);11931194		Ok(())1195	}11961197	pub fn get_typed_nft_collection(1198		collection_id: CollectionId,1199		collection_type: misc::CollectionType,1200	) -> Result<NonfungibleHandle<T>, DispatchError> {1201		Self::ensure_collection_type(collection_id, collection_type)?;12021203		Self::get_nft_collection(collection_id)1204	}12051206	pub fn get_typed_nft_collection_mapped(1207		rmrk_collection_id: RmrkCollectionId,1208		collection_type: misc::CollectionType,1209	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1210		let unique_collection_id = match collection_type {1211			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1212			_ => rmrk_collection_id.into(),1213		};12141215		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12161217		Ok((collection, unique_collection_id))1218	}12191220	pub fn get_nft_property(1221		collection_id: CollectionId,1222		nft_id: TokenId,1223		key: RmrkProperty,1224	) -> Result<PropertyValue, DispatchError> {1225		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1226			.get(&Self::rmrk_property_key(key)?)1227			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1228			.clone();12291230		Ok(nft_property)1231	}12321233	pub fn get_nft_property_decoded<V: Decode>(1234		collection_id: CollectionId,1235		nft_id: TokenId,1236		key: RmrkProperty,1237	) -> Result<V, DispatchError> {1238		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1239	}12401241	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1242		<TokenData<T>>::contains_key((collection_id, nft_id))1243	}12441245	pub fn get_nft_type(1246		collection_id: CollectionId,1247		token_id: TokenId,1248	) -> Result<NftType, DispatchError> {1249		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1250			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1251	}12521253	pub fn ensure_nft_type(1254		collection_id: CollectionId,1255		token_id: TokenId,1256		nft_type: NftType,1257	) -> DispatchResult {1258		let actual_type = Self::get_nft_type(collection_id, token_id)?;1259		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12601261		Ok(())1262	}12631264	pub fn ensure_nft_owner(1265		collection_id: CollectionId,1266		token_id: TokenId,1267		possible_owner: &T::CrossAccountId,1268		nesting_budget: &dyn budget::Budget,1269	) -> DispatchResult {1270		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1271			possible_owner.clone(),1272			collection_id,1273			token_id,1274			None,1275			nesting_budget,1276		)1277		.map_err(Self::map_unique_err_to_proxy)?;12781279		ensure!(is_owned, <Error<T>>::NoPermission);12801281		Ok(())1282	}12831284	pub fn filter_user_properties<Key, Value, R, Mapper>(1285		collection_id: CollectionId,1286		token_id: Option<TokenId>,1287		filter_keys: Option<Vec<RmrkPropertyKey>>,1288		mapper: Mapper,1289	) -> Result<Vec<R>, DispatchError>1290	where1291		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1292		Value: Decode + Default,1293		Mapper: Fn(Key, Value) -> R,1294	{1295		filter_keys1296			.map(|keys| {1297				let properties = keys1298					.into_iter()1299					.filter_map(|key| {1300						let key: Key = key.try_into().ok()?;13011302						let value = match token_id {1303							Some(token_id) => Self::get_nft_property_decoded(1304								collection_id,1305								token_id,1306								UserProperty(key.as_ref()),1307							),1308							None => Self::get_collection_property_decoded(1309								collection_id,1310								UserProperty(key.as_ref()),1311							),1312						}1313						.ok()?;13141315						Some(mapper(key, value))1316					})1317					.collect();13181319				Ok(properties)1320			})1321			.unwrap_or_else(|| {1322				let properties =1323					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13241325				Ok(properties)1326			})1327	}13281329	pub fn iterate_user_properties<Key, Value, R, Mapper>(1330		collection_id: CollectionId,1331		token_id: Option<TokenId>,1332		mapper: Mapper,1333	) -> Result<impl Iterator<Item = R>, DispatchError>1334	where1335		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1336		Value: Decode + Default,1337		Mapper: Fn(Key, Value) -> R,1338	{1339		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13401341		let properties = match token_id {1342			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1343			None => <PalletCommon<T>>::collection_properties(collection_id),1344		};13451346		let properties = properties.into_iter().filter_map(move |(key, value)| {1347			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13481349			let key: Key = key.to_vec().try_into().ok()?;1350			let value: Value = value.decode().ok()?;13511352			Some(mapper(key, value))1353		});13541355		Ok(properties)1356	}13571358	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1359		map_unique_err_to_proxy! {1360			match err {1361				CommonError::NoPermission => NoPermission,1362				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1363				CommonError::PublicMintingNotAllowed => NoPermission,1364				CommonError::TokenNotFound => NoAvailableNftId,1365				CommonError::ApprovedValueTooLow => NoPermission,1366				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1367				StructureError::TokenNotFound => NoAvailableNftId,1368				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1369			}1370		}1371	}1372}