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

difftreelog

refactor(rmrk-proxy) better decoding

Fahrrader2022-06-02parent: #71b4dea.patch.diff
in: master

3 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_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::*;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;45	use pallet_evm::account;4647	#[pallet::config]48	pub trait Config:49		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50	{51		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52	}5354	#[pallet::storage]55	#[pallet::getter(fn collection_index)]56	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758	#[pallet::storage]59	#[pallet::getter(fn collection_index_map)]60	pub type CollectionIndexMap<T: Config> =61		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::event]68	#[pallet::generate_deposit(pub(super) fn deposit_event)]69	pub enum Event<T: Config> {70		CollectionCreated {71			issuer: T::AccountId,72			collection_id: RmrkCollectionId,73		},74		CollectionDestroyed {75			issuer: T::AccountId,76			collection_id: RmrkCollectionId,77		},78		IssuerChanged {79			old_issuer: T::AccountId,80			new_issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionLocked {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		NftMinted {88			owner: T::AccountId,89			collection_id: RmrkCollectionId,90			nft_id: RmrkNftId,91		},92		NFTBurned {93			owner: T::AccountId,94			nft_id: RmrkNftId,95		},96		PropertySet {97			collection_id: RmrkCollectionId,98			maybe_nft_id: Option<RmrkNftId>,99			key: RmrkKeyString,100			value: RmrkValueString,101		},102		ResourceAdded {103			nft_id: RmrkNftId,104			resource_id: RmrkResourceId,105		},106	}107108	#[pallet::error]109	pub enum Error<T> {110		/* Unique-specific events */111		CorruptedCollectionType,112		NftTypeEncodeError,113		RmrkPropertyKeyIsTooLong,114		RmrkPropertyValueIsTooLong, // todo utilize that in RPCs115116		/* RMRK compatible events */117		CollectionNotEmpty,118		NoAvailableCollectionId,119		NoAvailableNftId,120		CollectionUnknown,121		NoPermission,122		CollectionFullOrLocked,123		// todo add resource errors?124	}125126	#[pallet::call]127	impl<T: Config> Pallet<T> {128		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]129		#[transactional]130		pub fn create_collection(131			origin: OriginFor<T>,132			metadata: RmrkString,133			max: Option<u32>,134			symbol: RmrkCollectionSymbol,135		) -> DispatchResult {136			let sender = ensure_signed(origin)?;137138			let limits = CollectionLimits {139				owner_can_transfer: Some(false),140				token_limit: max,141				..Default::default()142			};143144			let data = CreateCollectionData {145				limits: Some(limits),146				token_prefix: symbol147					.into_inner()148					.try_into()149					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,150				..Default::default()151			};152153			<CollectionIndex<T>>::mutate(|n| *n += 1);154155			let unique_collection_id = Self::init_collection(156				T::CrossAccountId::from_sub(sender.clone()),157				data,158				[159					Self::rmrk_property(Metadata, &metadata)?,160					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,161				]162				.into_iter(),163			)?; //collection_id_res?;164			let rmrk_collection_id = <CollectionIndex<T>>::get();165166			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);167168			Self::deposit_event(Event::CollectionCreated {169				issuer: sender,170				collection_id: rmrk_collection_id,171			});172173			Ok(())174		}175176		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]177		#[transactional]178		pub fn destroy_collection(179			origin: OriginFor<T>,180			collection_id: RmrkCollectionId,181		) -> DispatchResult {182			let sender = ensure_signed(origin)?;183			let cross_sender = T::CrossAccountId::from_sub(sender.clone());184185			let collection = Self::get_typed_nft_collection(186				Self::unique_collection_id(collection_id)?,187				misc::CollectionType::Regular,188			)?;189190			ensure!(191				collection.total_supply() == 0,192				<Error<T>>::CollectionNotEmpty193			);194195			<PalletNft<T>>::destroy_collection(collection, &cross_sender)196				.map_err(Self::map_common_err_to_proxy)?;197198			Self::deposit_event(Event::CollectionDestroyed {199				issuer: sender,200				collection_id,201			});202203			Ok(())204		}205206		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]207		#[transactional]208		pub fn change_collection_issuer(209			origin: OriginFor<T>,210			collection_id: RmrkCollectionId,211			new_issuer: <T::Lookup as StaticLookup>::Source,212		) -> DispatchResult {213			let sender = ensure_signed(origin)?;214215			let new_issuer = T::Lookup::lookup(new_issuer)?;216217			Self::change_collection_owner(218				Self::unique_collection_id(collection_id)?,219				misc::CollectionType::Regular,220				sender.clone(),221				new_issuer.clone(),222			)?;223224			Self::deposit_event(Event::IssuerChanged {225				old_issuer: sender,226				new_issuer,227				collection_id,228			});229230			Ok(())231		}232233		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]234		#[transactional]235		pub fn lock_collection(236			origin: OriginFor<T>,237			collection_id: RmrkCollectionId,238		) -> DispatchResult {239			let sender = ensure_signed(origin)?;240			let cross_sender = T::CrossAccountId::from_sub(sender.clone());241242			let collection = Self::get_typed_nft_collection(243				Self::unique_collection_id(collection_id)?,244				misc::CollectionType::Regular,245			)?;246247			Self::check_collection_owner(&collection, &cross_sender)?;248249			let token_count = collection.total_supply();250251			let mut collection = collection.into_inner();252			collection.limits.token_limit = Some(token_count);253			collection.save()?;254255			Self::deposit_event(Event::CollectionLocked {256				issuer: sender,257				collection_id,258			});259260			Ok(())261		}262263		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]264		#[transactional]265		pub fn mint_nft(266			origin: OriginFor<T>,267			owner: T::AccountId,268			collection_id: RmrkCollectionId,269			recipient: Option<T::AccountId>,270			royalty_amount: Option<Permill>,271			metadata: RmrkString,272		) -> DispatchResult {273			let sender = ensure_signed(origin)?;274			let sender = T::CrossAccountId::from_sub(sender);275			let cross_owner = T::CrossAccountId::from_sub(owner.clone());276277			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {278				recipient: recipient.unwrap_or_else(|| owner.clone()),279				amount,280			});281282			let collection = Self::get_typed_nft_collection(283				Self::unique_collection_id(collection_id)?,284				misc::CollectionType::Regular,285			)?;286287			let nft_id = Self::create_nft(288				&sender,289				&cross_owner,290				&collection,291				[292					Self::rmrk_property(TokenType, &NftType::Regular)?,293					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,294					Self::rmrk_property(Metadata, &metadata)?,295					Self::rmrk_property(Equipped, &false)?,296					Self::rmrk_property(297						ResourceCollection,298						&Self::init_collection(299							sender.clone(),300							CreateCollectionData {301								..Default::default()302							},303							[Self::rmrk_property(304								CollectionType,305								&misc::CollectionType::Resource,306							)?]307							.into_iter(),308						)?,309					)?, // todo possibly add limits to the collection if rmrk warrants them310					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?, // todo create resource priorities?311				]312				.into_iter(),313			)314			.map_err(|err| match err {315				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),316				err => Self::map_common_err_to_proxy(err),317			})?;318319			Self::deposit_event(Event::NftMinted {320				owner,321				collection_id,322				nft_id: nft_id.0,323			});324325			Ok(())326		}327328		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]329		#[transactional]330		pub fn burn_nft(331			origin: OriginFor<T>,332			collection_id: RmrkCollectionId,333			nft_id: RmrkNftId,334		) -> DispatchResult {335			let sender = ensure_signed(origin)?;336			let cross_sender = T::CrossAccountId::from_sub(sender.clone());337338			Self::destroy_nft(339				cross_sender,340				Self::unique_collection_id(collection_id)?,341				misc::CollectionType::Regular,342				nft_id.into(),343			)?;344345			Self::deposit_event(Event::NFTBurned {346				owner: sender,347				nft_id,348			});349350			Ok(())351		}352353		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]354		#[transactional]355		pub fn set_property(356			origin: OriginFor<T>,357			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,358			maybe_nft_id: Option<RmrkNftId>,359			key: RmrkKeyString,360			value: RmrkValueString,361		) -> DispatchResult {362			let sender = ensure_signed(origin)?;363			let sender = T::CrossAccountId::from_sub(sender);364365			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;366367			match maybe_nft_id {368				Some(nft_id) => {369					let token_id: TokenId = nft_id.into();370371					Self::ensure_nft_owner(collection_id, token_id, &sender)?;372					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;373374					<PalletNft<T>>::set_scoped_token_property(375						collection_id,376						token_id,377						PropertyScope::Rmrk,378						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,379					)?;380				}381				None => {382					let collection = Self::get_typed_nft_collection(383						collection_id,384						misc::CollectionType::Regular,385					)?;386387					Self::check_collection_owner(&collection, &sender)?;388389					<PalletCommon<T>>::set_scoped_collection_property(390						collection_id,391						PropertyScope::Rmrk,392						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,393					)?;394				}395			}396397			Self::deposit_event(Event::PropertySet {398				collection_id: rmrk_collection_id,399				maybe_nft_id,400				key,401				value,402			});403404			Ok(())405		}406407		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]408		#[transactional]409		pub fn add_basic_resource(410			origin: OriginFor<T>,411			collection_id: RmrkCollectionId,412			nft_id: RmrkNftId,413			resource: RmrkBasicResource,414		) -> DispatchResult {415			let sender = ensure_signed(origin.clone())?;416417			let resource_id = Self::resource_add(418				sender,419				Self::unique_collection_id(collection_id)?,420				nft_id.into(),421				[422					Self::rmrk_property(TokenType, &NftType::Resource)?,423					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,424					Self::rmrk_property(Src, &resource.src)?,425					Self::rmrk_property(Metadata, &resource.metadata)?,426					Self::rmrk_property(License, &resource.license)?,427					Self::rmrk_property(Thumb, &resource.thumb)?,428				]429				.into_iter(),430			)?;431432			Self::deposit_event(Event::ResourceAdded {433				nft_id,434				resource_id,435			});436			Ok(())437		}438439		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]440		#[transactional]441		pub fn add_composable_resource(442			origin: OriginFor<T>,443			collection_id: RmrkCollectionId,444			nft_id: RmrkNftId,445			_resource_id: RmrkBoundedResource,446			resource: RmrkComposableResource,447		) -> DispatchResult {448			let sender = ensure_signed(origin.clone())?;449450			let resource_id = Self::resource_add(451				sender,452				Self::unique_collection_id(collection_id)?,453				nft_id.into(),454				[455					Self::rmrk_property(TokenType, &NftType::Resource)?,456					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,457					Self::rmrk_property(Parts, &resource.parts)?,458					Self::rmrk_property(Base, &resource.base)?,459					Self::rmrk_property(Src, &resource.src)?,460					Self::rmrk_property(Metadata, &resource.metadata)?,461					Self::rmrk_property(License, &resource.license)?,462					Self::rmrk_property(Thumb, &resource.thumb)?,463				]464				.into_iter(),465			)?;466467			Self::deposit_event(Event::ResourceAdded {468				nft_id,469				resource_id,470			});471			Ok(())472		}473474		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]475		#[transactional]476		pub fn add_slot_resource(477			origin: OriginFor<T>,478			collection_id: RmrkCollectionId,479			nft_id: RmrkNftId,480			resource: RmrkSlotResource,481		) -> DispatchResult {482			let sender = ensure_signed(origin.clone())?;483484			let resource_id = Self::resource_add(485				sender,486				Self::unique_collection_id(collection_id)?,487				nft_id.into(),488				[489					Self::rmrk_property(TokenType, &NftType::Resource)?,490					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,491					Self::rmrk_property(Base, &resource.base)?,492					Self::rmrk_property(Src, &resource.src)?,493					Self::rmrk_property(Metadata, &resource.metadata)?,494					Self::rmrk_property(Slot, &resource.slot)?,495					Self::rmrk_property(License, &resource.license)?,496					Self::rmrk_property(Thumb, &resource.thumb)?,497				]498				.into_iter(),499			)?;500501			Self::deposit_event(Event::ResourceAdded {502				nft_id,503				resource_id,504			});505			Ok(())506		}507	}508}509510impl<T: Config> Pallet<T> {511	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {512		let key = rmrk_key.to_key::<T>()?;513514		let scoped_key = PropertyScope::Rmrk515			.apply(key)516			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;517518		Ok(scoped_key)519	}520521	pub fn rmrk_property<E: Encode>(522		rmrk_key: RmrkProperty,523		value: &E,524	) -> Result<Property, DispatchError> {525		let key = rmrk_key.to_key::<T>()?;526527		let value = value528			.encode()529			.try_into()530			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;531532		let property = Property { key, value };533534		Ok(property)535	}536537	fn init_collection(538		sender: T::CrossAccountId,539		data: CreateCollectionData<T::AccountId>,540		properties: impl Iterator<Item = Property>,541	) -> Result<CollectionId, DispatchError> {542		let collection_id = <PalletNft<T>>::init_collection(sender, data);543544		if let Err(DispatchError::Arithmetic(_)) = &collection_id {545			return Err(<Error<T>>::NoAvailableCollectionId.into());546		}547548		<PalletCommon<T>>::set_scoped_collection_properties(549			collection_id?,550			PropertyScope::Rmrk,551			properties,552		)?;553554		collection_id555	}556557	pub fn create_nft(558		sender: &T::CrossAccountId,559		owner: &T::CrossAccountId,560		collection: &NonfungibleHandle<T>,561		properties: impl Iterator<Item = Property>,562	) -> Result<TokenId, DispatchError> {563		let data = CreateNftExData {564			properties: BoundedVec::default(),565			owner: owner.clone(),566		};567568		let budget = budget::Value::new(2);569570		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;571572		let nft_id = <PalletNft<T>>::current_token_id(collection.id);573574		<PalletNft<T>>::set_scoped_token_properties(575			collection.id,576			nft_id,577			PropertyScope::Rmrk,578			properties,579		)?;580581		Ok(nft_id)582	}583584	fn destroy_nft(585		sender: T::CrossAccountId,586		collection_id: CollectionId,587		collection_type: misc::CollectionType,588		token_id: TokenId,589	) -> DispatchResult {590		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;591592		<PalletNft<T>>::burn(&collection, &sender, token_id)593			.map_err(Self::map_common_err_to_proxy)?;594595		Ok(())596	}597598	fn resource_add(599		sender: T::AccountId,600		collection_id: CollectionId,601		token_id: TokenId,602		resource_properties: impl Iterator<Item = Property>,603	) -> Result<RmrkResourceId, DispatchError> {604		let collection =605			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;606		ensure!(collection.owner == sender, Error::<T>::NoPermission);607608		// Check NFT lock status // todo depends on market, maybe later609		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);610611		let sender = T::CrossAccountId::from_sub(sender);612		let budget = budget::Value::new(10);613		let pending = <PalletStructure<T>>::check_indirectly_owned(614			sender.clone(),615			collection_id,616			token_id,617			None,618			&budget,619		)?;620621		let resource_collection_id: CollectionId =622			Self::get_nft_property(collection_id, token_id, ResourceCollection)?623				.decode_or_default();624		let resource_collection =625			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;626627		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them628629		let resource_id = Self::create_nft(630			&sender, // todo owner of the nft?631			&sender,632			&resource_collection,633			resource_properties.chain(634				[635					Self::rmrk_property(PendingResourceAccept, &pending)?,636					Self::rmrk_property(PendingResourceRemoval, &false)?,637				]638				.into_iter(),639			),640		)641		.map_err(|err| match err {642			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),643			err => Self::map_common_err_to_proxy(err),644		})?;645646		Ok(resource_id.0)647	}648649	fn change_collection_owner(650		collection_id: CollectionId,651		collection_type: misc::CollectionType,652		sender: T::AccountId,653		new_owner: T::AccountId,654	) -> DispatchResult {655		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;656		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;657658		let mut collection = collection.into_inner();659660		collection.owner = new_owner;661		collection.save()662	}663664	fn check_collection_owner(665		collection: &NonfungibleHandle<T>,666		account: &T::CrossAccountId,667	) -> DispatchResult {668		collection669			.check_is_owner(account)670			.map_err(Self::map_common_err_to_proxy)671	}672673	pub fn last_collection_idx() -> RmrkCollectionId {674		<CollectionIndex<T>>::get()675	}676677	pub fn unique_collection_id(678		rmrk_collection_id: RmrkCollectionId,679	) -> Result<CollectionId, DispatchError> {680		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)681			.map_err(|_| <Error<T>>::CollectionUnknown.into())682	}683684	pub fn get_nft_collection(685		collection_id: CollectionId,686	) -> Result<NonfungibleHandle<T>, DispatchError> {687		let collection = <CollectionHandle<T>>::try_get(collection_id)688			.map_err(|_| <Error<T>>::CollectionUnknown)?;689690		match collection.mode {691			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),692			_ => Err(<Error<T>>::CollectionUnknown.into()),693		}694	}695696	pub fn collection_exists(collection_id: CollectionId) -> bool {697		<CollectionHandle<T>>::try_get(collection_id).is_ok()698	}699700	pub fn get_collection_property(701		collection_id: CollectionId,702		key: RmrkProperty,703	) -> Result<PropertyValue, DispatchError> {704		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)705			.get(&Self::rmrk_property_key(key)?)706			.ok_or(<Error<T>>::CollectionUnknown)?707			.clone();708709		Ok(collection_property)710	}711712	pub fn get_collection_type(713		collection_id: CollectionId,714	) -> Result<misc::CollectionType, DispatchError> {715		let value = Self::get_collection_property(collection_id, CollectionType)?;716717		let mut value = value.as_slice();718719		misc::CollectionType::decode(&mut value)720			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())721	}722723	pub fn ensure_collection_type(724		collection_id: CollectionId,725		collection_type: misc::CollectionType,726	) -> DispatchResult {727		let actual_type = Self::get_collection_type(collection_id)?;728		ensure!(729			actual_type == collection_type,730			<CommonError<T>>::NoPermission731		);732733		Ok(())734	}735736	pub fn get_typed_nft_collection(737		collection_id: CollectionId,738		collection_type: misc::CollectionType,739	) -> Result<NonfungibleHandle<T>, DispatchError> {740		Self::ensure_collection_type(collection_id, collection_type)?;741742		Self::get_nft_collection(collection_id)743	}744745	pub fn get_nft_property(746		collection_id: CollectionId,747		nft_id: TokenId,748		key: RmrkProperty,749	) -> Result<PropertyValue, DispatchError> {750		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))751			.get(&Self::rmrk_property_key(key)?)752			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error753			.clone();754755		Ok(nft_property)756	}757758	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {759		<TokenData<T>>::contains_key((collection_id, nft_id))760	}761762	pub fn get_nft_type(763		collection_id: CollectionId,764		token_id: TokenId,765	) -> Result<NftType, DispatchError> {766		Ok(Self::get_nft_property(collection_id, token_id, TokenType)?.decode_or_default())767		// todo throw error768		// NftTypeEncodeError?769	}770771	pub fn ensure_nft_type(772		collection_id: CollectionId,773		token_id: TokenId,774		nft_type: NftType,775	) -> DispatchResult {776		let actual_type = Self::get_nft_type(collection_id, token_id)?;777		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);778779		Ok(())780	}781782	pub fn ensure_nft_owner(783		collection_id: CollectionId,784		token_id: TokenId,785		possible_owner: &T::CrossAccountId,786	) -> DispatchResult {787		let token_data =788			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;789790		ensure!(791			token_data.owner == *possible_owner,792			<Error<T>>::NoPermission793		);794795		Ok(())796	}797798	pub fn filter_user_properties<Key, Value, R, Mapper>(799		collection_id: CollectionId,800		token_id: Option<TokenId>,801		filter_keys: Option<Vec<RmrkPropertyKey>>,802		mapper: Mapper,803	) -> Result<Vec<R>, DispatchError>804	where805		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,806		Value: Decode + Default,807		Mapper: Fn(Key, Value) -> R,808	{809		filter_keys810			.map(|keys| {811				let properties = keys812					.into_iter()813					.filter_map(|key| {814						let key: Key = key.try_into().ok()?;815816						let value = match token_id {817							Some(token_id) => Self::get_nft_property(818								collection_id,819								token_id,820								UserProperty(key.as_ref()),821							),822							None => Self::get_collection_property(823								collection_id,824								UserProperty(key.as_ref()),825							),826						}827						.ok()?828						.decode_or_default();829830						Some(mapper(key, value))831					})832					.collect();833834				Ok(properties)835			})836			.unwrap_or_else(|| {837				let properties =838					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();839840				Ok(properties)841			})842	}843844	pub fn iterate_user_properties<Key, Value, R, Mapper>(845		collection_id: CollectionId,846		token_id: Option<TokenId>,847		mapper: Mapper,848	) -> Result<impl Iterator<Item = R>, DispatchError>849	where850		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,851		Value: Decode + Default,852		Mapper: Fn(Key, Value) -> R,853	{854		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;855856		let properties = match token_id {857			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),858			None => <PalletCommon<T>>::collection_properties(collection_id),859		};860861		let properties = properties.into_iter().filter_map(move |(key, value)| {862			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;863864			let key: Key = key.to_vec().try_into().ok()?;865			let value: Value = value.decode_or_default();866867			Some(mapper(key, value))868		});869870		Ok(properties)871	}872873	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {874		map_common_err_to_proxy! {875			match err {876				NoPermission => NoPermission,877				CollectionTokenLimitExceeded => CollectionFullOrLocked,878				PublicMintingNotAllowed => NoPermission,879				TokenNotFound => NoAvailableNftId880			}881		}882	}883}
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_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::*;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;45	use pallet_evm::account;4647	#[pallet::config]48	pub trait Config:49		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50	{51		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52	}5354	#[pallet::storage]55	#[pallet::getter(fn collection_index)]56	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758	#[pallet::storage]59	#[pallet::getter(fn collection_index_map)]60	pub type CollectionIndexMap<T: Config> =61		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::event]68	#[pallet::generate_deposit(pub(super) fn deposit_event)]69	pub enum Event<T: Config> {70		CollectionCreated {71			issuer: T::AccountId,72			collection_id: RmrkCollectionId,73		},74		CollectionDestroyed {75			issuer: T::AccountId,76			collection_id: RmrkCollectionId,77		},78		IssuerChanged {79			old_issuer: T::AccountId,80			new_issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionLocked {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		NftMinted {88			owner: T::AccountId,89			collection_id: RmrkCollectionId,90			nft_id: RmrkNftId,91		},92		NFTBurned {93			owner: T::AccountId,94			nft_id: RmrkNftId,95		},96		PropertySet {97			collection_id: RmrkCollectionId,98			maybe_nft_id: Option<RmrkNftId>,99			key: RmrkKeyString,100			value: RmrkValueString,101		},102		ResourceAdded {103			nft_id: RmrkNftId,104			resource_id: RmrkResourceId,105		},106	}107108	#[pallet::error]109	pub enum Error<T> {110		/* Unique-specific events */111		CorruptedCollectionType,112		NftTypeEncodeError,113		RmrkPropertyKeyIsTooLong,114		RmrkPropertyValueIsTooLong,115116		/* RMRK compatible events */117		CollectionNotEmpty,118		NoAvailableCollectionId,119		NoAvailableNftId,120		CollectionUnknown,121		NoPermission,122		CollectionFullOrLocked,123		// todo add resource errors?124	}125126	#[pallet::call]127	impl<T: Config> Pallet<T> {128		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]129		#[transactional]130		pub fn create_collection(131			origin: OriginFor<T>,132			metadata: RmrkString,133			max: Option<u32>,134			symbol: RmrkCollectionSymbol,135		) -> DispatchResult {136			let sender = ensure_signed(origin)?;137138			let limits = CollectionLimits {139				owner_can_transfer: Some(false),140				token_limit: max,141				..Default::default()142			};143144			let data = CreateCollectionData {145				limits: Some(limits),146				token_prefix: symbol147					.into_inner()148					.try_into()149					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,150				..Default::default()151			};152153			<CollectionIndex<T>>::mutate(|n| *n += 1);154155			let unique_collection_id = Self::init_collection(156				T::CrossAccountId::from_sub(sender.clone()),157				data,158				[159					Self::rmrk_property(Metadata, &metadata)?,160					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,161				]162				.into_iter(),163			)?; //collection_id_res?;164			let rmrk_collection_id = <CollectionIndex<T>>::get();165166			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);167168			Self::deposit_event(Event::CollectionCreated {169				issuer: sender,170				collection_id: rmrk_collection_id,171			});172173			Ok(())174		}175176		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]177		#[transactional]178		pub fn destroy_collection(179			origin: OriginFor<T>,180			collection_id: RmrkCollectionId,181		) -> DispatchResult {182			let sender = ensure_signed(origin)?;183			let cross_sender = T::CrossAccountId::from_sub(sender.clone());184185			let collection = Self::get_typed_nft_collection(186				Self::unique_collection_id(collection_id)?,187				misc::CollectionType::Regular,188			)?;189190			ensure!(191				collection.total_supply() == 0,192				<Error<T>>::CollectionNotEmpty193			);194195			<PalletNft<T>>::destroy_collection(collection, &cross_sender)196				.map_err(Self::map_common_err_to_proxy)?;197198			Self::deposit_event(Event::CollectionDestroyed {199				issuer: sender,200				collection_id,201			});202203			Ok(())204		}205206		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]207		#[transactional]208		pub fn change_collection_issuer(209			origin: OriginFor<T>,210			collection_id: RmrkCollectionId,211			new_issuer: <T::Lookup as StaticLookup>::Source,212		) -> DispatchResult {213			let sender = ensure_signed(origin)?;214215			let new_issuer = T::Lookup::lookup(new_issuer)?;216217			Self::change_collection_owner(218				Self::unique_collection_id(collection_id)?,219				misc::CollectionType::Regular,220				sender.clone(),221				new_issuer.clone(),222			)?;223224			Self::deposit_event(Event::IssuerChanged {225				old_issuer: sender,226				new_issuer,227				collection_id,228			});229230			Ok(())231		}232233		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]234		#[transactional]235		pub fn lock_collection(236			origin: OriginFor<T>,237			collection_id: RmrkCollectionId,238		) -> DispatchResult {239			let sender = ensure_signed(origin)?;240			let cross_sender = T::CrossAccountId::from_sub(sender.clone());241242			let collection = Self::get_typed_nft_collection(243				Self::unique_collection_id(collection_id)?,244				misc::CollectionType::Regular,245			)?;246247			Self::check_collection_owner(&collection, &cross_sender)?;248249			let token_count = collection.total_supply();250251			let mut collection = collection.into_inner();252			collection.limits.token_limit = Some(token_count);253			collection.save()?;254255			Self::deposit_event(Event::CollectionLocked {256				issuer: sender,257				collection_id,258			});259260			Ok(())261		}262263		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]264		#[transactional]265		pub fn mint_nft(266			origin: OriginFor<T>,267			owner: T::AccountId,268			collection_id: RmrkCollectionId,269			recipient: Option<T::AccountId>,270			royalty_amount: Option<Permill>,271			metadata: RmrkString,272		) -> DispatchResult {273			let sender = ensure_signed(origin)?;274			let sender = T::CrossAccountId::from_sub(sender);275			let cross_owner = T::CrossAccountId::from_sub(owner.clone());276277			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {278				recipient: recipient.unwrap_or_else(|| owner.clone()),279				amount,280			});281282			let collection = Self::get_typed_nft_collection(283				Self::unique_collection_id(collection_id)?,284				misc::CollectionType::Regular,285			)?;286287			let nft_id = Self::create_nft(288				&sender,289				&cross_owner,290				&collection,291				[292					Self::rmrk_property(TokenType, &NftType::Regular)?,293					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,294					Self::rmrk_property(Metadata, &metadata)?,295					Self::rmrk_property(Equipped, &false)?,296					Self::rmrk_property(297						ResourceCollection,298						&Self::init_collection(299							sender.clone(),300							CreateCollectionData {301								..Default::default()302							},303							[Self::rmrk_property(304								CollectionType,305								&misc::CollectionType::Resource,306							)?]307							.into_iter(),308						)?,309					)?, // todo possibly add limits to the collection if rmrk warrants them310					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?, // todo create resource priorities?311				]312				.into_iter(),313			)314			.map_err(|err| match err {315				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),316				err => Self::map_common_err_to_proxy(err),317			})?;318319			Self::deposit_event(Event::NftMinted {320				owner,321				collection_id,322				nft_id: nft_id.0,323			});324325			Ok(())326		}327328		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]329		#[transactional]330		pub fn burn_nft(331			origin: OriginFor<T>,332			collection_id: RmrkCollectionId,333			nft_id: RmrkNftId,334		) -> DispatchResult {335			let sender = ensure_signed(origin)?;336			let cross_sender = T::CrossAccountId::from_sub(sender.clone());337338			Self::destroy_nft(339				cross_sender,340				Self::unique_collection_id(collection_id)?,341				misc::CollectionType::Regular,342				nft_id.into(),343			)?;344345			Self::deposit_event(Event::NFTBurned {346				owner: sender,347				nft_id,348			});349350			Ok(())351		}352353		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]354		#[transactional]355		pub fn set_property(356			origin: OriginFor<T>,357			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,358			maybe_nft_id: Option<RmrkNftId>,359			key: RmrkKeyString,360			value: RmrkValueString,361		) -> DispatchResult {362			let sender = ensure_signed(origin)?;363			let sender = T::CrossAccountId::from_sub(sender);364365			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;366367			match maybe_nft_id {368				Some(nft_id) => {369					let token_id: TokenId = nft_id.into();370371					Self::ensure_nft_owner(collection_id, token_id, &sender)?;372					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;373374					<PalletNft<T>>::set_scoped_token_property(375						collection_id,376						token_id,377						PropertyScope::Rmrk,378						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,379					)?;380				}381				None => {382					let collection = Self::get_typed_nft_collection(383						collection_id,384						misc::CollectionType::Regular,385					)?;386387					Self::check_collection_owner(&collection, &sender)?;388389					<PalletCommon<T>>::set_scoped_collection_property(390						collection_id,391						PropertyScope::Rmrk,392						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,393					)?;394				}395			}396397			Self::deposit_event(Event::PropertySet {398				collection_id: rmrk_collection_id,399				maybe_nft_id,400				key,401				value,402			});403404			Ok(())405		}406407		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]408		#[transactional]409		pub fn add_basic_resource(410			origin: OriginFor<T>,411			collection_id: RmrkCollectionId,412			nft_id: RmrkNftId,413			resource: RmrkBasicResource,414		) -> DispatchResult {415			let sender = ensure_signed(origin.clone())?;416417			let resource_id = Self::resource_add(418				sender,419				Self::unique_collection_id(collection_id)?,420				nft_id.into(),421				[422					Self::rmrk_property(TokenType, &NftType::Resource)?,423					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,424					Self::rmrk_property(Src, &resource.src)?,425					Self::rmrk_property(Metadata, &resource.metadata)?,426					Self::rmrk_property(License, &resource.license)?,427					Self::rmrk_property(Thumb, &resource.thumb)?,428				]429				.into_iter(),430			)?;431432			Self::deposit_event(Event::ResourceAdded {433				nft_id,434				resource_id,435			});436			Ok(())437		}438439		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]440		#[transactional]441		pub fn add_composable_resource(442			origin: OriginFor<T>,443			collection_id: RmrkCollectionId,444			nft_id: RmrkNftId,445			_resource_id: RmrkBoundedResource,446			resource: RmrkComposableResource,447		) -> DispatchResult {448			let sender = ensure_signed(origin.clone())?;449450			let resource_id = Self::resource_add(451				sender,452				Self::unique_collection_id(collection_id)?,453				nft_id.into(),454				[455					Self::rmrk_property(TokenType, &NftType::Resource)?,456					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,457					Self::rmrk_property(Parts, &resource.parts)?,458					Self::rmrk_property(Base, &resource.base)?,459					Self::rmrk_property(Src, &resource.src)?,460					Self::rmrk_property(Metadata, &resource.metadata)?,461					Self::rmrk_property(License, &resource.license)?,462					Self::rmrk_property(Thumb, &resource.thumb)?,463				]464				.into_iter(),465			)?;466467			Self::deposit_event(Event::ResourceAdded {468				nft_id,469				resource_id,470			});471			Ok(())472		}473474		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]475		#[transactional]476		pub fn add_slot_resource(477			origin: OriginFor<T>,478			collection_id: RmrkCollectionId,479			nft_id: RmrkNftId,480			resource: RmrkSlotResource,481		) -> DispatchResult {482			let sender = ensure_signed(origin.clone())?;483484			let resource_id = Self::resource_add(485				sender,486				Self::unique_collection_id(collection_id)?,487				nft_id.into(),488				[489					Self::rmrk_property(TokenType, &NftType::Resource)?,490					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,491					Self::rmrk_property(Base, &resource.base)?,492					Self::rmrk_property(Src, &resource.src)?,493					Self::rmrk_property(Metadata, &resource.metadata)?,494					Self::rmrk_property(Slot, &resource.slot)?,495					Self::rmrk_property(License, &resource.license)?,496					Self::rmrk_property(Thumb, &resource.thumb)?,497				]498				.into_iter(),499			)?;500501			Self::deposit_event(Event::ResourceAdded {502				nft_id,503				resource_id,504			});505			Ok(())506		}507	}508}509510impl<T: Config> Pallet<T> {511	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {512		let key = rmrk_key.to_key::<T>()?;513514		let scoped_key = PropertyScope::Rmrk515			.apply(key)516			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;517518		Ok(scoped_key)519	}520521	pub fn rmrk_property<E: Encode>( // todo think about renaming this522		rmrk_key: RmrkProperty,523		value: &E,524	) -> Result<Property, DispatchError> {525		let key = rmrk_key.to_key::<T>()?;526527		let value = value528			.encode()529			.try_into()530			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;531532		let property = Property { key, value };533534		Ok(property)535	}536	537	pub fn decode_property<D: Decode>(538		vec: PropertyValue,539	) -> Result<D, DispatchError> {540		vec.decode().map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())541	}542543	pub fn rebind<L, S>(544		vec: &BoundedVec<u8, L>,545	) -> Result<BoundedVec<u8, S>, DispatchError> 546	where 547		BoundedVec<u8, S>: TryFrom<Vec<u8>> 548	{549		vec.rebind().map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())550	}551552	fn init_collection(553		sender: T::CrossAccountId,554		data: CreateCollectionData<T::AccountId>,555		properties: impl Iterator<Item = Property>,556	) -> Result<CollectionId, DispatchError> {557		let collection_id = <PalletNft<T>>::init_collection(sender, data);558559		if let Err(DispatchError::Arithmetic(_)) = &collection_id {560			return Err(<Error<T>>::NoAvailableCollectionId.into());561		}562563		<PalletCommon<T>>::set_scoped_collection_properties(564			collection_id?,565			PropertyScope::Rmrk,566			properties,567		)?;568569		collection_id570	}571572	pub fn create_nft(573		sender: &T::CrossAccountId,574		owner: &T::CrossAccountId,575		collection: &NonfungibleHandle<T>,576		properties: impl Iterator<Item = Property>,577	) -> Result<TokenId, DispatchError> {578		let data = CreateNftExData {579			properties: BoundedVec::default(),580			owner: owner.clone(),581		};582583		let budget = budget::Value::new(2);584585		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;586587		let nft_id = <PalletNft<T>>::current_token_id(collection.id);588589		<PalletNft<T>>::set_scoped_token_properties(590			collection.id,591			nft_id,592			PropertyScope::Rmrk,593			properties,594		)?;595596		Ok(nft_id)597	}598599	fn destroy_nft(600		sender: T::CrossAccountId,601		collection_id: CollectionId,602		collection_type: misc::CollectionType,603		token_id: TokenId,604	) -> DispatchResult {605		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;606607		<PalletNft<T>>::burn(&collection, &sender, token_id)608			.map_err(Self::map_common_err_to_proxy)?;609610		Ok(())611	}612613	fn resource_add(614		sender: T::AccountId,615		collection_id: CollectionId,616		token_id: TokenId,617		resource_properties: impl Iterator<Item = Property>,618	) -> Result<RmrkResourceId, DispatchError> {619		let collection =620			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;621		ensure!(collection.owner == sender, Error::<T>::NoPermission);622623		// Check NFT lock status // todo depends on market, maybe later624		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);625626		let sender = T::CrossAccountId::from_sub(sender);627		let budget = budget::Value::new(10);628		let pending = <PalletStructure<T>>::check_indirectly_owned(629			sender.clone(),630			collection_id,631			token_id,632			None,633			&budget,634		)?;635636		let resource_collection_id: CollectionId =637			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;638		let resource_collection =639			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;640641		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them642643		let resource_id = Self::create_nft(644			&sender, // todo owner of the nft?645			&sender,646			&resource_collection,647			resource_properties.chain(648				[649					Self::rmrk_property(PendingResourceAccept, &pending)?,650					Self::rmrk_property(PendingResourceRemoval, &false)?,651				]652				.into_iter(),653			),654		)655		.map_err(|err| match err {656			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),657			err => Self::map_common_err_to_proxy(err),658		})?;659660		Ok(resource_id.0)661	}662663	fn change_collection_owner(664		collection_id: CollectionId,665		collection_type: misc::CollectionType,666		sender: T::AccountId,667		new_owner: T::AccountId,668	) -> DispatchResult {669		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;670		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;671672		let mut collection = collection.into_inner();673674		collection.owner = new_owner;675		collection.save()676	}677678	fn check_collection_owner(679		collection: &NonfungibleHandle<T>,680		account: &T::CrossAccountId,681	) -> DispatchResult {682		collection683			.check_is_owner(account)684			.map_err(Self::map_common_err_to_proxy)685	}686687	pub fn last_collection_idx() -> RmrkCollectionId {688		<CollectionIndex<T>>::get()689	}690691	pub fn unique_collection_id(692		rmrk_collection_id: RmrkCollectionId,693	) -> Result<CollectionId, DispatchError> {694		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)695			.map_err(|_| <Error<T>>::CollectionUnknown.into())696	}697698	pub fn get_nft_collection(699		collection_id: CollectionId,700	) -> Result<NonfungibleHandle<T>, DispatchError> {701		let collection = <CollectionHandle<T>>::try_get(collection_id)702			.map_err(|_| <Error<T>>::CollectionUnknown)?;703704		match collection.mode {705			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),706			_ => Err(<Error<T>>::CollectionUnknown.into()),707		}708	}709710	pub fn collection_exists(collection_id: CollectionId) -> bool {711		<CollectionHandle<T>>::try_get(collection_id).is_ok()712	}713714	pub fn get_collection_property(715		collection_id: CollectionId,716		key: RmrkProperty,717	) -> Result<PropertyValue, DispatchError> {718		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)719			.get(&Self::rmrk_property_key(key)?)720			.ok_or(<Error<T>>::CollectionUnknown)?721			.clone();722723		Ok(collection_property)724	}725726	pub fn get_collection_property_decoded<V: Decode>(727		collection_id: CollectionId,728		key: RmrkProperty,729	) -> Result<V, DispatchError> {730		Self::decode_property(731			Self::get_collection_property(collection_id, key)?732		)733	}734735	pub fn get_collection_type(736		collection_id: CollectionId,737	) -> Result<misc::CollectionType, DispatchError> {738		Self::get_collection_property_decoded(collection_id, CollectionType)739			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())740	}741742	pub fn ensure_collection_type(743		collection_id: CollectionId,744		collection_type: misc::CollectionType,745	) -> DispatchResult {746		let actual_type = Self::get_collection_type(collection_id)?;747		ensure!(748			actual_type == collection_type,749			<CommonError<T>>::NoPermission750		);751752		Ok(())753	}754755	pub fn get_typed_nft_collection(756		collection_id: CollectionId,757		collection_type: misc::CollectionType,758	) -> Result<NonfungibleHandle<T>, DispatchError> {759		Self::ensure_collection_type(collection_id, collection_type)?;760761		Self::get_nft_collection(collection_id)762	}763764	pub fn get_nft_property(765		collection_id: CollectionId,766		nft_id: TokenId,767		key: RmrkProperty,768	) -> Result<PropertyValue, DispatchError> {769		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))770			.get(&Self::rmrk_property_key(key)?)771			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?772			.clone();773774		Ok(nft_property)775	}776777	pub fn get_nft_property_decoded<V: Decode>(778		collection_id: CollectionId,779		nft_id: TokenId,780		key: RmrkProperty,781	) -> Result<V, DispatchError> {782		Self::decode_property(783			Self::get_nft_property(collection_id, nft_id, key)?784		)785	}786787	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {788		<TokenData<T>>::contains_key((collection_id, nft_id))789	}790791	pub fn get_nft_type(792		collection_id: CollectionId,793		token_id: TokenId,794	) -> Result<NftType, DispatchError> {795		Self::get_nft_property_decoded(collection_id, token_id, TokenType)796			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())797	}798799	pub fn ensure_nft_type(800		collection_id: CollectionId,801		token_id: TokenId,802		nft_type: NftType,803	) -> DispatchResult {804		let actual_type = Self::get_nft_type(collection_id, token_id)?;805		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);806807		Ok(())808	}809810	pub fn ensure_nft_owner(811		collection_id: CollectionId,812		token_id: TokenId,813		possible_owner: &T::CrossAccountId,814	) -> DispatchResult {815		let token_data =816			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;817818		ensure!(819			token_data.owner == *possible_owner,820			<Error<T>>::NoPermission821		);822823		Ok(())824	}825826	pub fn filter_user_properties<Key, Value, R, Mapper>(827		collection_id: CollectionId,828		token_id: Option<TokenId>,829		filter_keys: Option<Vec<RmrkPropertyKey>>,830		mapper: Mapper,831	) -> Result<Vec<R>, DispatchError>832	where833		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,834		Value: Decode + Default,835		Mapper: Fn(Key, Value) -> R,836	{837		filter_keys838			.map(|keys| {839				let properties = keys840					.into_iter()841					.filter_map(|key| {842						let key: Key = key.try_into().ok()?;843844						let value = match token_id {845							Some(token_id) => Self::get_nft_property_decoded(846								collection_id,847								token_id,848								UserProperty(key.as_ref()),849							),850							None => Self::get_collection_property_decoded(851								collection_id,852								UserProperty(key.as_ref()),853							),854						}855						.ok()?;856857						Some(mapper(key, value))858					})859					.collect();860861				Ok(properties)862			})863			.unwrap_or_else(|| {864				let properties =865					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();866867				Ok(properties)868			})869	}870871	pub fn iterate_user_properties<Key, Value, R, Mapper>(872		collection_id: CollectionId,873		token_id: Option<TokenId>,874		mapper: Mapper,875	) -> Result<impl Iterator<Item = R>, DispatchError>876	where877		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,878		Value: Decode + Default,879		Mapper: Fn(Key, Value) -> R,880	{881		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;882883		let properties = match token_id {884			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),885			None => <PalletCommon<T>>::collection_properties(collection_id),886		};887888		let properties = properties.into_iter().filter_map(move |(key, value)| {889			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;890891			let key: Key = key.to_vec().try_into().ok()?;892			let value: Value = value.decode().ok()?;893894			Some(mapper(key, value))895		});896897		Ok(properties)898	}899900	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {901		map_common_err_to_proxy! {902			match err {903				NoPermission => NoPermission,904				CollectionTokenLimitExceeded => CollectionFullOrLocked,905				PublicMintingNotAllowed => NoPermission,906				TokenNotFound => NoAvailableNftId907			}908		}909	}910}
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -1,6 +1,5 @@
 use super::*;
