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

difftreelog

source

pallets/proxy-rmrk-equip/src/lib.rs9.5 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,26	Error as CoreError,27	misc::{self, *},28	property::RmrkProperty::*,29};30use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle};31use pallet_evm::account::CrossAccountId;32use weights::WeightInfo;3334pub use pallet::*;3536#[cfg(feature = "runtime-benchmarks")]37pub mod benchmarking;38pub mod rpc;39pub mod weights;4041pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4243#[frame_support::pallet]44pub mod pallet {45	use super::*;4647	#[pallet::config]48	pub trait Config: frame_system::Config + pallet_rmrk_core::Config {49		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;50		type WeightInfo: WeightInfo;51	}5253	#[pallet::storage]54	#[pallet::getter(fn internal_part_id)]55	pub type InernalPartId<T: Config> =56		StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;5758	#[pallet::storage]59	#[pallet::getter(fn base_has_default_theme)]60	pub type BaseHasDefaultTheme<T: Config> =61		StorageMap<_, Twox64Concat, CollectionId, bool, 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		BaseCreated {71			issuer: T::AccountId,72			base_id: RmrkBaseId,73		},74		EquippablesUpdated {75			base_id: RmrkBaseId,76			slot_id: RmrkSlotId,77		},78	}7980	#[pallet::error]81	pub enum Error<T> {82		PermissionError,83		NoAvailableBaseId,84		NoAvailablePartId,85		BaseDoesntExist,86		NeedsDefaultThemeFirst,87		PartDoesntExist,88		NoEquippableOnFixedPart,89	}9091	#[pallet::call]92	impl<T: Config> Pallet<T> {93		/// Creates a new Base.94		/// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)95		///96		/// Parameters:97		/// - origin: Caller, will be assigned as the issuer of the Base98		/// - base_type: media type, e.g. "svg"99		/// - symbol: arbitrary client-chosen symbol100		/// - parts: array of Fixed and Slot parts composing the base, confined in length by101		///   RmrkPartsLimit102		#[transactional]103		#[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]104		pub fn create_base(105			origin: OriginFor<T>,106			base_type: RmrkString,107			symbol: RmrkBaseSymbol,108			parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,109		) -> DispatchResult {110			let sender = ensure_signed(origin)?;111			let cross_sender = T::CrossAccountId::from_sub(sender.clone());112113			let data = CreateCollectionData {114				limits: None,115				token_prefix: symbol116					.into_inner()117					.try_into()118					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,119				..Default::default()120			};121122			let collection_id_res =123				<PalletNft<T>>::init_collection(cross_sender.clone(), data, true);124125			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {126				return Err(<Error<T>>::NoAvailableBaseId.into());127			}128129			let collection_id = collection_id_res?;130131			<PalletCommon<T>>::set_scoped_collection_properties(132				collection_id,133				PropertyScope::Rmrk,134				[135					<PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,136					<PalletCore<T>>::rmrk_property(BaseType, &base_type)?,137				]138				.into_iter(),139			)?;140141			let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;142143			for part in parts {144				let part_id = part.id();145				let part_token_id = Self::create_part(&cross_sender, &collection, part)?;146147				<InernalPartId<T>>::insert(collection_id, part_id, part_token_id);148149				<PalletNft<T>>::set_scoped_token_property(150					collection_id,151					part_token_id,152					PropertyScope::Rmrk,153					<PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,154				)?;155			}156157			Self::deposit_event(Event::BaseCreated {158				issuer: sender,159				base_id: collection_id.0,160			});161162			Ok(())163		}164165		/// Adds a Theme to a Base.166		/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)167		/// Themes are stored in the Themes storage168		/// A Theme named "default" is required prior to adding other Themes.169		///170		/// Parameters:171		/// - origin: The caller of the function, must be issuer of the base172		/// - base_id: The Base containing the Theme to be updated173		/// - theme: The Theme to add to the Base.  A Theme has a name and properties, which are an174		///   array of [key, value, inherit].175		///   - key: arbitrary BoundedString, defined by client176		///   - value: arbitrary BoundedString, defined by client177		///   - inherit: optional bool178		#[transactional]179		#[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]180		pub fn theme_add(181			origin: OriginFor<T>,182			base_id: RmrkBaseId,183			theme: RmrkBoundedTheme,184		) -> DispatchResult {185			let sender = ensure_signed(origin)?;186187			let sender = T::CrossAccountId::from_sub(sender);188			let owner = &sender;189190			let collection_id: CollectionId = base_id.into();191192			let collection = Self::get_base(collection_id)?;193194			if theme.name.as_slice() == b"default" {195				<BaseHasDefaultTheme<T>>::insert(collection_id, true);196			} else if !Self::base_has_default_theme(collection_id) {197				return Err(<Error<T>>::NeedsDefaultThemeFirst.into());198			}199200			let token_id = <PalletCore<T>>::create_nft(201				&sender,202				owner,203				&collection,204				[205					<PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,206					<PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,207					<PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,208				]209				.into_iter(),210			)211			.map_err(|_| <Error<T>>::PermissionError)?;212213			for property in theme.properties {214				<PalletNft<T>>::set_scoped_token_property(215					collection_id,216					token_id,217					PropertyScope::Rmrk,218					<PalletCore<T>>::rmrk_property(219						UserProperty(property.key.as_slice()),220						&property.value,221					)?,222				)?;223			}224225			Ok(())226		}227228		#[transactional]229		#[pallet::weight(<SelfWeightOf<T>>::equippable())]230		pub fn equippable(231			origin: OriginFor<T>,232			base_id: RmrkBaseId,233			slot_id: RmrkSlotId,234			equippables: RmrkEquippableList,235		) -> DispatchResult {236			let sender = ensure_signed(origin)?;237238			let base_collection_id = base_id.into();239			let collection = Self::get_base(base_collection_id)?;240241			<PalletCore<T>>::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))242				.map_err(|err| if err == <CoreError<T>>::NoPermission.into() {243					<Error<T>>::PermissionError.into()244				} else {245					err246				})?;247248			let part_id = Self::internal_part_id(base_collection_id, slot_id)249				.ok_or(<Error<T>>::PartDoesntExist)?;250251			let nft_type = <PalletCore<T>>::get_nft_type(base_collection_id, part_id)252				.map_err(|_| <Error<T>>::PartDoesntExist)?;253254			match nft_type {255				NftType::Regular | NftType::Theme => return Err(<Error<T>>::PermissionError.into()),256				NftType::FixedPart => return Err(<Error<T>>::NoEquippableOnFixedPart.into()),257				NftType::SlotPart => {258					<PalletNft<T>>::set_scoped_token_property(259						base_collection_id,260						part_id,261						PropertyScope::Rmrk,262						<PalletCore<T>>::rmrk_property(EquippableList, &equippables)?,263					)?;264				}265			}266267			Self::deposit_event(Event::EquippablesUpdated {268				base_id,269				slot_id,270			});271272			Ok(())273		}274	}275}276277impl<T: Config> Pallet<T> {278	fn create_part(279		sender: &T::CrossAccountId,280		collection: &NonfungibleHandle<T>,281		part: RmrkPartType,282	) -> Result<TokenId, DispatchError> {283		let owner = sender;284285		let src = part.src();286		let z_index = part.z_index();287288		let nft_type = match part {289			RmrkPartType::FixedPart(_) => NftType::FixedPart,290			RmrkPartType::SlotPart(_) => NftType::SlotPart,291		};292293		let token_id = <PalletCore<T>>::create_nft(294			sender,295			owner,296			collection,297			[298				<PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,299				<PalletCore<T>>::rmrk_property(Src, &src)?,300				<PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,301			]302			.into_iter(),303		)304		.map_err(|err| match err {305			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),306			err => err,307		})?;308309		if let RmrkPartType::SlotPart(part) = part {310			<PalletNft<T>>::set_scoped_token_property(311				collection.id,312				token_id,313				PropertyScope::Rmrk,314				<PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,315			)?;316		}317318		Ok(token_id)319	}320321	fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {322		let collection = <PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)323			.map_err(|err| if err == <CoreError<T>>::CollectionUnknown.into() {324				<Error<T>>::BaseDoesntExist.into()325			} else {326				err327			})?;328		collection.check_is_external()?;329330		Ok(collection)331	}332}