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

difftreelog

refactor(rmrk-rpc) merged mapped calls for collection and id into one

Fahrrader2022-06-03parent: #00b9fd6.patch.diff
in: master

2 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{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;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		CollectionFullOrLocked,132		ResourceDoesntExist,133	}134135	#[pallet::call]136	impl<T: Config> Pallet<T> {137		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138		#[transactional]139		pub fn create_collection(140			origin: OriginFor<T>,141			metadata: RmrkString,142			max: Option<u32>,143			symbol: RmrkCollectionSymbol,144		) -> DispatchResult {145			let sender = ensure_signed(origin)?;146147			let limits = CollectionLimits {148				owner_can_transfer: Some(false),149				token_limit: max,150				..Default::default()151			};152153			let data = CreateCollectionData {154				limits: Some(limits),155				token_prefix: symbol156					.into_inner()157					.try_into()158					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159				permissions: Some(CollectionPermissions {160					nesting: Some(NestingRule::Owner),161					..Default::default()162				}),163				..Default::default()164			};165166			let unique_collection_id = Self::init_collection(167				T::CrossAccountId::from_sub(sender.clone()),168				data,169				[170					Self::rmrk_property(Metadata, &metadata)?,171					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172				]173				.into_iter(),174			)?;175			let rmrk_collection_id = <CollectionIndex<T>>::get();176177			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180			<CollectionIndex<T>>::mutate(|n| *n += 1);181182			Self::deposit_event(Event::CollectionCreated {183				issuer: sender,184				collection_id: rmrk_collection_id,185			});186187			Ok(())188		}189190		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191		#[transactional]192		pub fn destroy_collection(193			origin: OriginFor<T>,194			collection_id: RmrkCollectionId,195		) -> DispatchResult {196			let sender = ensure_signed(origin)?;197			let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199			let collection = Self::get_typed_nft_collection(200				Self::unique_collection_id(collection_id)?,201				misc::CollectionType::Regular,202			)?;203204			ensure!(205				collection.total_supply() == 0,206				<Error<T>>::CollectionNotEmpty207			);208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_common_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		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let sender = T::CrossAccountId::from_sub(sender);289			let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292				recipient: recipient.unwrap_or_else(|| owner.clone()),293				amount,294			});295296			let collection = Self::get_typed_nft_collection(297				Self::unique_collection_id(collection_id)?,298				misc::CollectionType::Regular,299			)?;300301			let nft_id = Self::create_nft(302				&sender,303				&cross_owner,304				&collection,305				[306					Self::rmrk_property(TokenType, &NftType::Regular)?,307					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308					Self::rmrk_property(Metadata, &metadata)?,309					Self::rmrk_property(Equipped, &false)?,310					Self::rmrk_property(311						ResourceCollection,312						&Self::init_collection(313							sender.clone(),314							CreateCollectionData {315								..Default::default()316							},317							[Self::rmrk_property(318								CollectionType,319								&misc::CollectionType::Resource,320							)?]321							.into_iter(),322						)?,323					)?, // todo possibly add limits to the collection if rmrk warrants them324					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325				]326				.into_iter(),327			)328			.map_err(|err| match err {329				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330				err => Self::map_common_err_to_proxy(err),331			})?;332333			Self::deposit_event(Event::NftMinted {334				owner,335				collection_id,336				nft_id: nft_id.0,337			});338339			Ok(())340		}341342		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343		#[transactional]344		pub fn burn_nft(345			origin: OriginFor<T>,346			collection_id: RmrkCollectionId,347			nft_id: RmrkNftId,348		) -> DispatchResult {349			let sender = ensure_signed(origin)?;350			let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352			Self::destroy_nft(353				cross_sender,354				Self::unique_collection_id(collection_id)?,355				misc::CollectionType::Regular,356				nft_id.into(),357			)?;358359			Self::deposit_event(Event::NFTBurned {360				owner: sender,361				nft_id,362			});363364			Ok(())365		}366367		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368		#[transactional]369		pub fn send(370			origin: OriginFor<T>,371			rmrk_collection_id: RmrkCollectionId,372			rmrk_nft_id: RmrkNftId,373			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374		) -> DispatchResult {375			let sender = ensure_signed(origin.clone())?;376			let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379			let nft_id = rmrk_nft_id.into();380381			let token_data = <TokenData<T>>::get((collection_id, nft_id))382				.ok_or(<Error<T>>::NoAvailableNftId)?;383384			let from = token_data.owner;385386			let collection = Self::get_typed_nft_collection(387				collection_id,388				misc::CollectionType::Regular,389			)?;390391			let budget = budget::Value::new(NESTING_BUDGET);392393			let target_owner;394395			match new_owner {396				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397					target_owner = T::CrossAccountId::from_sub(account_id);398				},399				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400					let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402					target_owner = T::CrossTokenAddressMapping::token_to_address(403						target_collection_id,404						target_nft_id.into(),405					);406407					let spender = <PalletStructure<T>>::get_indirect_owner(408						target_collection_id,409						target_nft_id.into(),410						Some((collection_id, nft_id)),411						&budget,412					)?;413414					let is_approval_required = cross_sender != spender;415416					if is_approval_required {417						<PalletNft<T>>::set_allowance(418							&collection,419							&cross_sender,420							nft_id,421							Some(&spender),422							&budget423						).map_err(Self::map_common_err_to_proxy)?;424425						return Ok(());426					}427				}428			}429430			<PalletNft<T>>::transfer_from(431				&collection,432				&cross_sender,433				&from,434				&target_owner,435				nft_id,436				&budget437			).map_err(Self::map_common_err_to_proxy)?;438439			Ok(())440		}441442		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]443		#[transactional]444		pub fn set_property(445			origin: OriginFor<T>,446			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,447			maybe_nft_id: Option<RmrkNftId>,448			key: RmrkKeyString,449			value: RmrkValueString,450		) -> DispatchResult {451			let sender = ensure_signed(origin)?;452			let sender = T::CrossAccountId::from_sub(sender);453454			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455			let budget = budget::Value::new(NESTING_BUDGET);456457			match maybe_nft_id {458				Some(nft_id) => {459					let token_id: TokenId = nft_id.into();460461					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;462					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;463464					<PalletNft<T>>::set_scoped_token_property(465						collection_id,466						token_id,467						PropertyScope::Rmrk,468						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,469					)?;470				}471				None => {472					let collection = Self::get_typed_nft_collection(473						collection_id,474						misc::CollectionType::Regular,475					)?;476477					Self::check_collection_owner(&collection, &sender)?;478479					<PalletCommon<T>>::set_scoped_collection_property(480						collection_id,481						PropertyScope::Rmrk,482						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483					)?;484				}485			}486487			Self::deposit_event(Event::PropertySet {488				collection_id: rmrk_collection_id,489				maybe_nft_id,490				key,491				value,492			});493494			Ok(())495		}496497		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]498		#[transactional]499		pub fn add_basic_resource(500			origin: OriginFor<T>,501			collection_id: RmrkCollectionId,502			nft_id: RmrkNftId,503			resource: RmrkBasicResource,504		) -> DispatchResult {505			let sender = ensure_signed(origin.clone())?;506507			let resource_id = Self::resource_add(508				sender,509				Self::unique_collection_id(collection_id)?,510				nft_id.into(),511				[512					Self::rmrk_property(TokenType, &NftType::Resource)?,513					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,514					Self::rmrk_property(Src, &resource.src)?,515					Self::rmrk_property(Metadata, &resource.metadata)?,516					Self::rmrk_property(License, &resource.license)?,517					Self::rmrk_property(Thumb, &resource.thumb)?,518				]519				.into_iter(),520			)?;521522			Self::deposit_event(Event::ResourceAdded {523				nft_id,524				resource_id,525			});526			Ok(())527		}528529		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]530		#[transactional]531		pub fn add_composable_resource(532			origin: OriginFor<T>,533			collection_id: RmrkCollectionId,534			nft_id: RmrkNftId,535			_resource_id: RmrkBoundedResource,536			resource: RmrkComposableResource,537		) -> DispatchResult {538			let sender = ensure_signed(origin.clone())?;539540			let resource_id = Self::resource_add(541				sender,542				Self::unique_collection_id(collection_id)?,543				nft_id.into(),544				[545					Self::rmrk_property(TokenType, &NftType::Resource)?,546					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,547					Self::rmrk_property(Parts, &resource.parts)?,548					Self::rmrk_property(Base, &resource.base)?,549					Self::rmrk_property(Src, &resource.src)?,550					Self::rmrk_property(Metadata, &resource.metadata)?,551					Self::rmrk_property(License, &resource.license)?,552					Self::rmrk_property(Thumb, &resource.thumb)?,553				]554				.into_iter(),555			)?;556557			Self::deposit_event(Event::ResourceAdded {558				nft_id,559				resource_id,560			});561			Ok(())562		}563564		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]565		#[transactional]566		pub fn add_slot_resource(567			origin: OriginFor<T>,568			collection_id: RmrkCollectionId,569			nft_id: RmrkNftId,570			resource: RmrkSlotResource,571		) -> DispatchResult {572			let sender = ensure_signed(origin.clone())?;573574			let resource_id = Self::resource_add(575				sender,576				Self::unique_collection_id(collection_id)?,577				nft_id.into(),578				[579					Self::rmrk_property(TokenType, &NftType::Resource)?,580					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,581					Self::rmrk_property(Base, &resource.base)?,582					Self::rmrk_property(Src, &resource.src)?,583					Self::rmrk_property(Metadata, &resource.metadata)?,584					Self::rmrk_property(Slot, &resource.slot)?,585					Self::rmrk_property(License, &resource.license)?,586					Self::rmrk_property(Thumb, &resource.thumb)?,587				]588				.into_iter(),589			)?;590591			Self::deposit_event(Event::ResourceAdded {592				nft_id,593				resource_id,594			});595			Ok(())596		}597598		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]599		#[transactional]600		pub fn remove_resource(601			origin: OriginFor<T>,602			collection_id: RmrkCollectionId,603			nft_id: RmrkNftId,604			resource_id: RmrkResourceId,605		) -> DispatchResult {606			let sender = ensure_signed(origin.clone())?;607608			Self::resource_remove(609				sender,610				Self::unique_collection_id(collection_id)?,611				nft_id.into(),612				resource_id.into(),613			)?;614615			Self::deposit_event(Event::ResourceRemoval {616				nft_id,617				resource_id,618			});619			Ok(())620		}621	}622}623624impl<T: Config> Pallet<T> {625	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {626		let key = rmrk_key.to_key::<T>()?;627628		let scoped_key = PropertyScope::Rmrk629			.apply(key)630			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;631632		Ok(scoped_key)633	}634635	// todo think about renaming these636	pub fn rmrk_property<E: Encode>(637		rmrk_key: RmrkProperty,638		value: &E,639	) -> Result<Property, DispatchError> {640		let key = rmrk_key.to_key::<T>()?;641642		let value = value643			.encode()644			.try_into()645			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;646647		let property = Property { key, value };648649		Ok(property)650	}651652	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {653		vec.decode()654			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())655	}656657	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>658	where659		BoundedVec<u8, S>: TryFrom<Vec<u8>>,660	{661		vec.rebind()662			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())663	}664665	fn init_collection(666		sender: T::CrossAccountId,667		data: CreateCollectionData<T::AccountId>,668		properties: impl Iterator<Item = Property>,669	) -> Result<CollectionId, DispatchError> {670		let collection_id = <PalletNft<T>>::init_collection(sender, data);671672		if let Err(DispatchError::Arithmetic(_)) = &collection_id {673			return Err(<Error<T>>::NoAvailableCollectionId.into());674		}675676		<PalletCommon<T>>::set_scoped_collection_properties(677			collection_id?,678			PropertyScope::Rmrk,679			properties,680		)?;681682		collection_id683	}684685	pub fn create_nft(686		sender: &T::CrossAccountId,687		owner: &T::CrossAccountId,688		collection: &NonfungibleHandle<T>,689		properties: impl Iterator<Item = Property>,690	) -> Result<TokenId, DispatchError> {691		let data = CreateNftExData {692			properties: BoundedVec::default(),693			owner: owner.clone(),694		};695696		let budget = budget::Value::new(NESTING_BUDGET);697698		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;699700		let nft_id = <PalletNft<T>>::current_token_id(collection.id);701702		<PalletNft<T>>::set_scoped_token_properties(703			collection.id,704			nft_id,705			PropertyScope::Rmrk,706			properties,707		)?;708709		Ok(nft_id)710	}711712	fn destroy_nft(713		sender: T::CrossAccountId,714		collection_id: CollectionId,715		collection_type: misc::CollectionType,716		token_id: TokenId,717	) -> DispatchResult {718		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;719720		<PalletNft<T>>::burn(&collection, &sender, token_id)721			.map_err(Self::map_common_err_to_proxy)?;722723		Ok(())724	}725726	fn resource_add(727		sender: T::AccountId,728		collection_id: CollectionId,729		token_id: TokenId,730		resource_properties: impl Iterator<Item = Property>,731	) -> Result<RmrkResourceId, DispatchError> {732		let collection =733			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;734		ensure!(collection.owner == sender, Error::<T>::NoPermission);735736		// Check NFT lock status // todo depends on market, maybe later737		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);738739		let sender = T::CrossAccountId::from_sub(sender);740		let budget = budget::Value::new(NESTING_BUDGET);741		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();742743		let resource_collection_id: CollectionId =744			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;745		let resource_collection =746			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;747748		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them749750		let resource_id = Self::create_nft(751			&sender, // todo owner of the nft?752			&sender,753			&resource_collection,754			resource_properties.chain(755				[756					Self::rmrk_property(PendingResourceAccept, &pending)?,757					Self::rmrk_property(PendingResourceRemoval, &false)?,758				]759				.into_iter(),760			),761		)762		.map_err(|err| match err {763			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),764			err => Self::map_common_err_to_proxy(err),765		})?;766767		Ok(resource_id.0)768	}769770	fn resource_remove(771		sender: T::AccountId,772		collection_id: CollectionId,773		nft_id: TokenId,774		resource_id: TokenId,775	) -> DispatchResult {776		let collection =777			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;778		ensure!(collection.owner == sender, Error::<T>::NoPermission);779780		let resource_collection_id: CollectionId =781			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;782		let resource_collection =783			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;784		ensure!(785			<PalletNft<T>>::token_exists(&resource_collection, resource_id),786			Error::<T>::ResourceDoesntExist787		);788789		let budget = up_data_structs::budget::Value::new(10);790		let topmost_owner =791			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;792793		let sender = T::CrossAccountId::from_sub(sender);794		if topmost_owner == sender {795			<PalletNft<T>>::burn(&collection, &sender, nft_id)796				.map_err(Self::map_common_err_to_proxy)?;797		} else {798			<PalletNft<T>>::set_scoped_token_property(799				collection_id,800				nft_id,801				PropertyScope::Rmrk,802				Self::rmrk_property(PendingResourceRemoval, &true)?,803			)?;804		}805806		Ok(())807	}808809	fn change_collection_owner(810		collection_id: CollectionId,811		collection_type: misc::CollectionType,812		sender: T::AccountId,813		new_owner: T::AccountId,814	) -> DispatchResult {815		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;816		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;817818		let mut collection = collection.into_inner();819820		collection.owner = new_owner;821		collection.save()822	}823824	fn check_collection_owner(825		collection: &NonfungibleHandle<T>,826		account: &T::CrossAccountId,827	) -> DispatchResult {828		collection829			.check_is_owner(account)830			.map_err(Self::map_common_err_to_proxy)831	}832833	pub fn last_collection_idx() -> RmrkCollectionId {834		<CollectionIndex<T>>::get()835	}836837	pub fn unique_collection_id(838		rmrk_collection_id: RmrkCollectionId,839	) -> Result<CollectionId, DispatchError> {840		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)841			.map_err(|_| <Error<T>>::CollectionUnknown.into())842	}843844	pub fn rmrk_collection_id(845		unique_collection_id: CollectionId846	) -> Result<RmrkCollectionId, DispatchError> {847		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)848			.map_err(|_| <Error<T>>::CollectionUnknown.into())849	}850851	pub fn get_nft_collection(852		collection_id: CollectionId,853	) -> Result<NonfungibleHandle<T>, DispatchError> {854		let collection = <CollectionHandle<T>>::try_get(collection_id)855			.map_err(|_| <Error<T>>::CollectionUnknown)?;856857		match collection.mode {858			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),859			_ => Err(<Error<T>>::CollectionUnknown.into()),860		}861	}862863	pub fn collection_exists(collection_id: CollectionId) -> bool {864		<CollectionHandle<T>>::try_get(collection_id).is_ok()865	}866867	pub fn get_collection_property(868		collection_id: CollectionId,869		key: RmrkProperty,870	) -> Result<PropertyValue, DispatchError> {871		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)872			.get(&Self::rmrk_property_key(key)?)873			.ok_or(<Error<T>>::CollectionUnknown)?874			.clone();875876		Ok(collection_property)877	}878879	pub fn get_collection_property_decoded<V: Decode>(880		collection_id: CollectionId,881		key: RmrkProperty,882	) -> Result<V, DispatchError> {883		Self::decode_property(Self::get_collection_property(collection_id, key)?)884	}885886	pub fn get_collection_type(887		collection_id: CollectionId,888	) -> Result<misc::CollectionType, DispatchError> {889		Self::get_collection_property_decoded(collection_id, CollectionType)890			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())891	}892893	pub fn ensure_collection_type(894		collection_id: CollectionId,895		collection_type: misc::CollectionType,896	) -> DispatchResult {897		let actual_type = Self::get_collection_type(collection_id)?;898		ensure!(899			actual_type == collection_type,900			<CommonError<T>>::NoPermission901		);902903		Ok(())904	}905906	pub fn get_typed_nft_collection(907		collection_id: CollectionId,908		collection_type: misc::CollectionType,909	) -> Result<NonfungibleHandle<T>, DispatchError> {910		Self::ensure_collection_type(collection_id, collection_type)?;911912		Self::get_nft_collection(collection_id)913	}914915	pub fn get_nft_property(916		collection_id: CollectionId,917		nft_id: TokenId,918		key: RmrkProperty,919	) -> Result<PropertyValue, DispatchError> {920		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))921			.get(&Self::rmrk_property_key(key)?)922			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?923			.clone();924925		Ok(nft_property)926	}927928	pub fn get_nft_property_decoded<V: Decode>(929		collection_id: CollectionId,930		nft_id: TokenId,931		key: RmrkProperty,932	) -> Result<V, DispatchError> {933		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)934	}935936	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {937		<TokenData<T>>::contains_key((collection_id, nft_id))938	}939940	pub fn get_nft_type(941		collection_id: CollectionId,942		token_id: TokenId,943	) -> Result<NftType, DispatchError> {944		Self::get_nft_property_decoded(collection_id, token_id, TokenType)945			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())946	}947948	pub fn ensure_nft_type(949		collection_id: CollectionId,950		token_id: TokenId,951		nft_type: NftType,952	) -> DispatchResult {953		let actual_type = Self::get_nft_type(collection_id, token_id)?;954		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);955956		Ok(())957	}958959	pub fn ensure_nft_owner(960		collection_id: CollectionId,961		token_id: TokenId,962		possible_owner: &T::CrossAccountId,963		nesting_budget: &dyn budget::Budget964	) -> DispatchResult {965		let is_owned = <PalletStructure<T>>::check_indirectly_owned(966			possible_owner.clone(),967			collection_id,968			token_id,969			None,970			nesting_budget,971		)?;972973		ensure!(974			is_owned,975			<Error<T>>::NoPermission976		);977978		Ok(())979	}980981	pub fn filter_user_properties<Key, Value, R, Mapper>(982		collection_id: CollectionId,983		token_id: Option<TokenId>,984		filter_keys: Option<Vec<RmrkPropertyKey>>,985		mapper: Mapper,986	) -> Result<Vec<R>, DispatchError>987	where988		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,989		Value: Decode + Default,990		Mapper: Fn(Key, Value) -> R,991	{992		filter_keys993			.map(|keys| {994				let properties = keys995					.into_iter()996					.filter_map(|key| {997						let key: Key = key.try_into().ok()?;998999						let value = match token_id {1000							Some(token_id) => Self::get_nft_property_decoded(1001								collection_id,1002								token_id,1003								UserProperty(key.as_ref()),1004							),1005							None => Self::get_collection_property_decoded(1006								collection_id,1007								UserProperty(key.as_ref()),1008							),1009						}1010						.ok()?;10111012						Some(mapper(key, value))1013					})1014					.collect();10151016				Ok(properties)1017			})1018			.unwrap_or_else(|| {1019				let properties =1020					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10211022				Ok(properties)1023			})1024	}10251026	pub fn iterate_user_properties<Key, Value, R, Mapper>(1027		collection_id: CollectionId,1028		token_id: Option<TokenId>,1029		mapper: Mapper,1030	) -> Result<impl Iterator<Item = R>, DispatchError>1031	where1032		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1033		Value: Decode + Default,1034		Mapper: Fn(Key, Value) -> R,1035	{1036		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10371038		let properties = match token_id {1039			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1040			None => <PalletCommon<T>>::collection_properties(collection_id),1041		};10421043		let properties = properties.into_iter().filter_map(move |(key, value)| {1044			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10451046			let key: Key = key.to_vec().try_into().ok()?;1047			let value: Value = value.decode().ok()?;10481049			Some(mapper(key, value))1050		});10511052		Ok(properties)1053	}10541055	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1056		map_common_err_to_proxy! {1057			match err {1058				NoPermission => NoPermission,1059				CollectionTokenLimitExceeded => CollectionFullOrLocked,1060				PublicMintingNotAllowed => NoPermission,1061				TokenNotFound => NoAvailableNftId,1062				ApprovedValueTooLow => NoPermission1063			}1064		}1065	}1066}
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;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		CollectionFullOrLocked,132		ResourceDoesntExist,133	}134135	#[pallet::call]136	impl<T: Config> Pallet<T> {137		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138		#[transactional]139		pub fn create_collection(140			origin: OriginFor<T>,141			metadata: RmrkString,142			max: Option<u32>,143			symbol: RmrkCollectionSymbol,144		) -> DispatchResult {145			let sender = ensure_signed(origin)?;146147			let limits = CollectionLimits {148				owner_can_transfer: Some(false),149				token_limit: max,150				..Default::default()151			};152153			let data = CreateCollectionData {154				limits: Some(limits),155				token_prefix: symbol156					.into_inner()157					.try_into()158					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159				permissions: Some(CollectionPermissions {160					nesting: Some(NestingRule::Owner),161					..Default::default()162				}),163				..Default::default()164			};165166			let unique_collection_id = Self::init_collection(167				T::CrossAccountId::from_sub(sender.clone()),168				data,169				[170					Self::rmrk_property(Metadata, &metadata)?,171					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172				]173				.into_iter(),174			)?;175			let rmrk_collection_id = <CollectionIndex<T>>::get();176177			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180			<CollectionIndex<T>>::mutate(|n| *n += 1);181182			Self::deposit_event(Event::CollectionCreated {183				issuer: sender,184				collection_id: rmrk_collection_id,185			});186187			Ok(())188		}189190		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191		#[transactional]192		pub fn destroy_collection(193			origin: OriginFor<T>,194			collection_id: RmrkCollectionId,195		) -> DispatchResult {196			let sender = ensure_signed(origin)?;197			let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199			let collection = Self::get_typed_nft_collection(200				Self::unique_collection_id(collection_id)?,201				misc::CollectionType::Regular,202			)?;203204			ensure!(205				collection.total_supply() == 0,206				<Error<T>>::CollectionNotEmpty207			);208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_common_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		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let sender = T::CrossAccountId::from_sub(sender);289			let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292				recipient: recipient.unwrap_or_else(|| owner.clone()),293				amount,294			});295296			let collection = Self::get_typed_nft_collection(297				Self::unique_collection_id(collection_id)?,298				misc::CollectionType::Regular,299			)?;300301			let nft_id = Self::create_nft(302				&sender,303				&cross_owner,304				&collection,305				[306					Self::rmrk_property(TokenType, &NftType::Regular)?,307					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308					Self::rmrk_property(Metadata, &metadata)?,309					Self::rmrk_property(Equipped, &false)?,310					Self::rmrk_property(311						ResourceCollection,312						&Self::init_collection(313							sender.clone(),314							CreateCollectionData {315								..Default::default()316							},317							[Self::rmrk_property(318								CollectionType,319								&misc::CollectionType::Resource,320							)?]321							.into_iter(),322						)?,323					)?, // todo possibly add limits to the collection if rmrk warrants them324					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325				]326				.into_iter(),327			)328			.map_err(|err| match err {329				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330				err => Self::map_common_err_to_proxy(err),331			})?;332333			Self::deposit_event(Event::NftMinted {334				owner,335				collection_id,336				nft_id: nft_id.0,337			});338339			Ok(())340		}341342		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343		#[transactional]344		pub fn burn_nft(345			origin: OriginFor<T>,346			collection_id: RmrkCollectionId,347			nft_id: RmrkNftId,348		) -> DispatchResult {349			let sender = ensure_signed(origin)?;350			let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352			Self::destroy_nft(353				cross_sender,354				Self::unique_collection_id(collection_id)?,355				misc::CollectionType::Regular,356				nft_id.into(),357			)?;358359			Self::deposit_event(Event::NFTBurned {360				owner: sender,361				nft_id,362			});363364			Ok(())365		}366367		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368		#[transactional]369		pub fn send(370			origin: OriginFor<T>,371			rmrk_collection_id: RmrkCollectionId,372			rmrk_nft_id: RmrkNftId,373			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374		) -> DispatchResult {375			let sender = ensure_signed(origin.clone())?;376			let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379			let nft_id = rmrk_nft_id.into();380381			let token_data = <TokenData<T>>::get((collection_id, nft_id))382				.ok_or(<Error<T>>::NoAvailableNftId)?;383384			let from = token_data.owner;385386			let collection = Self::get_typed_nft_collection(387				collection_id,388				misc::CollectionType::Regular,389			)?;390391			let budget = budget::Value::new(NESTING_BUDGET);392393			let target_owner;394395			match new_owner {396				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397					target_owner = T::CrossAccountId::from_sub(account_id);398				},399				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400					let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402					target_owner = T::CrossTokenAddressMapping::token_to_address(403						target_collection_id,404						target_nft_id.into(),405					);406407					let spender = <PalletStructure<T>>::get_indirect_owner(408						target_collection_id,409						target_nft_id.into(),410						Some((collection_id, nft_id)),411						&budget,412					)?;413414					let is_approval_required = cross_sender != spender;415416					if is_approval_required {417						<PalletNft<T>>::set_allowance(418							&collection,419							&cross_sender,420							nft_id,421							Some(&spender),422							&budget423						).map_err(Self::map_common_err_to_proxy)?;424425						return Ok(());426					}427				}428			}429430			<PalletNft<T>>::transfer_from(431				&collection,432				&cross_sender,433				&from,434				&target_owner,435				nft_id,436				&budget437			).map_err(Self::map_common_err_to_proxy)?;438439			Ok(())440		}441442		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]443		#[transactional]444		pub fn set_property(445			origin: OriginFor<T>,446			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,447			maybe_nft_id: Option<RmrkNftId>,448			key: RmrkKeyString,449			value: RmrkValueString,450		) -> DispatchResult {451			let sender = ensure_signed(origin)?;452			let sender = T::CrossAccountId::from_sub(sender);453454			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455			let budget = budget::Value::new(NESTING_BUDGET);456457			match maybe_nft_id {458				Some(nft_id) => {459					let token_id: TokenId = nft_id.into();460461					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;462					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;463464					<PalletNft<T>>::set_scoped_token_property(465						collection_id,466						token_id,467						PropertyScope::Rmrk,468						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,469					)?;470				}471				None => {472					let collection = Self::get_typed_nft_collection(473						collection_id,474						misc::CollectionType::Regular,475					)?;476477					Self::check_collection_owner(&collection, &sender)?;478479					<PalletCommon<T>>::set_scoped_collection_property(480						collection_id,481						PropertyScope::Rmrk,482						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483					)?;484				}485			}486487			Self::deposit_event(Event::PropertySet {488				collection_id: rmrk_collection_id,489				maybe_nft_id,490				key,491				value,492			});493494			Ok(())495		}496497		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]498		#[transactional]499		pub fn add_basic_resource(500			origin: OriginFor<T>,501			collection_id: RmrkCollectionId,502			nft_id: RmrkNftId,503			resource: RmrkBasicResource,504		) -> DispatchResult {505			let sender = ensure_signed(origin.clone())?;506507			let resource_id = Self::resource_add(508				sender,509				Self::unique_collection_id(collection_id)?,510				nft_id.into(),511				[512					Self::rmrk_property(TokenType, &NftType::Resource)?,513					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,514					Self::rmrk_property(Src, &resource.src)?,515					Self::rmrk_property(Metadata, &resource.metadata)?,516					Self::rmrk_property(License, &resource.license)?,517					Self::rmrk_property(Thumb, &resource.thumb)?,518				]519				.into_iter(),520			)?;521522			Self::deposit_event(Event::ResourceAdded {523				nft_id,524				resource_id,525			});526			Ok(())527		}528529		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]530		#[transactional]531		pub fn add_composable_resource(532			origin: OriginFor<T>,533			collection_id: RmrkCollectionId,534			nft_id: RmrkNftId,535			_resource_id: RmrkBoundedResource,536			resource: RmrkComposableResource,537		) -> DispatchResult {538			let sender = ensure_signed(origin.clone())?;539540			let resource_id = Self::resource_add(541				sender,542				Self::unique_collection_id(collection_id)?,543				nft_id.into(),544				[545					Self::rmrk_property(TokenType, &NftType::Resource)?,546					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,547					Self::rmrk_property(Parts, &resource.parts)?,548					Self::rmrk_property(Base, &resource.base)?,549					Self::rmrk_property(Src, &resource.src)?,550					Self::rmrk_property(Metadata, &resource.metadata)?,551					Self::rmrk_property(License, &resource.license)?,552					Self::rmrk_property(Thumb, &resource.thumb)?,553				]554				.into_iter(),555			)?;556557			Self::deposit_event(Event::ResourceAdded {558				nft_id,559				resource_id,560			});561			Ok(())562		}563564		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]565		#[transactional]566		pub fn add_slot_resource(567			origin: OriginFor<T>,568			collection_id: RmrkCollectionId,569			nft_id: RmrkNftId,570			resource: RmrkSlotResource,571		) -> DispatchResult {572			let sender = ensure_signed(origin.clone())?;573574			let resource_id = Self::resource_add(575				sender,576				Self::unique_collection_id(collection_id)?,577				nft_id.into(),578				[579					Self::rmrk_property(TokenType, &NftType::Resource)?,580					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,581					Self::rmrk_property(Base, &resource.base)?,582					Self::rmrk_property(Src, &resource.src)?,583					Self::rmrk_property(Metadata, &resource.metadata)?,584					Self::rmrk_property(Slot, &resource.slot)?,585					Self::rmrk_property(License, &resource.license)?,586					Self::rmrk_property(Thumb, &resource.thumb)?,587				]588				.into_iter(),589			)?;590591			Self::deposit_event(Event::ResourceAdded {592				nft_id,593				resource_id,594			});595			Ok(())596		}597598		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]599		#[transactional]600		pub fn remove_resource(601			origin: OriginFor<T>,602			collection_id: RmrkCollectionId,603			nft_id: RmrkNftId,604			resource_id: RmrkResourceId,605		) -> DispatchResult {606			let sender = ensure_signed(origin.clone())?;607608			Self::resource_remove(609				sender,610				Self::unique_collection_id(collection_id)?,611				nft_id.into(),612				resource_id.into(),613			)?;614615			Self::deposit_event(Event::ResourceRemoval {616				nft_id,617				resource_id,618			});619			Ok(())620		}621	}622}623624impl<T: Config> Pallet<T> {625	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {626		let key = rmrk_key.to_key::<T>()?;627628		let scoped_key = PropertyScope::Rmrk629			.apply(key)630			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;631632		Ok(scoped_key)633	}634635	// todo think about renaming these636	pub fn rmrk_property<E: Encode>(637		rmrk_key: RmrkProperty,638		value: &E,639	) -> Result<Property, DispatchError> {640		let key = rmrk_key.to_key::<T>()?;641642		let value = value643			.encode()644			.try_into()645			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;646647		let property = Property { key, value };648649		Ok(property)650	}651652	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {653		vec.decode()654			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())655	}656657	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>658	where659		BoundedVec<u8, S>: TryFrom<Vec<u8>>,660	{661		vec.rebind()662			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())663	}664665	fn init_collection(666		sender: T::CrossAccountId,667		data: CreateCollectionData<T::AccountId>,668		properties: impl Iterator<Item = Property>,669	) -> Result<CollectionId, DispatchError> {670		let collection_id = <PalletNft<T>>::init_collection(sender, data);671672		if let Err(DispatchError::Arithmetic(_)) = &collection_id {673			return Err(<Error<T>>::NoAvailableCollectionId.into());674		}675676		<PalletCommon<T>>::set_scoped_collection_properties(677			collection_id?,678			PropertyScope::Rmrk,679			properties,680		)?;681682		collection_id683	}684685	pub fn create_nft(686		sender: &T::CrossAccountId,687		owner: &T::CrossAccountId,688		collection: &NonfungibleHandle<T>,689		properties: impl Iterator<Item = Property>,690	) -> Result<TokenId, DispatchError> {691		let data = CreateNftExData {692			properties: BoundedVec::default(),693			owner: owner.clone(),694		};695696		let budget = budget::Value::new(NESTING_BUDGET);697698		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;699700		let nft_id = <PalletNft<T>>::current_token_id(collection.id);701702		<PalletNft<T>>::set_scoped_token_properties(703			collection.id,704			nft_id,705			PropertyScope::Rmrk,706			properties,707		)?;708709		Ok(nft_id)710	}711712	fn destroy_nft(713		sender: T::CrossAccountId,714		collection_id: CollectionId,715		collection_type: misc::CollectionType,716		token_id: TokenId,717	) -> DispatchResult {718		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;719720		<PalletNft<T>>::burn(&collection, &sender, token_id)721			.map_err(Self::map_common_err_to_proxy)?;722723		Ok(())724	}725726	fn resource_add(727		sender: T::AccountId,728		collection_id: CollectionId,729		token_id: TokenId,730		resource_properties: impl Iterator<Item = Property>,731	) -> Result<RmrkResourceId, DispatchError> {732		let collection =733			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;734		ensure!(collection.owner == sender, Error::<T>::NoPermission);735736		// Check NFT lock status // todo depends on market, maybe later737		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);738739		let sender = T::CrossAccountId::from_sub(sender);740		let budget = budget::Value::new(NESTING_BUDGET);741		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();742743		let resource_collection_id: CollectionId =744			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;745		let resource_collection =746			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;747748		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them749750		let resource_id = Self::create_nft(751			&sender, // todo owner of the nft?752			&sender,753			&resource_collection,754			resource_properties.chain(755				[756					Self::rmrk_property(PendingResourceAccept, &pending)?,757					Self::rmrk_property(PendingResourceRemoval, &false)?,758				]759				.into_iter(),760			),761		)762		.map_err(|err| match err {763			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),764			err => Self::map_common_err_to_proxy(err),765		})?;766767		Ok(resource_id.0)768	}769770	fn resource_remove(771		sender: T::AccountId,772		collection_id: CollectionId,773		nft_id: TokenId,774		resource_id: TokenId,775	) -> DispatchResult {776		let collection =777			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;778		ensure!(collection.owner == sender, Error::<T>::NoPermission);779780		let resource_collection_id: CollectionId =781			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;782		let resource_collection =783			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;784		ensure!(785			<PalletNft<T>>::token_exists(&resource_collection, resource_id),786			Error::<T>::ResourceDoesntExist787		);788789		let budget = up_data_structs::budget::Value::new(10);790		let topmost_owner =791			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;792793		let sender = T::CrossAccountId::from_sub(sender);794		if topmost_owner == sender {795			<PalletNft<T>>::burn(&collection, &sender, nft_id)796				.map_err(Self::map_common_err_to_proxy)?;797		} else {798			<PalletNft<T>>::set_scoped_token_property(799				collection_id,800				nft_id,801				PropertyScope::Rmrk,802				Self::rmrk_property(PendingResourceRemoval, &true)?,803			)?;804		}805806		Ok(())807	}808809	fn change_collection_owner(810		collection_id: CollectionId,811		collection_type: misc::CollectionType,812		sender: T::AccountId,813		new_owner: T::AccountId,814	) -> DispatchResult {815		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;816		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;817818		let mut collection = collection.into_inner();819820		collection.owner = new_owner;821		collection.save()822	}823824	fn check_collection_owner(825		collection: &NonfungibleHandle<T>,826		account: &T::CrossAccountId,827	) -> DispatchResult {828		collection829			.check_is_owner(account)830			.map_err(Self::map_common_err_to_proxy)831	}832833	pub fn last_collection_idx() -> RmrkCollectionId {834		<CollectionIndex<T>>::get()835	}836837	pub fn unique_collection_id(838		rmrk_collection_id: RmrkCollectionId,839	) -> Result<CollectionId, DispatchError> {840		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)841			.map_err(|_| <Error<T>>::CollectionUnknown.into())842	}843844	pub fn rmrk_collection_id(845		unique_collection_id: CollectionId846	) -> Result<RmrkCollectionId, DispatchError> {847		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)848			.map_err(|_| <Error<T>>::CollectionUnknown.into())849	}850851	pub fn get_nft_collection(852		collection_id: CollectionId,853	) -> Result<NonfungibleHandle<T>, DispatchError> {854		let collection = <CollectionHandle<T>>::try_get(collection_id)855			.map_err(|_| <Error<T>>::CollectionUnknown)?;856857		match collection.mode {858			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),859			_ => Err(<Error<T>>::CollectionUnknown.into()),860		}861	}862863	pub fn collection_exists(collection_id: CollectionId) -> bool {864		<CollectionHandle<T>>::try_get(collection_id).is_ok()865	}866867	pub fn get_collection_property(868		collection_id: CollectionId,869		key: RmrkProperty,870	) -> Result<PropertyValue, DispatchError> {871		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)872			.get(&Self::rmrk_property_key(key)?)873			.ok_or(<Error<T>>::CollectionUnknown)?874			.clone();875876		Ok(collection_property)877	}878879	pub fn get_collection_property_decoded<V: Decode>(880		collection_id: CollectionId,881		key: RmrkProperty,882	) -> Result<V, DispatchError> {883		Self::decode_property(Self::get_collection_property(collection_id, key)?)884	}885886	pub fn get_collection_type(887		collection_id: CollectionId,888	) -> Result<misc::CollectionType, DispatchError> {889		Self::get_collection_property_decoded(collection_id, CollectionType)890			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())891	}892893	pub fn ensure_collection_type(894		collection_id: CollectionId,895		collection_type: misc::CollectionType,896	) -> DispatchResult {897		let actual_type = Self::get_collection_type(collection_id)?;898		ensure!(899			actual_type == collection_type,900			<CommonError<T>>::NoPermission901		);902903		Ok(())904	}905906	pub fn get_typed_nft_collection(907		collection_id: CollectionId,908		collection_type: misc::CollectionType,909	) -> Result<NonfungibleHandle<T>, DispatchError> {910		Self::ensure_collection_type(collection_id, collection_type)?;911912		Self::get_nft_collection(collection_id)913	}914915	pub fn get_typed_nft_collection_mapped(916		rmrk_collection_id: RmrkCollectionId,917		collection_type: misc::CollectionType,918	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {919		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;920921		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;922923		Ok((collection, unique_collection_id))924	}925926	pub fn get_nft_property(927		collection_id: CollectionId,928		nft_id: TokenId,929		key: RmrkProperty,930	) -> Result<PropertyValue, DispatchError> {931		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))932			.get(&Self::rmrk_property_key(key)?)933			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?934			.clone();935936		Ok(nft_property)937	}938939	pub fn get_nft_property_decoded<V: Decode>(940		collection_id: CollectionId,941		nft_id: TokenId,942		key: RmrkProperty,943	) -> Result<V, DispatchError> {944		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)945	}946947	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {948		<TokenData<T>>::contains_key((collection_id, nft_id))949	}950951	pub fn get_nft_type(952		collection_id: CollectionId,953		token_id: TokenId,954	) -> Result<NftType, DispatchError> {955		Self::get_nft_property_decoded(collection_id, token_id, TokenType)956			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())957	}958959	pub fn ensure_nft_type(960		collection_id: CollectionId,961		token_id: TokenId,962		nft_type: NftType,963	) -> DispatchResult {964		let actual_type = Self::get_nft_type(collection_id, token_id)?;965		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);966967		Ok(())968	}969970	pub fn ensure_nft_owner(971		collection_id: CollectionId,972		token_id: TokenId,973		possible_owner: &T::CrossAccountId,974		nesting_budget: &dyn budget::Budget975	) -> DispatchResult {976		let is_owned = <PalletStructure<T>>::check_indirectly_owned(977			possible_owner.clone(),978			collection_id,979			token_id,980			None,981			nesting_budget,982		)?;983984		ensure!(985			is_owned,986			<Error<T>>::NoPermission987		);988989		Ok(())990	}991992	pub fn filter_user_properties<Key, Value, R, Mapper>(993		collection_id: CollectionId,994		token_id: Option<TokenId>,995		filter_keys: Option<Vec<RmrkPropertyKey>>,996		mapper: Mapper,997	) -> Result<Vec<R>, DispatchError>998	where999		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1000		Value: Decode + Default,1001		Mapper: Fn(Key, Value) -> R,1002	{1003		filter_keys1004			.map(|keys| {1005				let properties = keys1006					.into_iter()1007					.filter_map(|key| {1008						let key: Key = key.try_into().ok()?;10091010						let value = match token_id {1011							Some(token_id) => Self::get_nft_property_decoded(1012								collection_id,1013								token_id,1014								UserProperty(key.as_ref()),1015							),1016							None => Self::get_collection_property_decoded(1017								collection_id,1018								UserProperty(key.as_ref()),1019							),1020						}1021						.ok()?;10221023						Some(mapper(key, value))1024					})1025					.collect();10261027				Ok(properties)1028			})1029			.unwrap_or_else(|| {1030				let properties =1031					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10321033				Ok(properties)1034			})1035	}10361037	pub fn iterate_user_properties<Key, Value, R, Mapper>(1038		collection_id: CollectionId,1039		token_id: Option<TokenId>,1040		mapper: Mapper,1041	) -> Result<impl Iterator<Item = R>, DispatchError>1042	where1043		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1044		Value: Decode + Default,1045		Mapper: Fn(Key, Value) -> R,1046	{1047		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10481049		let properties = match token_id {1050			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1051			None => <PalletCommon<T>>::collection_properties(collection_id),1052		};10531054		let properties = properties.into_iter().filter_map(move |(key, value)| {1055			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10561057			let key: Key = key.to_vec().try_into().ok()?;1058			let value: Value = value.decode().ok()?;10591060			Some(mapper(key, value))1061		});10621063		Ok(properties)1064	}10651066	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1067		map_common_err_to_proxy! {1068			match err {1069				NoPermission => NoPermission,1070				CollectionTokenLimitExceeded => CollectionFullOrLocked,1071				PublicMintingNotAllowed => NoPermission,1072				TokenNotFound => NoAvailableNftId,1073				ApprovedValueTooLow => NoPermission1074			}1075		}1076	}1077}
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -147,12 +147,7 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(None)
-                    };
-
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
                     };
