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 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 weights;3839pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4041#[frame_support::pallet]42pub mod pallet {43 use super::*;4445 #[pallet::config]46 pub trait Config: frame_system::Config + pallet_rmrk_core::Config {47 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;48 type WeightInfo: WeightInfo;49 }5051 #[pallet::storage]52 #[pallet::getter(fn internal_part_id)]53 pub type InernalPartId<T: Config> =54 StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;5556 #[pallet::storage]57 #[pallet::getter(fn base_has_default_theme)]58 pub type BaseHasDefaultTheme<T: Config> =59 StorageMap<_, Twox64Concat, CollectionId, bool, ValueQuery>;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 86 87 88 89 90 91 92 93 94 #[transactional]95 #[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]96 pub fn create_base(97 origin: OriginFor<T>,98 base_type: RmrkString,99 symbol: RmrkBaseSymbol,100 parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,101 ) -> DispatchResult {102 let sender = ensure_signed(origin)?;103 let cross_sender = T::CrossAccountId::from_sub(sender.clone());104105 let data = CreateCollectionData {106 limits: None,107 token_prefix: symbol108 .into_inner()109 .try_into()110 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,111 ..Default::default()112 };113114 let collection_id_res =115 <PalletNft<T>>::init_collection(cross_sender.clone(), data, true);116117 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {118 return Err(<Error<T>>::NoAvailableBaseId.into());119 }120121 let collection_id = collection_id_res?;122123 <PalletCommon<T>>::set_scoped_collection_properties(124 collection_id,125 PropertyScope::Rmrk,126 [127 <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,128 <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,129 ]130 .into_iter(),131 )?;132133 let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;134135 for part in parts {136 let part_id = part.id();137 let part_token_id = Self::create_part(&cross_sender, &collection, part)?;138139 <InernalPartId<T>>::insert(collection_id, part_id, part_token_id);140141 <PalletNft<T>>::set_scoped_token_property(142 collection_id,143 part_token_id,144 PropertyScope::Rmrk,145 <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,146 )?;147 }148149 Self::deposit_event(Event::BaseCreated {150 issuer: sender,151 base_id: collection_id.0,152 });153154 Ok(())155 }156157 158 159 160 161 162 163 164 165 166 167 168 169 170 #[transactional]171 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]172 pub fn theme_add(173 origin: OriginFor<T>,174 base_id: RmrkBaseId,175 theme: RmrkBoundedTheme,176 ) -> DispatchResult {177 let sender = ensure_signed(origin)?;178179 let sender = T::CrossAccountId::from_sub(sender);180 let owner = &sender;181182 let collection_id: CollectionId = base_id.into();183184 let collection = <PalletCore<T>>::get_typed_nft_collection(185 collection_id,186 misc::CollectionType::Base,187 )188 .map_err(|_| <Error<T>>::BaseDoesntExist)?;189 collection.check_is_external()?;190191 if theme.name.as_slice() == b"default" {192 <BaseHasDefaultTheme<T>>::insert(collection_id, true);193 } else if !Self::base_has_default_theme(collection_id) {194 return Err(<Error<T>>::NeedsDefaultThemeFirst.into());195 }196197 let token_id = <PalletCore<T>>::create_nft(198 &sender,199 owner,200 &collection,201 [202 <PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,203 <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,204 <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,205 ]206 .into_iter(),207 )208 .map_err(|_| <Error<T>>::PermissionError)?;209210 for property in theme.properties {211 <PalletNft<T>>::set_scoped_token_property(212 collection_id,213 token_id,214 PropertyScope::Rmrk,215 <PalletCore<T>>::rmrk_property(216 UserProperty(property.key.as_slice()),217 &property.value,218 )?,219 )?;220 }221222 Ok(())223 }224 }225}226227impl<T: Config> Pallet<T> {228 fn create_part(229 sender: &T::CrossAccountId,230 collection: &NonfungibleHandle<T>,231 part: RmrkPartType,232 ) -> Result<TokenId, DispatchError> {233 let owner = sender;234235 let src = part.src();236 let z_index = part.z_index();237238 let nft_type = match part {239 RmrkPartType::FixedPart(_) => NftType::FixedPart,240 RmrkPartType::SlotPart(_) => NftType::SlotPart,241 };242243 let token_id = <PalletCore<T>>::create_nft(244 sender,245 owner,246 collection,247 [248 <PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,249 <PalletCore<T>>::rmrk_property(Src, &src)?,250 <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,251 ]252 .into_iter(),253 )254 .map_err(|err| match err {255 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),256 err => err,257 })?;258259 if let RmrkPartType::SlotPart(part) = part {260 <PalletNft<T>>::set_scoped_token_property(261 collection.id,262 token_id,263 PropertyScope::Rmrk,264 <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,265 )?;266 }267268 Ok(token_id)269 }270}