-use codec::{Encode, Decode};
-use derivative::Derivative;
+use codec::{Encode, Decode, Error};
 
 #[macro_export]
 macro_rules! map_common_err_to_proxy {
@@ -15,30 +14,30 @@
     };
 }
 
-pub trait RmrkDecode<T: Decode + Default, S> {
-	fn decode_or_default(&self) -> T;
+// Utilize the RmrkCore pallet for access to Runtime errors.
+pub trait RmrkDecode<T: Decode, S> {
+	fn decode(&self) -> Result<T, Error>;
 }
 
-// todo fail if unwrap doesn't work
-impl<T: Decode + Default, S> RmrkDecode<T, S> for BoundedVec<u8, S> {
-	fn decode_or_default(&self) -> T {
+impl<T: Decode, S> RmrkDecode<T, S> for BoundedVec<u8, S> {
+	fn decode(&self) -> Result<T, Error> {
 		let mut value = self.as_slice();
 
-		T::decode(&mut value).unwrap_or_default()
+		T::decode(&mut value)
 	}
 }
 
+// Utilize the RmrkCore pallet for access to Runtime errors.
 pub trait RmrkRebind<T, S> {
-	fn rebind(&self) -> BoundedVec<u8, S>;
+	fn rebind(&self) -> Result<BoundedVec<u8, S>, Error>;
 }
 
-// todo fail if unwrap doesn't work
 impl<T, S> RmrkRebind<T, S> for BoundedVec<u8, T>
 where
 	BoundedVec<u8, S>: TryFrom<Vec<u8>>,
 {
-	fn rebind(&self) -> BoundedVec<u8, S> {
-		BoundedVec::<u8, S>::try_from(self.clone().into_inner()).unwrap_or_default()
+	fn rebind(&self) -> Result<BoundedVec<u8, S>, Error> {
+		BoundedVec::<u8, S>::try_from(self.clone().into_inner()).map_err(|_| "BoundedVec exceeds its limit".into())
 	}
 }
 
@@ -49,11 +48,8 @@
 	Base,
 }
 
-// todo remove default?
-#[derive(Encode, Decode, PartialEq, Eq, Derivative)]
-#[derivative(Default(bound = ""))]
+#[derive(Encode, Decode, PartialEq, Eq)]
 pub enum NftType {
-	#[derivative(Default)]
 	Regular,
 	Resource,
 	FixedPart,
@@ -61,11 +57,8 @@
 	Theme,
 }
 
-// todo remove default?
-#[derive(Encode, Decode, PartialEq, Eq, Derivative)]
-#[derivative(Default(bound = ""))]
+#[derive(Encode, Decode, PartialEq, Eq)]
 pub enum ResourceType {
-	#[derivative(Default)]
 	Basic,
 	Composable,
 	Slot,
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -144,7 +144,7 @@
                 }
 
                 fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-                    use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind}};
+                    use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType}};
 
                     let collection_id = RmrkCore::unique_collection_id(collection_id)?;
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
@@ -157,9 +157,9 @@
 
                     Ok(Some(RmrkCollectionInfo {
                         issuer: collection.owner.clone(),
-                        metadata: RmrkCore::get_collection_property(collection_id, RmrkProperty::Metadata)?.decode_or_default(),
+                        metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
                         max: collection.limits.token_limit,
-                        symbol: collection.token_prefix.rebind(),
+                        symbol: RmrkCore::rebind(&collection.token_prefix)?,
                         nfts_count
                     }))
                 }
