1234567891011121314151617#![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 94 95 96 97 98 99 100 101 102 #[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 166 167 168 169 170 171 172 173 174 175 176 177 178 #[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}