git.delta.rocks / unique-network / refs/commits / 5ca51b5e5337

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs34.9 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136		CannotRejectNonOwnedNft,137		ResourceNotPending,138	}139140	#[pallet::call]141	impl<T: Config> Pallet<T> {142		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143		#[transactional]144		pub fn create_collection(145			origin: OriginFor<T>,146			metadata: RmrkString,147			max: Option<u32>,148			symbol: RmrkCollectionSymbol,149		) -> DispatchResult {150			let sender = ensure_signed(origin)?;151152			let limits = CollectionLimits {153				owner_can_transfer: Some(false),154				token_limit: max,155				..Default::default()156			};157158			let data = CreateCollectionData {159				limits: Some(limits),160				token_prefix: symbol161					.into_inner()162					.try_into()163					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164				permissions: Some(CollectionPermissions {165					nesting: Some(NestingRule::Owner),166					..Default::default()167				}),168				..Default::default()169			};170171			let unique_collection_id = Self::init_collection(172				T::CrossAccountId::from_sub(sender.clone()),173				data,174				[175					Self::rmrk_property(Metadata, &metadata)?,176					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177				]178				.into_iter(),179			)?;180			let rmrk_collection_id = <CollectionIndex<T>>::get();181182			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185			<CollectionIndex<T>>::mutate(|n| *n += 1);186187			Self::deposit_event(Event::CollectionCreated {188				issuer: sender,189				collection_id: rmrk_collection_id,190			});191192			Ok(())193		}194195		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196		#[transactional]197		pub fn destroy_collection(198			origin: OriginFor<T>,199			collection_id: RmrkCollectionId,200		) -> DispatchResult {201			let sender = ensure_signed(origin)?;202			let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204			let collection = Self::get_typed_nft_collection(205				Self::unique_collection_id(collection_id)?,206				misc::CollectionType::Regular,207			)?;208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_unique_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286			transferable: bool,287		) -> DispatchResult {288			let sender = ensure_signed(origin)?;289			let sender = T::CrossAccountId::from_sub(sender);290			let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293				recipient: recipient.unwrap_or_else(|| owner.clone()),294				amount,295			});296297			let collection = Self::get_typed_nft_collection(298				Self::unique_collection_id(collection_id)?,299				misc::CollectionType::Regular,300			)?;301302			let nft_id = Self::create_nft(303				&sender,304				&cross_owner,305				&collection,306				[307					Self::rmrk_property(TokenType, &NftType::Regular)?,308					Self::rmrk_property(Transferable, &transferable)?,309					Self::rmrk_property(PendingNftAccept, &false)?,310					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311					Self::rmrk_property(Metadata, &metadata)?,312					Self::rmrk_property(Equipped, &false)?,313					Self::rmrk_property(314						ResourceCollection,315						&Self::init_collection(316							sender.clone(),317							CreateCollectionData {318								..Default::default()319							},320							[Self::rmrk_property(321								CollectionType,322								&misc::CollectionType::Resource,323							)?]324							.into_iter(),325						)?,326					)?, // todo possibly add limits to the collection if rmrk warrants them327					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328				]329				.into_iter(),330			)331			.map_err(|err| match err {332				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333				err => Self::map_unique_err_to_proxy(err),334			})?;335336			Self::deposit_event(Event::NftMinted {337				owner,338				collection_id,339				nft_id: nft_id.0,340			});341342			Ok(())343		}344345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn burn_nft(348			origin: OriginFor<T>,349			collection_id: RmrkCollectionId,350			nft_id: RmrkNftId,351		) -> DispatchResult {352			let sender = ensure_signed(origin)?;353			let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355			Self::destroy_nft(356				cross_sender,357				Self::unique_collection_id(collection_id)?,358				nft_id.into(),359			)360			.map_err(Self::map_unique_err_to_proxy)?;361362			Self::deposit_event(Event::NFTBurned {363				owner: sender,364				nft_id,365			});366367			Ok(())368		}369370		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371		#[transactional]372		pub fn send(373			origin: OriginFor<T>,374			rmrk_collection_id: RmrkCollectionId,375			rmrk_nft_id: RmrkNftId,376			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377		) -> DispatchResult {378			let sender = ensure_signed(origin.clone())?;379			let cross_sender = T::CrossAccountId::from_sub(sender);380381			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382			let nft_id = rmrk_nft_id.into();383384			let token_data =385				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387			let from = token_data.owner;388389			let collection =390				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392			ensure!(393				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394				<Error<T>>::NonTransferable395			);396397			ensure!(398				!Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,399				<Error<T>>::NoPermission400			);401402			let target_owner;403404			match new_owner {405				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {406					target_owner = T::CrossAccountId::from_sub(account_id);407				}408				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(409					target_collection_id,410					target_nft_id,411				) => {412					let target_collection_id = Self::unique_collection_id(target_collection_id)?;413414					let target_nft_budget = budget::Value::new(NESTING_BUDGET);415416					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(417						target_collection_id,418						target_nft_id.into(),419						Some((collection_id, nft_id)),420						&target_nft_budget,421					)422					.map_err(Self::map_unique_err_to_proxy)?;423424					let is_approval_required = cross_sender != target_nft_owner;425426					if is_approval_required {427						target_owner = target_nft_owner;428429						<PalletNft<T>>::set_scoped_token_property(430							collection.id,431							nft_id,432							PropertyScope::Rmrk,433							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,434						)?;435					} else {436						target_owner = T::CrossTokenAddressMapping::token_to_address(437							target_collection_id,438							target_nft_id.into(),439						);440					}441				}442			}443444			let src_nft_budget = budget::Value::new(NESTING_BUDGET);445446			<PalletNft<T>>::transfer_from(447				&collection,448				&cross_sender,449				&from,450				&target_owner,451				nft_id,452				&src_nft_budget,453			)454			.map_err(Self::map_unique_err_to_proxy)?;455456			Ok(())457		}458459		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]460		#[transactional]461		pub fn accept_nft(462			origin: OriginFor<T>,463			rmrk_collection_id: RmrkCollectionId,464			rmrk_nft_id: RmrkNftId,465			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,466		) -> DispatchResult {467			let sender = ensure_signed(origin.clone())?;468			let cross_sender = T::CrossAccountId::from_sub(sender);469470			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;471			let nft_id = rmrk_nft_id.into();472473			let collection =474				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;475476			let new_owner = match new_owner {477				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {478					T::CrossAccountId::from_sub(account_id)479				}480				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(481					target_collection_id,482					target_nft_id,483				) => {484					let target_collection_id = Self::unique_collection_id(target_collection_id)?;485486					T::CrossTokenAddressMapping::token_to_address(487						target_collection_id,488						TokenId(target_nft_id),489					)490				}491			};492493			let budget = budget::Value::new(NESTING_BUDGET);494495			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)496				.map_err(|err| {497					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {498						<Error<T>>::CannotAcceptNonOwnedNft.into()499					} else {500						Self::map_unique_err_to_proxy(err)501					}502				})?;503504			<PalletNft<T>>::set_scoped_token_property(505				collection.id,506				nft_id,507				PropertyScope::Rmrk,508				Self::rmrk_property(PendingNftAccept, &false)?,509			)?;510511			Ok(())512		}513514		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]515		#[transactional]516		pub fn reject_nft(517			origin: OriginFor<T>,518			collection_id: RmrkCollectionId,519			nft_id: RmrkNftId,520		) -> DispatchResult {521			let sender = ensure_signed(origin)?;522			let cross_sender = T::CrossAccountId::from_sub(sender);523524			let collection_id = Self::unique_collection_id(collection_id)?;525			let nft_id = nft_id.into();526527			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {528				if err == <CommonError<T>>::NoPermission.into()529					|| err == <CommonError<T>>::ApprovedValueTooLow.into()530				{531					<Error<T>>::CannotRejectNonOwnedNft.into()532				} else {533					Self::map_unique_err_to_proxy(err)534				}535			})?;536537			Ok(())538		}539540		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]541		#[transactional]542		pub fn accept_resource(543			origin: OriginFor<T>,544			rmrk_collection_id: RmrkCollectionId,545			rmrk_nft_id: RmrkNftId,546			rmrk_resource_id: RmrkResourceId,547		) -> DispatchResult {548			let sender = ensure_signed(origin)?;549			let cross_sender = T::CrossAccountId::from_sub(sender);550551			let collection_id = Self::unique_collection_id(rmrk_collection_id)552				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;553554			let nft_id = rmrk_nft_id.into();555			let resource_id = rmrk_resource_id.into();556557			let budget = budget::Value::new(NESTING_BUDGET);558559			let nft_owner =560				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)561					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;562563			let resource_collection_id: CollectionId =564				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)565					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567			let is_pending: bool = Self::get_nft_property_decoded(568				resource_collection_id,569				resource_id,570				PendingResourceAccept,571			)572			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;573574			ensure!(is_pending, <Error<T>>::ResourceNotPending);575576			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);577578			<PalletNft<T>>::set_scoped_token_property(579				resource_collection_id,580				rmrk_resource_id.into(),581				PropertyScope::Rmrk,582				Self::rmrk_property(PendingResourceAccept, &false)?,583			)?;584585			Ok(())586		}587588		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]589		#[transactional]590		pub fn accept_resource_removal(591			origin: OriginFor<T>,592			rmrk_collection_id: RmrkCollectionId,593			rmrk_nft_id: RmrkNftId,594			rmrk_resource_id: RmrkResourceId,595		) -> DispatchResult {596			let sender = ensure_signed(origin)?;597			let cross_sender = T::CrossAccountId::from_sub(sender);598599			let collection_id = Self::unique_collection_id(rmrk_collection_id)600				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;601602			let nft_id = rmrk_nft_id.into();603			let resource_id = rmrk_resource_id.into();604605			let budget = budget::Value::new(NESTING_BUDGET);606607			let nft_owner =608				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)609					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;610611			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);612613			let resource_collection_id: CollectionId =614				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)615					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;616617			let is_pending: bool = Self::get_nft_property_decoded(618				resource_collection_id,619				resource_id,620				PendingResourceRemoval,621			)622			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;623624			ensure!(is_pending, <Error<T>>::ResourceNotPending);625626			let resource_collection = Self::get_typed_nft_collection(627				resource_collection_id,628				misc::CollectionType::Resource,629			)?;630631			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())632				.map_err(Self::map_unique_err_to_proxy)?;633634			Ok(())635		}636637		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]638		#[transactional]639		pub fn set_property(640			origin: OriginFor<T>,641			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,642			maybe_nft_id: Option<RmrkNftId>,643			key: RmrkKeyString,644			value: RmrkValueString,645		) -> DispatchResult {646			let sender = ensure_signed(origin)?;647			let sender = T::CrossAccountId::from_sub(sender);648649			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;650			let budget = budget::Value::new(NESTING_BUDGET);651652			match maybe_nft_id {653				Some(nft_id) => {654					let token_id: TokenId = nft_id.into();655656					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;657					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;658659					<PalletNft<T>>::set_scoped_token_property(660						collection_id,661						token_id,662						PropertyScope::Rmrk,663						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,664					)?;665				}666				None => {667					let collection = Self::get_typed_nft_collection(668						collection_id,669						misc::CollectionType::Regular,670					)?;671672					Self::check_collection_owner(&collection, &sender)?;673674					<PalletCommon<T>>::set_scoped_collection_property(675						collection_id,676						PropertyScope::Rmrk,677						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,678					)?;679				}680			}681682			Self::deposit_event(Event::PropertySet {683				collection_id: rmrk_collection_id,684				maybe_nft_id,685				key,686				value,687			});688689			Ok(())690		}691692		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]693		#[transactional]694		pub fn add_basic_resource(695			origin: OriginFor<T>,696			collection_id: RmrkCollectionId,697			nft_id: RmrkNftId,698			resource: RmrkBasicResource,699		) -> DispatchResult {700			let sender = ensure_signed(origin.clone())?;701702			let resource_id = Self::resource_add(703				sender,704				Self::unique_collection_id(collection_id)?,705				nft_id.into(),706				[707					Self::rmrk_property(TokenType, &NftType::Resource)?,708					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,709					Self::rmrk_property(Src, &resource.src)?,710					Self::rmrk_property(Metadata, &resource.metadata)?,711					Self::rmrk_property(License, &resource.license)?,712					Self::rmrk_property(Thumb, &resource.thumb)?,713				]714				.into_iter(),715			)?;716717			Self::deposit_event(Event::ResourceAdded {718				nft_id,719				resource_id,720			});721			Ok(())722		}723724		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725		#[transactional]726		pub fn add_composable_resource(727			origin: OriginFor<T>,728			collection_id: RmrkCollectionId,729			nft_id: RmrkNftId,730			_resource_id: RmrkBoundedResource,731			resource: RmrkComposableResource,732		) -> DispatchResult {733			let sender = ensure_signed(origin.clone())?;734735			let resource_id = Self::resource_add(736				sender,737				Self::unique_collection_id(collection_id)?,738				nft_id.into(),739				[740					Self::rmrk_property(TokenType, &NftType::Resource)?,741					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,742					Self::rmrk_property(Parts, &resource.parts)?,743					Self::rmrk_property(Base, &resource.base)?,744					Self::rmrk_property(Src, &resource.src)?,745					Self::rmrk_property(Metadata, &resource.metadata)?,746					Self::rmrk_property(License, &resource.license)?,747					Self::rmrk_property(Thumb, &resource.thumb)?,748				]749				.into_iter(),750			)?;751752			Self::deposit_event(Event::ResourceAdded {753				nft_id,754				resource_id,755			});756			Ok(())757		}758759		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]760		#[transactional]761		pub fn add_slot_resource(762			origin: OriginFor<T>,763			collection_id: RmrkCollectionId,764			nft_id: RmrkNftId,765			resource: RmrkSlotResource,766		) -> DispatchResult {767			let sender = ensure_signed(origin.clone())?;768769			let resource_id = Self::resource_add(770				sender,771				Self::unique_collection_id(collection_id)?,772				nft_id.into(),773				[774					Self::rmrk_property(TokenType, &NftType::Resource)?,775					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,776					Self::rmrk_property(Base, &resource.base)?,777					Self::rmrk_property(Src, &resource.src)?,778					Self::rmrk_property(Metadata, &resource.metadata)?,779					Self::rmrk_property(Slot, &resource.slot)?,780					Self::rmrk_property(License, &resource.license)?,781					Self::rmrk_property(Thumb, &resource.thumb)?,782				]783				.into_iter(),784			)?;785786			Self::deposit_event(Event::ResourceAdded {787				nft_id,788				resource_id,789			});790			Ok(())791		}792793		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794		#[transactional]795		pub fn remove_resource(796			origin: OriginFor<T>,797			collection_id: RmrkCollectionId,798			nft_id: RmrkNftId,799			resource_id: RmrkResourceId,800		) -> DispatchResult {801			let sender = ensure_signed(origin.clone())?;802803			Self::resource_remove(804				sender,805				Self::unique_collection_id(collection_id)?,806				nft_id.into(),807				resource_id.into(),808			)?;809810			Self::deposit_event(Event::ResourceRemoval {811				nft_id,812				resource_id,813			});814			Ok(())815		}816	}817}818819impl<T: Config> Pallet<T> {820	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {821		let key = rmrk_key.to_key::<T>()?;822823		let scoped_key = PropertyScope::Rmrk824			.apply(key)825			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;826827		Ok(scoped_key)828	}829830	// todo think about renaming these831	pub fn rmrk_property<E: Encode>(832		rmrk_key: RmrkProperty,833		value: &E,834	) -> Result<Property, DispatchError> {835		let key = rmrk_key.to_key::<T>()?;836837		let value = value838			.encode()839			.try_into()840			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;841842		let property = Property { key, value };843844		Ok(property)845	}846847	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {848		vec.decode()849			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())850	}851852	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>853	where854		BoundedVec<u8, S>: TryFrom<Vec<u8>>,855	{856		vec.rebind()857			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())858	}859860	fn init_collection(861		sender: T::CrossAccountId,862		data: CreateCollectionData<T::AccountId>,863		properties: impl Iterator<Item = Property>,864	) -> Result<CollectionId, DispatchError> {865		let collection_id = <PalletNft<T>>::init_collection(sender, data);866867		if let Err(DispatchError::Arithmetic(_)) = &collection_id {868			return Err(<Error<T>>::NoAvailableCollectionId.into());869		}870871		<PalletCommon<T>>::set_scoped_collection_properties(872			collection_id?,873			PropertyScope::Rmrk,874			properties,875		)?;876877		collection_id878	}879880	pub fn create_nft(881		sender: &T::CrossAccountId,882		owner: &T::CrossAccountId,883		collection: &NonfungibleHandle<T>,884		properties: impl Iterator<Item = Property>,885	) -> Result<TokenId, DispatchError> {886		let data = CreateNftExData {887			properties: BoundedVec::default(),888			owner: owner.clone(),889		};890891		let budget = budget::Value::new(NESTING_BUDGET);892893		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;894895		let nft_id = <PalletNft<T>>::current_token_id(collection.id);896897		<PalletNft<T>>::set_scoped_token_properties(898			collection.id,899			nft_id,900			PropertyScope::Rmrk,901			properties,902		)?;903904		Ok(nft_id)905	}906907	fn destroy_nft(908		sender: T::CrossAccountId,909		collection_id: CollectionId,910		token_id: TokenId,911	) -> DispatchResult {912		let collection =913			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;914915		let token_data =916			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;917918		let from = token_data.owner;919920		let budget = budget::Value::new(NESTING_BUDGET);921922		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)923	}924925	fn resource_add(926		sender: T::AccountId,927		collection_id: CollectionId,928		token_id: TokenId,929		resource_properties: impl Iterator<Item = Property>,930	) -> Result<RmrkResourceId, DispatchError> {931		let collection =932			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;933		ensure!(collection.owner == sender, Error::<T>::NoPermission);934935		let sender = T::CrossAccountId::from_sub(sender);936		let budget = budget::Value::new(NESTING_BUDGET);937938		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)939			.map_err(Self::map_unique_err_to_proxy)?;940941		let pending = sender != nft_owner;942943		let resource_collection_id: CollectionId =944			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;945		let resource_collection =946			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;947948		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them949950		let resource_id = Self::create_nft(951			&sender,952			&nft_owner,953			&resource_collection,954			resource_properties.chain(955				[956					Self::rmrk_property(PendingResourceAccept, &pending)?,957					Self::rmrk_property(PendingResourceRemoval, &false)?,958				]959				.into_iter(),960			),961		)962		.map_err(|err| match err {963			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),964			err => Self::map_unique_err_to_proxy(err),965		})?;966967		Ok(resource_id.0)968	}969970	fn resource_remove(971		sender: T::AccountId,972		collection_id: CollectionId,973		nft_id: TokenId,974		resource_id: TokenId,975	) -> DispatchResult {976		let collection =977			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;978		ensure!(collection.owner == sender, Error::<T>::NoPermission);979980		let resource_collection_id: CollectionId =981			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;982		let resource_collection =983			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;984		ensure!(985			<PalletNft<T>>::token_exists(&resource_collection, resource_id),986			Error::<T>::ResourceDoesntExist987		);988989		let budget = up_data_structs::budget::Value::new(10);990		let topmost_owner =991			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;992993		let sender = T::CrossAccountId::from_sub(sender);994		if topmost_owner == sender {995			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)996				.map_err(Self::map_unique_err_to_proxy)?;997		} else {998			<PalletNft<T>>::set_scoped_token_property(999				resource_collection_id,1000				resource_id,1001				PropertyScope::Rmrk,1002				Self::rmrk_property(PendingResourceRemoval, &true)?,1003			)?;1004		}10051006		Ok(())1007	}10081009	fn change_collection_owner(1010		collection_id: CollectionId,1011		collection_type: misc::CollectionType,1012		sender: T::AccountId,1013		new_owner: T::AccountId,1014	) -> DispatchResult {1015		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1016		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10171018		let mut collection = collection.into_inner();10191020		collection.owner = new_owner;1021		collection.save()1022	}10231024	fn check_collection_owner(1025		collection: &NonfungibleHandle<T>,1026		account: &T::CrossAccountId,1027	) -> DispatchResult {1028		collection1029			.check_is_owner(account)1030			.map_err(Self::map_unique_err_to_proxy)1031	}10321033	pub fn last_collection_idx() -> RmrkCollectionId {1034		<CollectionIndex<T>>::get()1035	}10361037	pub fn unique_collection_id(1038		rmrk_collection_id: RmrkCollectionId,1039	) -> Result<CollectionId, DispatchError> {1040		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1041			.map_err(|_| <Error<T>>::CollectionUnknown.into())1042	}10431044	pub fn rmrk_collection_id(1045		unique_collection_id: CollectionId,1046	) -> Result<RmrkCollectionId, DispatchError> {1047		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1048			.map_err(|_| <Error<T>>::CollectionUnknown.into())1049	}10501051	pub fn get_nft_collection(1052		collection_id: CollectionId,1053	) -> Result<NonfungibleHandle<T>, DispatchError> {1054		let collection = <CollectionHandle<T>>::try_get(collection_id)1055			.map_err(|_| <Error<T>>::CollectionUnknown)?;10561057		match collection.mode {1058			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1059			_ => Err(<Error<T>>::CollectionUnknown.into()),1060		}1061	}10621063	pub fn collection_exists(collection_id: CollectionId) -> bool {1064		<CollectionHandle<T>>::try_get(collection_id).is_ok()1065	}10661067	pub fn get_collection_property(1068		collection_id: CollectionId,1069		key: RmrkProperty,1070	) -> Result<PropertyValue, DispatchError> {1071		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1072			.get(&Self::rmrk_property_key(key)?)1073			.ok_or(<Error<T>>::CollectionUnknown)?1074			.clone();10751076		Ok(collection_property)1077	}10781079	pub fn get_collection_property_decoded<V: Decode>(1080		collection_id: CollectionId,1081		key: RmrkProperty,1082	) -> Result<V, DispatchError> {1083		Self::decode_property(Self::get_collection_property(collection_id, key)?)1084	}10851086	pub fn get_collection_type(1087		collection_id: CollectionId,1088	) -> Result<misc::CollectionType, DispatchError> {1089		Self::get_collection_property_decoded(collection_id, CollectionType)1090			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1091	}10921093	pub fn ensure_collection_type(1094		collection_id: CollectionId,1095		collection_type: misc::CollectionType,1096	) -> DispatchResult {1097		let actual_type = Self::get_collection_type(collection_id)?;1098		ensure!(1099			actual_type == collection_type,1100			<CommonError<T>>::NoPermission1101		);11021103		Ok(())1104	}11051106	pub fn get_typed_nft_collection(1107		collection_id: CollectionId,1108		collection_type: misc::CollectionType,1109	) -> Result<NonfungibleHandle<T>, DispatchError> {1110		Self::ensure_collection_type(collection_id, collection_type)?;11111112		Self::get_nft_collection(collection_id)1113	}11141115	pub fn get_typed_nft_collection_mapped(1116		rmrk_collection_id: RmrkCollectionId,1117		collection_type: misc::CollectionType,1118	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1119		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11201121		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11221123		Ok((collection, unique_collection_id))1124	}11251126	pub fn get_nft_property(1127		collection_id: CollectionId,1128		nft_id: TokenId,1129		key: RmrkProperty,1130	) -> Result<PropertyValue, DispatchError> {1131		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1132			.get(&Self::rmrk_property_key(key)?)1133			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1134			.clone();11351136		Ok(nft_property)1137	}11381139	pub fn get_nft_property_decoded<V: Decode>(1140		collection_id: CollectionId,1141		nft_id: TokenId,1142		key: RmrkProperty,1143	) -> Result<V, DispatchError> {1144		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1145	}11461147	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1148		<TokenData<T>>::contains_key((collection_id, nft_id))1149	}11501151	pub fn get_nft_type(1152		collection_id: CollectionId,1153		token_id: TokenId,1154	) -> Result<NftType, DispatchError> {1155		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1156			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1157	}11581159	pub fn ensure_nft_type(1160		collection_id: CollectionId,1161		token_id: TokenId,1162		nft_type: NftType,1163	) -> DispatchResult {1164		let actual_type = Self::get_nft_type(collection_id, token_id)?;1165		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11661167		Ok(())1168	}11691170	pub fn ensure_nft_owner(1171		collection_id: CollectionId,1172		token_id: TokenId,1173		possible_owner: &T::CrossAccountId,1174		nesting_budget: &dyn budget::Budget,1175	) -> DispatchResult {1176		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1177			possible_owner.clone(),1178			collection_id,1179			token_id,1180			None,1181			nesting_budget,1182		)?;11831184		ensure!(is_owned, <Error<T>>::NoPermission);11851186		Ok(())1187	}11881189	pub fn filter_user_properties<Key, Value, R, Mapper>(1190		collection_id: CollectionId,1191		token_id: Option<TokenId>,1192		filter_keys: Option<Vec<RmrkPropertyKey>>,1193		mapper: Mapper,1194	) -> Result<Vec<R>, DispatchError>1195	where1196		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1197		Value: Decode + Default,1198		Mapper: Fn(Key, Value) -> R,1199	{1200		filter_keys1201			.map(|keys| {1202				let properties = keys1203					.into_iter()1204					.filter_map(|key| {1205						let key: Key = key.try_into().ok()?;12061207						let value = match token_id {1208							Some(token_id) => Self::get_nft_property_decoded(1209								collection_id,1210								token_id,1211								UserProperty(key.as_ref()),1212							),1213							None => Self::get_collection_property_decoded(1214								collection_id,1215								UserProperty(key.as_ref()),1216							),1217						}1218						.ok()?;12191220						Some(mapper(key, value))1221					})1222					.collect();12231224				Ok(properties)1225			})1226			.unwrap_or_else(|| {1227				let properties =1228					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12291230				Ok(properties)1231			})1232	}12331234	pub fn iterate_user_properties<Key, Value, R, Mapper>(1235		collection_id: CollectionId,1236		token_id: Option<TokenId>,1237		mapper: Mapper,1238	) -> Result<impl Iterator<Item = R>, DispatchError>1239	where1240		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1241		Value: Decode + Default,1242		Mapper: Fn(Key, Value) -> R,1243	{1244		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12451246		let properties = match token_id {1247			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1248			None => <PalletCommon<T>>::collection_properties(collection_id),1249		};12501251		let properties = properties.into_iter().filter_map(move |(key, value)| {1252			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12531254			let key: Key = key.to_vec().try_into().ok()?;1255			let value: Value = value.decode().ok()?;12561257			Some(mapper(key, value))1258		});12591260		Ok(properties)1261	}12621263	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1264		map_unique_err_to_proxy! {1265			match err {1266				CommonError::NoPermission => NoPermission,1267				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1268				CommonError::PublicMintingNotAllowed => NoPermission,1269				CommonError::TokenNotFound => NoAvailableNftId,1270				CommonError::ApprovedValueTooLow => NoPermission,1271				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1272				StructureError::TokenNotFound => NoAvailableNftId,1273				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1274			}1275		}1276	}1277}