@@ -185,9 +185,9 @@
 
                     Ok(Some(RmrkInstanceInfo {
                         owner: owner,
-                        royalty: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?.decode_or_default(),
-                        metadata: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::Metadata)?.decode_or_default(),
-                        equipped: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::Equipped)?.decode_or_default(),
+                        royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
+                        metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
+                        equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
                         pending: allowance.is_some(),
                     }))
                 }
@@ -281,8 +281,7 @@
                     let nft_id = TokenId(nft_id);
                     if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
 
-                    let res_collection_id: CollectionId = RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::ResourceCollection)?
-                        .decode_or_default();
+                    let res_collection_id: CollectionId = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
                     let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
 
                     let resources = resource_collection
@@ -290,32 +289,31 @@
                         .iter()
                         .filter_map(|(res_id)| Some(RmrkResourceInfo {
                             id: res_id.0,
-                            pending: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).unwrap().decode_or_default(),
-                            pending_removal: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).unwrap().decode_or_default(),
-                            resource: match RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::ResourceType).unwrap().decode_or_default() {
+                            pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).unwrap(),
+                            pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).unwrap(),
+                            resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).unwrap() {
                                 ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
-                                    src: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Src).unwrap().decode_or_default(),
-                                    metadata: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap().decode_or_default(),
-                                    license: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::License).unwrap().decode_or_default(),
-                                    thumb: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap().decode_or_default(),
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
                                 }),
                                 ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
