git.delta.rocks / unique-network / refs/commits / 47e3bf21a896

difftreelog

feat(rmrk-proxy) separate collection ids for unique and rmrk and their mapping

Fahrrader2022-05-31parent: #5d01a90.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::*;24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_evm::account::CrossAccountId;29use core::convert::AsRef;3031pub use pallet::*;3233pub mod misc;34pub mod property;3536use misc::*;37pub use property::*;3839use RmrkProperty::*;4041#[frame_support::pallet]42pub mod pallet {43	use super::*;44	use pallet_evm::account;4546	#[pallet::config]47	pub trait Config:48		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config49	{50		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;51	}5253	#[pallet::storage]54	#[pallet::getter(fn collection_index)]55	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5657	#[pallet::pallet]58	#[pallet::generate_store(pub(super) trait Store)]59	pub struct Pallet<T>(_);6061	#[pallet::event]62	#[pallet::generate_deposit(pub(super) fn deposit_event)]63	pub enum Event<T: Config> {64		CollectionCreated {65			issuer: T::AccountId,66			collection_id: RmrkCollectionId,67		},68		CollectionDestroyed {69			issuer: T::AccountId,70			collection_id: RmrkCollectionId,71		},72		IssuerChanged {73			old_issuer: T::AccountId,74			new_issuer: T::AccountId,75			collection_id: RmrkCollectionId,76		},77		CollectionLocked {78			issuer: T::AccountId,79			collection_id: RmrkCollectionId,80		},81		NftMinted {82			owner: T::AccountId,83			collection_id: RmrkCollectionId,84			nft_id: RmrkNftId,85		},86		NFTBurned {87			owner: T::AccountId,88			nft_id: RmrkNftId,89		},90		PropertySet {91			collection_id: RmrkCollectionId,92			maybe_nft_id: Option<RmrkNftId>,93			key: RmrkKeyString,94			value: RmrkValueString,95		},96	}9798	#[pallet::error]99	pub enum Error<T> {100		/* Unique-specific events */101		CorruptedCollectionType,102		NftTypeEncodeError,103		RmrkPropertyKeyIsTooLong,104		RmrkPropertyValueIsTooLong,105106		/* RMRK compatible events */107		CollectionNotEmpty,108		NoAvailableCollectionId,109		NoAvailableNftId,110		CollectionUnknown,111		NoPermission,112		CollectionFullOrLocked,113	}114115	#[pallet::call]116	impl<T: Config> Pallet<T> {117		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]118		#[transactional]119		pub fn create_collection(120			origin: OriginFor<T>,121			metadata: RmrkString,122			max: Option<u32>,123			symbol: RmrkCollectionSymbol,124		) -> DispatchResult {125			let sender = ensure_signed(origin)?;126127			let limits = CollectionLimits {128				owner_can_transfer: Some(false),129				token_limit: max,130				..Default::default()131			};132133			let data = CreateCollectionData {134				limits: Some(limits),135				token_prefix: symbol136					.into_inner()137					.try_into()138					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,139				..Default::default()140			};141142			let collection_id_res =143				<PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);144145			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {146				return Err(<Error<T>>::NoAvailableCollectionId.into());147			}148149			let collection_id = collection_id_res?;150151			<PalletCommon<T>>::set_scoped_collection_properties(152				collection_id,153				PropertyScope::Rmrk,154				[155					Self::rmrk_property(Metadata, &metadata)?,156					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,157				]158				.into_iter(),159			)?;160161			<CollectionIndex<T>>::mutate(|n| *n += 1);162163			Self::deposit_event(Event::CollectionCreated {164				issuer: sender,165				collection_id: collection_id.0,166			});167168			Ok(())169		}170171		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]172		#[transactional]173		pub fn destroy_collection(174			origin: OriginFor<T>,175			collection_id: RmrkCollectionId,176		) -> DispatchResult {177			let sender = ensure_signed(origin)?;178			let cross_sender = T::CrossAccountId::from_sub(sender.clone());179180			let unique_collection_id = collection_id.into();181182			let collection = Self::get_typed_nft_collection(183				unique_collection_id,184				misc::CollectionType::Regular,185			)?;186187			ensure!(188				collection.total_supply() == 0,189				<Error<T>>::CollectionNotEmpty190			);191192			<PalletNft<T>>::destroy_collection(collection, &cross_sender)193				.map_err(Self::map_common_err_to_proxy)?;194195			Self::deposit_event(Event::CollectionDestroyed {196				issuer: sender,197				collection_id,198			});199200			Ok(())201		}202203		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]204		#[transactional]205		pub fn change_collection_issuer(206			origin: OriginFor<T>,207			collection_id: RmrkCollectionId,208			new_issuer: <T::Lookup as StaticLookup>::Source,209		) -> DispatchResult {210			let sender = ensure_signed(origin)?;211212			let new_issuer = T::Lookup::lookup(new_issuer)?;213214			Self::change_collection_owner(215				collection_id.into(),216				misc::CollectionType::Regular,217				sender.clone(),218				new_issuer.clone(),219			)?;220221			Self::deposit_event(Event::IssuerChanged {222				old_issuer: sender,223				new_issuer,224				collection_id,225			});226227			Ok(())228		}229230		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]231		#[transactional]232		pub fn lock_collection(233			origin: OriginFor<T>,234			collection_id: RmrkCollectionId,235		) -> DispatchResult {236			let sender = ensure_signed(origin)?;237			let cross_sender = T::CrossAccountId::from_sub(sender.clone());238239			let collection = Self::get_typed_nft_collection(240				collection_id.into(),241				misc::CollectionType::Regular,242			)?;243244			Self::check_collection_owner(&collection, &cross_sender)?;245246			let token_count = collection.total_supply();247248			let mut collection = collection.into_inner();249			collection.limits.token_limit = Some(token_count);250			collection.save()?;251252			Self::deposit_event(Event::CollectionLocked {253				issuer: sender,254				collection_id,255			});256257			Ok(())258		}259260		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]261		#[transactional]262		pub fn mint_nft(263			origin: OriginFor<T>,264			owner: T::AccountId,265			collection_id: RmrkCollectionId,266			recipient: Option<T::AccountId>,267			royalty_amount: Option<Permill>,268			metadata: RmrkString,269		) -> DispatchResult {270			let sender = ensure_signed(origin)?;271			let sender = T::CrossAccountId::from_sub(sender);272			let cross_owner = T::CrossAccountId::from_sub(owner.clone());273274			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {275				recipient: recipient.unwrap_or_else(|| owner.clone()),276				amount,277			});278279			let collection = Self::get_typed_nft_collection(280				collection_id.into(),281				misc::CollectionType::Regular,282			)?;283284			let nft_id = Self::create_nft(285				&sender,286				&cross_owner,287				&collection,288				NftType::Regular,289				[290					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,291					Self::rmrk_property(Metadata, &metadata)?,292					Self::rmrk_property(Equipped, &false)?,293					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,294					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,295				]296				.into_iter(),297			)298			.map_err(|err| match err {299				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),300				err => Self::map_common_err_to_proxy(err),301			})?;302303			Self::deposit_event(Event::NftMinted {304				owner,305				collection_id,306				nft_id: nft_id.0,307			});308309			Ok(())310		}311312		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]313		#[transactional]314		pub fn burn_nft(315			origin: OriginFor<T>,316			collection_id: RmrkCollectionId,317			nft_id: RmrkNftId,318		) -> DispatchResult {319			let sender = ensure_signed(origin)?;320			let cross_sender = T::CrossAccountId::from_sub(sender.clone());321322			Self::destroy_nft(323				cross_sender,324				collection_id.into(),325				misc::CollectionType::Regular,326				nft_id.into(),327			)?;328329			Self::deposit_event(Event::NFTBurned {330				owner: sender,331				nft_id,332			});333334			Ok(())335		}336337		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]338		#[transactional]339		pub fn set_property(340			origin: OriginFor<T>,341			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,342			maybe_nft_id: Option<RmrkNftId>,343			key: RmrkKeyString,344			value: RmrkValueString,345		) -> DispatchResult {346			let sender = ensure_signed(origin)?;347			let sender = T::CrossAccountId::from_sub(sender);348349			let collection_id: CollectionId = rmrk_collection_id.into();350351			match maybe_nft_id {352				Some(nft_id) => {353					let token_id: TokenId = nft_id.into();354355					Self::ensure_nft_owner(collection_id, token_id, &sender)?;356					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;357358					<PalletNft<T>>::set_scoped_token_property(359						collection_id,360						token_id,361						PropertyScope::Rmrk,362						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,363					)?;364				}365				None => {366					let collection = Self::get_typed_nft_collection(367						collection_id,368						misc::CollectionType::Regular,369					)?;370371					Self::check_collection_owner(&collection, &sender)?;372373					<PalletCommon<T>>::set_scoped_collection_property(374						collection_id,375						PropertyScope::Rmrk,376						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,377					)?;378				}379			}380381			Self::deposit_event(Event::PropertySet {382				collection_id: rmrk_collection_id,383				maybe_nft_id,384				key,385				value,386			});387388			Ok(())389		}390	}391}392393impl<T: Config> Pallet<T> {394	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {395		let key = rmrk_key.to_key::<T>()?;396397		let scoped_key = PropertyScope::Rmrk398			.apply(key)399			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;400401		Ok(scoped_key)402	}403404	pub fn rmrk_property<E: Encode>(405		rmrk_key: RmrkProperty,406		value: &E,407	) -> Result<Property, DispatchError> {408		let key = rmrk_key.to_key::<T>()?;409410		let value = value411			.encode()412			.try_into()413			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;414415		let property = Property { key, value };416417		Ok(property)418	}419420	pub fn create_nft(421		sender: &T::CrossAccountId,422		owner: &T::CrossAccountId,423		collection: &NonfungibleHandle<T>,424		nft_type: NftType,425		properties: impl Iterator<Item = Property>,426	) -> Result<TokenId, DispatchError> {427		todo!("store nft type");428		let data = CreateNftExData {429			properties: BoundedVec::default(),430			owner: owner.clone(),431		};432433		let budget = budget::Value::new(2);434435		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;436437		let nft_id = <PalletNft<T>>::current_token_id(collection.id);438439		<PalletNft<T>>::set_scoped_token_properties(440			collection.id,441			nft_id,442			PropertyScope::Rmrk,443			properties,444		)?;445446		Ok(nft_id)447	}448449	fn destroy_nft(450		sender: T::CrossAccountId,451		collection_id: CollectionId,452		collection_type: misc::CollectionType,453		token_id: TokenId,454	) -> DispatchResult {455		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;456457		<PalletNft<T>>::burn(&collection, &sender, token_id)458			.map_err(Self::map_common_err_to_proxy)?;459460		Ok(())461	}462463	fn change_collection_owner(464		collection_id: CollectionId,465		collection_type: misc::CollectionType,466		sender: T::AccountId,467		new_owner: T::AccountId,468	) -> DispatchResult {469		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;470		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;471472		let mut collection = collection.into_inner();473474		collection.owner = new_owner;475		collection.save()476	}477478	fn check_collection_owner(479		collection: &NonfungibleHandle<T>,480		account: &T::CrossAccountId,481	) -> DispatchResult {482		collection483			.check_is_owner(account)484			.map_err(Self::map_common_err_to_proxy)485	}486487	pub fn last_collection_idx() -> RmrkCollectionId {488		<CollectionIndex<T>>::get()489	}490491	pub fn get_nft_collection(492		collection_id: CollectionId,493	) -> Result<NonfungibleHandle<T>, DispatchError> {494		let collection = <CollectionHandle<T>>::try_get(collection_id)495			.map_err(|_| <Error<T>>::CollectionUnknown)?;496497		match collection.mode {498			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),499			_ => Err(<Error<T>>::CollectionUnknown.into()),500		}501	}502503	pub fn collection_exists(collection_id: CollectionId) -> bool {504		<CollectionHandle<T>>::try_get(collection_id).is_ok()505	}506507	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {508		<TokenData<T>>::contains_key((collection_id, nft_id))509	}510511	pub fn get_collection_property(512		collection_id: CollectionId,513		key: RmrkProperty,514	) -> Result<PropertyValue, DispatchError> {515		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)516			.get(&Self::rmrk_property_key(key)?)517			.ok_or(<Error<T>>::CollectionUnknown)?518			.clone();519520		Ok(collection_property)521	}522523	pub fn get_collection_type(524		collection_id: CollectionId,525	) -> Result<misc::CollectionType, DispatchError> {526		let value = Self::get_collection_property(collection_id, CollectionType)?;527528		let mut value = value.as_slice();529530		misc::CollectionType::decode(&mut value)531			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())532	}533534	pub fn ensure_collection_type(535		collection_id: CollectionId,536		collection_type: misc::CollectionType,537	) -> DispatchResult {538		let actual_type = Self::get_collection_type(collection_id)?;539		ensure!(540			actual_type == collection_type,541			<CommonError<T>>::NoPermission542		);543544		Ok(())545	}546547	pub fn get_nft_property(548		collection_id: CollectionId,549		nft_id: TokenId,550		key: RmrkProperty,551	) -> Result<PropertyValue, DispatchError> {552		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))553			.get(&Self::rmrk_property_key(key)?)554			.ok_or(<Error<T>>::NoAvailableNftId)?555			.clone();556557		Ok(nft_property)558	}559560	pub fn get_nft_type(561		_collection_id: CollectionId,562		_token_id: TokenId,563	) -> Result<NftType, DispatchError> {564		todo!("should get it from properties?")565	}566567	pub fn ensure_nft_type(568		collection_id: CollectionId,569		token_id: TokenId,570		nft_type: NftType,571	) -> DispatchResult {572		let actual_type = Self::get_nft_type(collection_id, token_id)?;573		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);574575		Ok(())576	}577578	pub fn ensure_nft_owner(579		collection_id: CollectionId,580		token_id: TokenId,581		possible_owner: &T::CrossAccountId,582	) -> DispatchResult {583		let token_data =584			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;585586		ensure!(587			token_data.owner == *possible_owner,588			<Error<T>>::NoPermission589		);590591		Ok(())592	}593594	pub fn filter_user_properties<Key, Value, R, Mapper>(595		collection_id: CollectionId,596		token_id: Option<TokenId>,597		filter_keys: Option<Vec<RmrkPropertyKey>>,598		mapper: Mapper,599	) -> Result<Vec<R>, DispatchError>600	where601		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,602		Value: Decode + Default,603		Mapper: Fn(Key, Value) -> R,604	{605		filter_keys606			.map(|keys| {607				let properties = keys608					.into_iter()609					.filter_map(|key| {610						let key: Key = key.try_into().ok()?;611612						let value = match token_id {613							Some(token_id) => Self::get_nft_property(614								collection_id,615								token_id,616								UserProperty(key.as_ref()),617							),618							None => Self::get_collection_property(619								collection_id,620								UserProperty(key.as_ref()),621							),622						}623						.ok()?624						.decode_or_default();625626						Some(mapper(key, value))627					})628					.collect();629630				Ok(properties)631			})632			.unwrap_or_else(|| {633				let properties =634					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();635636				Ok(properties)637			})638	}639640	pub fn iterate_user_properties<Key, Value, R, Mapper>(641		collection_id: CollectionId,642		token_id: Option<TokenId>,643		mapper: Mapper,644	) -> Result<impl Iterator<Item = R>, DispatchError>645	where646		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,647		Value: Decode + Default,648		Mapper: Fn(Key, Value) -> R,649	{650		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;651652		let properties = match token_id {653			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),654			None => <PalletCommon<T>>::collection_properties(collection_id),655		};656657		let properties = properties.into_iter().filter_map(move |(key, value)| {658			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;659660			let key: Key = key.to_vec().try_into().ok()?;661			let value: Value = value.decode_or_default();662663			Some(mapper(key, value))664		});665666		Ok(properties)667	}668669	pub fn get_typed_nft_collection(670		collection_id: CollectionId,671		collection_type: misc::CollectionType,672	) -> Result<NonfungibleHandle<T>, DispatchError> {673		Self::ensure_collection_type(collection_id, collection_type)?;674675		Self::get_nft_collection(collection_id)676	}677678	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {679		map_common_err_to_proxy! {680			match err {681				NoPermission => NoPermission,682				CollectionTokenLimitExceeded => CollectionFullOrLocked,683				PublicMintingNotAllowed => NoPermission,684				TokenNotFound => NoAvailableNftId685			}686		}687	}688}
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::*;24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_evm::account::CrossAccountId;29use core::convert::AsRef;3031pub use pallet::*;3233pub mod misc;34pub mod property;3536use misc::*;37pub use property::*;3839use RmrkProperty::*;4041#[frame_support::pallet]42pub mod pallet {43	use super::*;44	use pallet_evm::account;4546	#[pallet::config]47	pub trait Config:48		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config49	{50		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;51	}5253	#[pallet::storage]54	#[pallet::getter(fn collection_index)]55	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5657	#[pallet::storage]58	#[pallet::getter(fn collection_index_map)]59	pub type CollectionIndexMap<T: Config> = 60		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6162	#[pallet::pallet]63	#[pallet::generate_store(pub(super) trait Store)]64	pub struct Pallet<T>(_);6566	#[pallet::event]67	#[pallet::generate_deposit(pub(super) fn deposit_event)]68	pub enum Event<T: Config> {69		CollectionCreated {70			issuer: T::AccountId,71			collection_id: RmrkCollectionId,72		},73		CollectionDestroyed {74			issuer: T::AccountId,75			collection_id: RmrkCollectionId,76		},77		IssuerChanged {78			old_issuer: T::AccountId,79			new_issuer: T::AccountId,80			collection_id: RmrkCollectionId,81		},82		CollectionLocked {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		NftMinted {87			owner: T::AccountId,88			collection_id: RmrkCollectionId,89			nft_id: RmrkNftId,90		},91		NFTBurned {92			owner: T::AccountId,93			nft_id: RmrkNftId,94		},95		PropertySet {96			collection_id: RmrkCollectionId,97			maybe_nft_id: Option<RmrkNftId>,98			key: RmrkKeyString,99			value: RmrkValueString,100		},101	}102103	#[pallet::error]104	pub enum Error<T> {105		/* Unique-specific events */106		CorruptedCollectionType,107		NftTypeEncodeError,108		RmrkPropertyKeyIsTooLong,109		RmrkPropertyValueIsTooLong,110111		/* RMRK compatible events */112		CollectionNotEmpty,113		NoAvailableCollectionId,114		NoAvailableNftId,115		CollectionUnknown,116		NoPermission,117		CollectionFullOrLocked,118	}119120	#[pallet::call]121	impl<T: Config> Pallet<T> {122		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]123		#[transactional]124		pub fn create_collection(125			origin: OriginFor<T>,126			metadata: RmrkString,127			max: Option<u32>,128			symbol: RmrkCollectionSymbol,129		) -> DispatchResult {130			let sender = ensure_signed(origin)?;131132			let limits = CollectionLimits {133				owner_can_transfer: Some(false),134				token_limit: max,135				..Default::default()136			};137138			let data = CreateCollectionData {139				limits: Some(limits),140				token_prefix: symbol141					.into_inner()142					.try_into()143					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,144				..Default::default()145			};146147			let collection_id_res =148				<PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);149150			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {151				return Err(<Error<T>>::NoAvailableCollectionId.into());152			}153154			let unique_collection_id = collection_id_res?;155			let rmrk_collection_id = <CollectionIndex<T>>::get();156157			<CollectionIndex<T>>::mutate(|n| *n += 1);158			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);159160			<PalletCommon<T>>::set_scoped_collection_properties(161				unique_collection_id,162				PropertyScope::Rmrk,163				[164					Self::rmrk_property(Metadata, &metadata)?,165					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,166				]167				.into_iter(),168			)?;169170			Self::deposit_event(Event::CollectionCreated {171				issuer: sender,172				collection_id: rmrk_collection_id,173			});174175			Ok(())176		}177178		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]179		#[transactional]180		pub fn destroy_collection(181			origin: OriginFor<T>,182			collection_id: RmrkCollectionId,183		) -> DispatchResult {184			let sender = ensure_signed(origin)?;185			let cross_sender = T::CrossAccountId::from_sub(sender.clone());186187			let collection = Self::get_typed_nft_collection(188				Self::unique_collection_id(collection_id)?,189				misc::CollectionType::Regular,190			)?;191192			ensure!(193				collection.total_supply() == 0,194				<Error<T>>::CollectionNotEmpty195			);196197			<PalletNft<T>>::destroy_collection(collection, &cross_sender)198				.map_err(Self::map_common_err_to_proxy)?;199200			Self::deposit_event(Event::CollectionDestroyed {201				issuer: sender,202				collection_id,203			});204205			Ok(())206		}207208		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]209		#[transactional]210		pub fn change_collection_issuer(211			origin: OriginFor<T>,212			collection_id: RmrkCollectionId,213			new_issuer: <T::Lookup as StaticLookup>::Source,214		) -> DispatchResult {215			let sender = ensure_signed(origin)?;216217			let new_issuer = T::Lookup::lookup(new_issuer)?;218219			Self::change_collection_owner(220				Self::unique_collection_id(collection_id)?,221				misc::CollectionType::Regular,222				sender.clone(),223				new_issuer.clone(),224			)?;225226			Self::deposit_event(Event::IssuerChanged {227				old_issuer: sender,228				new_issuer,229				collection_id,230			});231232			Ok(())233		}234235		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]236		#[transactional]237		pub fn lock_collection(238			origin: OriginFor<T>,239			collection_id: RmrkCollectionId,240		) -> DispatchResult {241			let sender = ensure_signed(origin)?;242			let cross_sender = T::CrossAccountId::from_sub(sender.clone());243244			let collection = Self::get_typed_nft_collection(245				Self::unique_collection_id(collection_id)?,246				misc::CollectionType::Regular,247			)?;248249			Self::check_collection_owner(&collection, &cross_sender)?;250251			let token_count = collection.total_supply();252253			let mut collection = collection.into_inner();254			collection.limits.token_limit = Some(token_count);255			collection.save()?;256257			Self::deposit_event(Event::CollectionLocked {258				issuer: sender,259				collection_id,260			});261262			Ok(())263		}264265		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]266		#[transactional]267		pub fn mint_nft(268			origin: OriginFor<T>,269			owner: T::AccountId,270			collection_id: RmrkCollectionId,271			recipient: Option<T::AccountId>,272			royalty_amount: Option<Permill>,273			metadata: RmrkString,274		) -> DispatchResult {275			let sender = ensure_signed(origin)?;276			let sender = T::CrossAccountId::from_sub(sender);277			let cross_owner = T::CrossAccountId::from_sub(owner.clone());278279			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {280				recipient: recipient.unwrap_or_else(|| owner.clone()),281				amount,282			});283284			let collection = Self::get_typed_nft_collection(285				Self::unique_collection_id(collection_id)?,286				misc::CollectionType::Regular,287			)?;288289			let nft_id = Self::create_nft(290				&sender,291				&cross_owner,292				&collection,293				NftType::Regular,294				[295					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,296					Self::rmrk_property(Metadata, &metadata)?,297					Self::rmrk_property(Equipped, &false)?,298					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,299					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,300				]301				.into_iter(),302			)303			.map_err(|err| match err {304				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),305				err => Self::map_common_err_to_proxy(err),306			})?;307308			Self::deposit_event(Event::NftMinted {309				owner,310				collection_id,311				nft_id: nft_id.0,312			});313314			Ok(())315		}316317		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]318		#[transactional]319		pub fn burn_nft(320			origin: OriginFor<T>,321			collection_id: RmrkCollectionId,322			nft_id: RmrkNftId,323		) -> DispatchResult {324			let sender = ensure_signed(origin)?;325			let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327			Self::destroy_nft(328				cross_sender,329				Self::unique_collection_id(collection_id)?,330				misc::CollectionType::Regular,331				nft_id.into(),332			)?;333334			Self::deposit_event(Event::NFTBurned {335				owner: sender,336				nft_id,337			});338339			Ok(())340		}341342		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343		#[transactional]344		pub fn set_property(345			origin: OriginFor<T>,346			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,347			maybe_nft_id: Option<RmrkNftId>,348			key: RmrkKeyString,349			value: RmrkValueString,350		) -> DispatchResult {351			let sender = ensure_signed(origin)?;352			let sender = T::CrossAccountId::from_sub(sender);353354			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;355356			match maybe_nft_id {357				Some(nft_id) => {358					let token_id: TokenId = nft_id.into();359360					Self::ensure_nft_owner(collection_id, token_id, &sender)?;361					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;362363					<PalletNft<T>>::set_scoped_token_property(364						collection_id,365						token_id,366						PropertyScope::Rmrk,367						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,368					)?;369				}370				None => {371					let collection = Self::get_typed_nft_collection(372						collection_id,373						misc::CollectionType::Regular,374					)?;375376					Self::check_collection_owner(&collection, &sender)?;377378					<PalletCommon<T>>::set_scoped_collection_property(379						collection_id,380						PropertyScope::Rmrk,381						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,382					)?;383				}384			}385386			Self::deposit_event(Event::PropertySet {387				collection_id: rmrk_collection_id,388				maybe_nft_id,389				key,390				value,391			});392393			Ok(())394		}395	}396}397398impl<T: Config> Pallet<T> {399	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {400		let key = rmrk_key.to_key::<T>()?;401402		let scoped_key = PropertyScope::Rmrk403			.apply(key)404			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;405406		Ok(scoped_key)407	}408409	pub fn rmrk_property<E: Encode>(410		rmrk_key: RmrkProperty,411		value: &E,412	) -> Result<Property, DispatchError> {413		let key = rmrk_key.to_key::<T>()?;414415		let value = value416			.encode()417			.try_into()418			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;419420		let property = Property { key, value };421422		Ok(property)423	}424425	pub fn create_nft(426		sender: &T::CrossAccountId,427		owner: &T::CrossAccountId,428		collection: &NonfungibleHandle<T>,429		nft_type: NftType,430		properties: impl Iterator<Item = Property>,431	) -> Result<TokenId, DispatchError> {432		todo!("store nft type");433		let data = CreateNftExData {434			properties: BoundedVec::default(),435			owner: owner.clone(),436		};437438		let budget = budget::Value::new(2);439440		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;441442		let nft_id = <PalletNft<T>>::current_token_id(collection.id);443444		<PalletNft<T>>::set_scoped_token_properties(445			collection.id,446			nft_id,447			PropertyScope::Rmrk,448			properties,449		)?;450451		Ok(nft_id)452	}453454	fn destroy_nft(455		sender: T::CrossAccountId,456		collection_id: CollectionId,457		collection_type: misc::CollectionType,458		token_id: TokenId,459	) -> DispatchResult {460		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;461462		<PalletNft<T>>::burn(&collection, &sender, token_id)463			.map_err(Self::map_common_err_to_proxy)?;464465		Ok(())466	}467468	fn change_collection_owner(469		collection_id: CollectionId,470		collection_type: misc::CollectionType,471		sender: T::AccountId,472		new_owner: T::AccountId,473	) -> DispatchResult {474		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;475		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;476477		let mut collection = collection.into_inner();478479		collection.owner = new_owner;480		collection.save()481	}482483	fn check_collection_owner(484		collection: &NonfungibleHandle<T>,485		account: &T::CrossAccountId,486	) -> DispatchResult {487		collection488			.check_is_owner(account)489			.map_err(Self::map_common_err_to_proxy)490	}491492	pub fn last_collection_idx() -> RmrkCollectionId {493		<CollectionIndex<T>>::get()494	}495496	pub fn unique_collection_id(rmrk_collection_id: RmrkCollectionId) -> Result<CollectionId, DispatchError> {497		<CollectionIndexMap<T>>::try_get(rmrk_collection_id).map_err(|_| <Error<T>>::CollectionUnknown.into())498	}499500	pub fn get_nft_collection(501		collection_id: CollectionId,502	) -> Result<NonfungibleHandle<T>, DispatchError> {503		let collection = <CollectionHandle<T>>::try_get(collection_id)504			.map_err(|_| <Error<T>>::CollectionUnknown)?;505506		match collection.mode {507			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),508			_ => Err(<Error<T>>::CollectionUnknown.into()),509		}510	}511512	pub fn collection_exists(collection_id: CollectionId) -> bool {513		<CollectionHandle<T>>::try_get(collection_id).is_ok()514	}515516	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {517		<TokenData<T>>::contains_key((collection_id, nft_id))518	}519520	pub fn get_collection_property(521		collection_id: CollectionId,522		key: RmrkProperty,523	) -> Result<PropertyValue, DispatchError> {524		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)525			.get(&Self::rmrk_property_key(key)?)526			.ok_or(<Error<T>>::CollectionUnknown)?527			.clone();528529		Ok(collection_property)530	}531532	pub fn get_collection_type(533		collection_id: CollectionId,534	) -> Result<misc::CollectionType, DispatchError> {535		let value = Self::get_collection_property(collection_id, CollectionType)?;536537		let mut value = value.as_slice();538539		misc::CollectionType::decode(&mut value)540			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())541	}542543	pub fn ensure_collection_type(544		collection_id: CollectionId,545		collection_type: misc::CollectionType,546	) -> DispatchResult {547		let actual_type = Self::get_collection_type(collection_id)?;548		ensure!(549			actual_type == collection_type,550			<CommonError<T>>::NoPermission551		);552553		Ok(())554	}555556	pub fn get_nft_property(557		collection_id: CollectionId,558		nft_id: TokenId,559		key: RmrkProperty,560	) -> Result<PropertyValue, DispatchError> {561		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))562			.get(&Self::rmrk_property_key(key)?)563			.ok_or(<Error<T>>::NoAvailableNftId)?564			.clone();565566		Ok(nft_property)567	}568569	pub fn get_nft_type(570		_collection_id: CollectionId,571		_token_id: TokenId,572	) -> Result<NftType, DispatchError> {573		todo!("should get it from properties?")574	}575576	pub fn ensure_nft_type(577		collection_id: CollectionId,578		token_id: TokenId,579		nft_type: NftType,580	) -> DispatchResult {581		let actual_type = Self::get_nft_type(collection_id, token_id)?;582		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);583584		Ok(())585	}586587	pub fn ensure_nft_owner(588		collection_id: CollectionId,589		token_id: TokenId,590		possible_owner: &T::CrossAccountId,591	) -> DispatchResult {592		let token_data =593			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;594595		ensure!(596			token_data.owner == *possible_owner,597			<Error<T>>::NoPermission598		);599600		Ok(())601	}602603	pub fn filter_user_properties<Key, Value, R, Mapper>(604		collection_id: CollectionId,605		token_id: Option<TokenId>,606		filter_keys: Option<Vec<RmrkPropertyKey>>,607		mapper: Mapper,608	) -> Result<Vec<R>, DispatchError>609	where610		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,611		Value: Decode + Default,612		Mapper: Fn(Key, Value) -> R,613	{614		filter_keys615			.map(|keys| {616				let properties = keys617					.into_iter()618					.filter_map(|key| {619						let key: Key = key.try_into().ok()?;620621						let value = match token_id {622							Some(token_id) => Self::get_nft_property(623								collection_id,624								token_id,625								UserProperty(key.as_ref()),626							),627							None => Self::get_collection_property(628								collection_id,629								UserProperty(key.as_ref()),630							),631						}632						.ok()?633						.decode_or_default();634635						Some(mapper(key, value))636					})637					.collect();638639				Ok(properties)640			})641			.unwrap_or_else(|| {642				let properties =643					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();644645				Ok(properties)646			})647	}648649	pub fn iterate_user_properties<Key, Value, R, Mapper>(650		collection_id: CollectionId,651		token_id: Option<TokenId>,652		mapper: Mapper,653	) -> Result<impl Iterator<Item = R>, DispatchError>654	where655		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,656		Value: Decode + Default,657		Mapper: Fn(Key, Value) -> R,658	{659		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;660661		let properties = match token_id {662			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),663			None => <PalletCommon<T>>::collection_properties(collection_id),664		};665666		let properties = properties.into_iter().filter_map(move |(key, value)| {667			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;668669			let key: Key = key.to_vec().try_into().ok()?;670			let value: Value = value.decode_or_default();671672			Some(mapper(key, value))673		});674675		Ok(properties)676	}677678	pub fn get_typed_nft_collection(679		collection_id: CollectionId,680		collection_type: misc::CollectionType,681	) -> Result<NonfungibleHandle<T>, DispatchError> {682		Self::ensure_collection_type(collection_id, collection_type)?;683684		Self::get_nft_collection(collection_id)685	}686687	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {688		map_common_err_to_proxy! {689			match err {690				NoPermission => NoPermission,691				CollectionTokenLimitExceeded => CollectionFullOrLocked,692				PublicMintingNotAllowed => NoPermission,693				TokenNotFound => NoAvailableNftId694			}695		}696	}697}
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -146,7 +146,7 @@
                 fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind}};
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
@@ -167,7 +167,7 @@
                     use up_data_structs::mapping::TokenAddressMapping;
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::RmrkDecode};
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     let nft_id = TokenId(nft_by_id);
                     if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
 