@@ -173,11 +168,7 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(None)
-                    };
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
                     };
@@ -213,11 +204,11 @@
                     use pallet_common::CommonCollectionOperations;
 
                     let cross_account_id = CrossAccountId::from_sub(account_id);
-                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(Vec::new())
+
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+                        Ok(c) => c,
+                        Err(_) => return Ok(Vec::new()),
                     };
-                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
 
                     Ok(
                         collection.account_tokens(cross_account_id)
@@ -387,11 +378,7 @@
                         RmrkProperty, misc::{CollectionType},
                     };
 
-                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(None)
-                    };
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
                     };
@@ -407,12 +394,11 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(Vec::new())
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+                        Ok(c) => c,
+                        Err(_) => return Ok(Vec::new()),
                     };
-                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); }
-
+                    
                     let parts = collection.collection_tokens()
                         .into_iter()
                         .filter_map(|token_id| {
@@ -442,14 +428,10 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(Vec::new())
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+                        Ok(c) => c,
+                        Err(_) => return Ok(Vec::new()),
                     };
-                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
-                        return Ok(Vec::new());
-                    }
-
 
                     let theme_names = collection.collection_tokens()
                         .iter()
@@ -475,13 +457,10 @@
                     };
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
-                        Ok(id) => id,
-                        Err(_) => return Ok(None)
+                    let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+                        Ok(c) => c,
+                        Err(_) => return Ok(None),
                     };
-                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
-                        return Ok(None);
-                    }
 
                     let theme_info = collection.collection_tokens()
                         .into_iter()