-                                    parts: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Parts).unwrap().decode_or_default(),
-                                    base: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Base).unwrap().decode_or_default(),
-                                    src: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Src).unwrap().decode_or_default(),
-                                    metadata: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap().decode_or_default(),
-                                    license: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::License).unwrap().decode_or_default(),
-                                    thumb: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap().decode_or_default(),
+                                    parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).unwrap(),
+                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).unwrap(),
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
                                 }),
                                 ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
-                                    base: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Base).unwrap().decode_or_default(),
-                                    src: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Src).unwrap().decode_or_default(),
-                                    metadata: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap().decode_or_default(),
-                                    slot: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Slot).unwrap().decode_or_default(),
-                                    license: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::License).unwrap().decode_or_default(),
-                                    thumb: RmrkCore::get_nft_property(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap().decode_or_default(),
+                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).unwrap(),
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
+                                    slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).unwrap(),
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
                                 }),
-                                // todo refactor :|
                             },
                         }))
                         .collect();
@@ -332,28 +330,26 @@
                     let nft_id = TokenId(nft_id);
                     if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
 
-                    /*let resource_collection_id: CollectionId = RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::ResourceCollection)
-                        .unwrap()
-                        .decode_or_default();
+                    /*let resource_collection_id: CollectionId = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)
+                        .unwrap();
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Resource).is_err() { return Ok(Vec::new()); }
 
                     let resources = pallet_nonfungible::TokenProperties::<Runtime>::iter_prefix((resource_collection_id,))
                         .filter_map(|(resource_id, properties)| Some((
                             resource_id, // ResourceId property
-                            RmrkCore::get_nft_property(resource_collection_id, resource_id, RmrkProperty::Priority).unwrap().decode_or_default(),
+                            RmrkCore::get_nft_property_decoded(resource_collection_id, resource_id, RmrkProperty::Priority).unwrap(),
                         )))
                         .collect()
                         .sort_by_key(|(_, index)| *index)
                         .into_iter().map(|(resource_id, _)| resource_id)*/
