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

difftreelog

source

pallets/proxy-rmrk-equip/src/lib.rs9.7 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::DispatchError;22use up_data_structs::*;23use pallet_common::{Pallet as PalletCommon, Error as CommonError};24use pallet_rmrk_core::{25	Pallet as PalletCore, Error as CoreError,26	misc::{self, *},27	property::RmrkProperty::*,28};29use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle};30use pallet_evm::account::CrossAccountId;31use weights::WeightInfo;3233pub use pallet::*;3435#[cfg(feature = "runtime-benchmarks")]36pub mod benchmarking;37pub mod rpc;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;4546	#[pallet::config]47	pub trait Config: frame_system::Config + pallet_rmrk_core::Config {48		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;49		type WeightInfo: WeightInfo;50	}5152	#[pallet::storage]53	#[pallet::getter(fn internal_part_id)]54	pub type InernalPartId<T: Config> =55		StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;5657	#[pallet::storage]58	#[pallet::getter(fn base_has_default_theme)]59	pub type BaseHasDefaultTheme<T: Config> =60		StorageMap<_, Twox64Concat, CollectionId, bool, 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		BaseCreated {70			issuer: T::AccountId,71			base_id: RmrkBaseId,72		},73		EquippablesUpdated {74			base_id: RmrkBaseId,75			slot_id: RmrkSlotId,76		},77	}7879	#[pallet::error]80	pub enum Error<T> {81		PermissionError,82		NoAvailableBaseId,83		NoAvailablePartId,84		BaseDoesntExist,85		NeedsDefaultThemeFirst,86		PartDoesntExist,87		NoEquippableOnFixedPart,88	}8990	#[pallet::call]91	impl<T: Config> Pallet<T> {92		/// Creates a new Base.93		/// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)94		///95		/// Parameters:96		/// - origin: Caller, will be assigned as the issuer of the Base97		/// - base_type: media type, e.g. "svg"98		/// - symbol: arbitrary client-chosen symbol99		/// - parts: array of Fixed and Slot parts composing the base, confined in length by100		///   RmrkPartsLimit101		#[transactional]102		#[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]103		pub fn create_base(104			origin: OriginFor<T>,105			base_type: RmrkString,106			symbol: RmrkBaseSymbol,107			parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,108		) -> DispatchResult {109			let sender = ensure_signed(origin)?;110			let cross_sender = T::CrossAccountId::from_sub(sender.clone());111112			let data = CreateCollectionData {113				limits: None,114				token_prefix: symbol115					.into_inner()116					.try_into()117					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,118				..Default::default()119			};120121			let collection_id_res =122				<PalletNft<T>>::init_collection(cross_sender.clone(), data, true);123124			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {125				return Err(<Error<T>>::NoAvailableBaseId.into());126			}127128			let collection_id = collection_id_res?;129130			<PalletCommon<T>>::set_scoped_collection_properties(131				collection_id,132				PropertyScope::Rmrk,133				[134					<PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,135					<PalletCore<T>>::rmrk_property(BaseType, &base_type)?,136				]137				.into_iter(),138			)?;139140			let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;141142			for part in parts {143				Self::create_part(&cross_sender, &collection, part)?;144			}145146			Self::deposit_event(Event::BaseCreated {147				issuer: sender,148				base_id: collection_id.0,149			});150151			Ok(())152		}153154		/// Adds a Theme to a Base.155		/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)156		/// Themes are stored in the Themes storage157		/// A Theme named "default" is required prior to adding other Themes.158		///159		/// Parameters:160		/// - origin: The caller of the function, must be issuer of the base161		/// - base_id: The Base containing the Theme to be updated162		/// - theme: The Theme to add to the Base.  A Theme has a name and properties, which are an163		///   array of [key, value, inherit].164		///   - key: arbitrary BoundedString, defined by client165		///   - value: arbitrary BoundedString, defined by client166		///   - inherit: optional bool167		#[transactional]168		#[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]169		pub fn theme_add(170			origin: OriginFor<T>,171			base_id: RmrkBaseId,172			theme: RmrkBoundedTheme,173		) -> DispatchResult {174			let sender = ensure_signed(origin)?;175176			let sender = T::CrossAccountId::from_sub(sender);177			let owner = &sender;178179			let collection_id: CollectionId = base_id.into();180181			let collection = Self::get_base(collection_id)?;182183			if theme.name.as_slice() == b"default" {184				<BaseHasDefaultTheme<T>>::insert(collection_id, true);185			} else if !Self::base_has_default_theme(collection_id) {186				return Err(<Error<T>>::NeedsDefaultThemeFirst.into());187			}188189			let token_id = <PalletCore<T>>::create_nft(190				&sender,191				owner,192				&collection,193				[194					<PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,195					<PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,196					<PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,197				]198				.into_iter(),199			)200			.map_err(|_| <Error<T>>::PermissionError)?;201202			for property in theme.properties {203				<PalletNft<T>>::set_scoped_token_property(204					collection_id,205					token_id,206					PropertyScope::Rmrk,207					<PalletCore<T>>::rmrk_property(208						UserProperty(property.key.as_slice()),209						&property.value,210					)?,211				)?;212			}213214			Ok(())215		}216217		#[transactional]218		#[pallet::weight(<SelfWeightOf<T>>::equippable())]219		pub fn equippable(220			origin: OriginFor<T>,221			base_id: RmrkBaseId,222			slot_id: RmrkSlotId,223			equippables: RmrkEquippableList,224		) -> DispatchResult {225			let sender = ensure_signed(origin)?;226227			let base_collection_id = base_id.into();228			let collection = Self::get_base(base_collection_id)?;229230			<PalletCore<T>>::check_collection_owner(231				&collection,232				&T::CrossAccountId::from_sub(sender),233			)234			.map_err(|err| {235				if err == <CoreError<T>>::NoPermission.into() {236					<Error<T>>::PermissionError.into()237				} else {238					err239				}240			})?;241242			let part_id = Self::internal_part_id(base_collection_id, slot_id)243				.ok_or(<Error<T>>::PartDoesntExist)?;244245			let nft_type = <PalletCore<T>>::get_nft_type(base_collection_id, part_id)246				.map_err(|_| <Error<T>>::PartDoesntExist)?;247248			match nft_type {249				NftType::Regular | NftType::Theme => return Err(<Error<T>>::PermissionError.into()),250				NftType::FixedPart => return Err(<Error<T>>::NoEquippableOnFixedPart.into()),251				NftType::SlotPart => {252					<PalletNft<T>>::set_scoped_token_property(253						base_collection_id,254						part_id,255						PropertyScope::Rmrk,256						<PalletCore<T>>::rmrk_property(EquippableList, &equippables)?,257					)?;258				}259			}260261			Self::deposit_event(Event::EquippablesUpdated { base_id, slot_id });262263			Ok(())264		}265	}266}267268impl<T: Config> Pallet<T> {269	fn create_part(270		sender: &T::CrossAccountId,271		collection: &NonfungibleHandle<T>,272		part: RmrkPartType,273	) -> DispatchResult {274		let owner = sender;275276		let part_id = part.id();277		let src = part.src();278		let z_index = part.z_index();279280		let nft_type = match part {281			RmrkPartType::FixedPart(_) => NftType::FixedPart,282			RmrkPartType::SlotPart(_) => NftType::SlotPart,283		};284285		let token_id = match Self::internal_part_id(collection.id, part_id) {286			Some(token_id) => token_id,287			None => {288				let token_id =289					<PalletCore<T>>::create_nft(sender, owner, collection, [].into_iter())290						.map_err(|err| match err {291							DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),292							err => err,293						})?;294295				<InernalPartId<T>>::insert(collection.id, part_id, token_id);296297				<PalletNft<T>>::set_scoped_token_property(298					collection.id,299					token_id,300					PropertyScope::Rmrk,301					<PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,302				)?;303304				token_id305			}306		};307308		<PalletNft<T>>::set_scoped_token_properties(309			collection.id,310			token_id,311			PropertyScope::Rmrk,312			[313				<PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,314				<PalletCore<T>>::rmrk_property(Src, &src)?,315				<PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,316			]317			.into_iter(),318		)?;319320		if let RmrkPartType::SlotPart(part) = part {321			<PalletNft<T>>::set_scoped_token_property(322				collection.id,323				token_id,324				PropertyScope::Rmrk,325				<PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,326			)?;327		}328329		Ok(())330	}331332	fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {333		let collection =334			<PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)335				.map_err(|err| {336					if err == <CoreError<T>>::CollectionUnknown.into() {337						<Error<T>>::BaseDoesntExist.into()338					} else {339						err340					}341				})?;342		collection.check_is_external()?;343344		Ok(collection)345	}346}