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::{Pallet as PalletCore, misc::{self, *}, property::RmrkProperty::*};25use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle};26use pallet_evm::account::CrossAccountId;2728pub use pallet::*;2930#[frame_support::pallet]31pub mod pallet {32 use super::*;3334 #[pallet::config]35 pub trait Config: frame_system::Config36 + pallet_rmrk_core::Config {37 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;38 }3940 #[pallet::storage]41 #[pallet::getter(fn internal_part_id)]42 pub type InernalPartId<T: Config> = StorageDoubleMap<43 _,44 Twox64Concat,45 CollectionId,46 Twox64Concat,47 RmrkPartId,48 TokenId49 >;5051 #[pallet::storage]52 #[pallet::getter(fn base_has_default_theme)]53 pub type BaseHasDefaultTheme<T: Config> = StorageMap<54 _,55 Twox64Concat,56 CollectionId,57 bool,58 ValueQuery59 >;6061 #[pallet::pallet]62 #[pallet::generate_store(pub(super) trait Store)]63 pub struct Pallet<T>(_);6465 #[pallet::event]66 #[pallet::generate_deposit(pub(super) fn deposit_event)]67 pub enum Event<T: Config> {68 BaseCreated {69 issuer: T::AccountId,70 base_id: RmrkBaseId,71 },72 }7374 #[pallet::error]75 pub enum Error<T> {76 PermissionError,77 NoAvailableBaseId,78 NoAvailablePartId,79 BaseDoesntExist,80 NeedsDefaultThemeFirst,81 }8283 #[pallet::call]84 impl<T: Config> Pallet<T> {85 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]86 #[transactional]87 pub fn create_base(88 origin: OriginFor<T>,89 base_type: RmrkString,90 symbol: RmrkString,91 parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,92 ) -> DispatchResult {93 let sender = ensure_signed(origin)?;94 let cross_sender = T::CrossAccountId::from_sub(sender.clone());9596 let data = CreateCollectionData {97 limits: None,98 token_prefix: symbol.into_inner()99 .try_into()100 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,101 ..Default::default()102 };103104 let collection_id_res = <PalletNft<T>>::init_collection(sender.clone(), data);105106 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {107 return Err(<Error<T>>::NoAvailableBaseId.into());108 }109110 let collection_id = collection_id_res?;111112 <PalletCommon<T>>::set_scoped_collection_properties(113 collection_id,114 PropertyScope::Rmrk,115 [116 <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,117 <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,118 ].into_iter()119 )?;120121 let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;122123 for part in parts {124 let part_id = part.id();125 let part_token_id = Self::create_part(126 &cross_sender,127 &collection,128 part129 )?;130131 <InernalPartId<T>>::insert(collection_id, part_id, part_token_id);132133 <PalletNft<T>>::set_scoped_token_property(134 collection_id,135 part_token_id,136 PropertyScope::Rmrk,137 <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?138 )?;139 }140141 Self::deposit_event(Event::BaseCreated { issuer: sender, base_id: collection_id.0 });142143 Ok(())144 }145146 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]147 #[transactional]148 pub fn theme_add(149 origin: OriginFor<T>,150 base_id: RmrkBaseId,151 theme: RmrkTheme,152 ) -> DispatchResult {153 let sender = ensure_signed(origin)?;154155 let sender = T::CrossAccountId::from_sub(sender);156 let owner = &sender;157158 let collection_id: CollectionId = base_id.into();159160 let collection = <PalletCore<T>>::get_typed_nft_collection(161 collection_id,162 misc::CollectionType::Base163 ).map_err(|_| <Error<T>>::BaseDoesntExist)?;164165 if theme.name.as_slice() == b"default" {166 <BaseHasDefaultTheme<T>>::insert(collection_id, true);167 } else if !Self::base_has_default_theme(collection_id) {168 return Err(<Error<T>>::NeedsDefaultThemeFirst.into());169 }170171 let token_id = <PalletCore<T>>::create_nft(172 &sender,173 owner,174 &collection,175 NftType::Theme,176 [177 <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,178 <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?179 ].into_iter()180 ).map_err(|_| <Error<T>>::PermissionError)?;181182 for property in theme.properties {183 <PalletNft<T>>::set_scoped_token_property(184 collection_id,185 token_id,186 PropertyScope::Rmrk,187 <PalletCore<T>>::rmrk_property(188 UserProperty(property.key.as_slice()),189 &property.value190 )?191 )?;192 }193194 Ok(())195 }196 }197}198199impl<T: Config> Pallet<T> {200 fn create_part(201 sender: &T::CrossAccountId,202 collection: &NonfungibleHandle<T>,203 part: RmrkPartType204 ) -> Result<TokenId, DispatchError> {205 let owner = sender;206207 let src = part.src();208 let z_index = part.z_index();209210 let nft_type = match part {211 RmrkPartType::FixedPart(_) => NftType::FixedPart,212 RmrkPartType::SlotPart(_) => NftType::SlotPart,213 };214215 let token_id = <PalletCore<T>>::create_nft(216 sender,217 owner,218 collection,219 nft_type,220 [221 <PalletCore<T>>::rmrk_property(Src, &src)?,222 <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?223 ].into_iter()224 ).map_err(|err| match err {225 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),226 err => err227 })?;228229 if let RmrkPartType::SlotPart(part) = part {230 <PalletNft<T>>::set_scoped_token_property(231 collection.id,232 token_id,233 PropertyScope::Rmrk,234 <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?235 )?;236 }237238 Ok(token_id)239 }240}