-                    let priorities = RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::ResourcePriorities)?.decode_or_default();
-                    // todo let it simply be default here after removing default from decode
+                    let priorities = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
 
                     Ok(priorities)
                 }
 
                 fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
                     use pallet_proxy_rmrk_core::{
-                        RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind},
+                        RmrkProperty, misc::{CollectionType},
                     };
 
                     let collection_id = RmrkCore::unique_collection_id(base_id)?;
@@ -364,8 +360,8 @@
 
                     Ok(Some(RmrkBaseInfo {
                         issuer: collection.owner.clone(),
-                        base_type: RmrkCore::get_collection_property(collection_id, RmrkProperty::BaseType)?.decode_or_default(),
-                        symbol: collection.token_prefix.rebind(),
+                        base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
+                        symbol: RmrkCore::rebind(&collection.token_prefix)?,
                     }))
                 }
 
@@ -382,15 +378,15 @@
 
                             match nft_type {
                                 NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
-                                    id: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?.decode_or_default(),
-                                    src: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::Src).ok()?.decode_or_default(),
-                                    z: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::ZIndex).ok()?.decode_or_default(),
+                                    id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+                                    src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+                                    z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
                                 })),
                                 NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
-                                    id: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?.decode_or_default(),
-                                    src: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::Src).ok()?.decode_or_default(),
-                                    z: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::ZIndex).ok()?.decode_or_default(),
-                                    equippable: RmrkCore::get_nft_property(collection_id, token_id, RmrkProperty::EquippableList).ok()?.decode_or_default(),
+                                    id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+                                    src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+                                    z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
+                                    equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
                                 })),
                                 _ => None
                             }
@@ -415,7 +411,7 @@
 
                             match nft_type {
                                 Theme => Some(
-                                    RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::ThemeName).unwrap().decode_or_default()
+                                    RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).unwrap()
                                 ),
                                 _ => None
                             }
@@ -441,9 +437,9 @@
                         .find_map(|token_id| {
                             RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
 
-                            let name: RmrkString = RmrkCore::get_nft_property(
+                            let name: RmrkString = RmrkCore::get_nft_property_decoded(
                                 collection_id, token_id, RmrkProperty::ThemeName
-                            ).ok()?.decode_or_default();
+                            ).ok()?;
 
                             if name == theme_name {
                                 Some((name, token_id))
@@ -467,11 +463,11 @@
                         }
                     )?;
 
-                    let inherit = RmrkCore::get_nft_property(
+                    let inherit = RmrkCore::get_nft_property_decoded(
                         collection_id,
                         theme_id,
                         RmrkProperty::ThemeInherit
-                    )?.decode_or_default();
+                    )?;
 
                     let theme = RmrkTheme {
                         name,