@@ -194,7 +194,7 @@
                     use pallet_proxy_rmrk_core::misc::CollectionType;
 
                     let cross_account_id = CrossAccountId::from_sub(account_id);
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
 
                     Ok(
@@ -206,7 +206,7 @@
                 }
 
                 fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     let nft_id = TokenId(nft_id);
                     if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
 
@@ -227,7 +227,7 @@
                 fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
                     use pallet_proxy_rmrk_core::misc::CollectionType;
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
                         return Ok(Vec::new());
                     }
@@ -248,7 +248,7 @@
                 fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
                     use pallet_proxy_rmrk_core::misc::NftType;
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     let token_id = TokenId(nft_id);
 
                     if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
@@ -272,7 +272,7 @@
                     use frame_support::BoundedVec;
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkDecode}};
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     if !RmrkCore::collection_exists(collection_id) { return Ok(Vec::new()); } // todo make sure the collection type doesn't matter
 
                     let nft_id = TokenId(nft_id);
@@ -307,7 +307,7 @@
                 fn nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceId>, DispatchError> {
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkDecode}};
 
-                    let collection_id = CollectionId(collection_id);
+                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     if !RmrkCore::collection_exists(collection_id) { return Ok(Vec::new()); } // todo ensure the collection type doesn't matter
 
                     let nft_id = TokenId(nft_id);
@@ -336,7 +336,7 @@
                         RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind},
                     };
 
-                    let collection_id = CollectionId(base_id);
+                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
@@ -352,7 +352,7 @@
                 fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkDecode}};
 
-                    let collection_id = CollectionId(base_id);
+                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); }
 
                     let parts = dispatch_unique_runtime!(collection_id.collection_tokens())?
@@ -383,7 +383,7 @@
                 fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode}};
 
-                    let collection_id = CollectionId(base_id);
+                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
                         return Ok(Vec::new());
                     }
@@ -411,7 +411,7 @@
                         misc::{CollectionType, NftType, RmrkDecode}
                     };
 
-                    let collection_id = CollectionId(base_id);
+                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
                         return Ok(None);
                     }