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 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 }7475 #[pallet::error]76 pub enum Error<T> {77 PermissionError,78 NoAvailableBaseId,79 NoAvailablePartId,80 BaseDoesntExist,81 NeedsDefaultThemeFirst,82 }8384 #[pallet::call]85 impl<T: Config> Pallet<T> {86 87 88 89 90 91 92 93 94 95 #[transactional]96 #[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]97 pub fn create_base(98 origin: OriginFor<T>,99 base_type: RmrkString,100 symbol: RmrkBaseSymbol,101 parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,102 ) -> DispatchResult {103 let sender = ensure_signed(origin)?;104 let cross_sender = T::CrossAccountId::from_sub(sender.clone());105106 let data = CreateCollectionData {107 limits: None,108 token_prefix: symbol109 .into_inner()110 .try_into()111 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,112 ..Default::default()113 };114115 let collection_id_res =116 <PalletNft<T>>::init_collection(cross_sender.clone(), data, true);117118 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {119 return Err(<Error<T>>::NoAvailableBaseId.into());120 }121122 let collection_id = collection_id_res?;123124 <PalletCommon<T>>::set_scoped_collection_properties(125 collection_id,126 PropertyScope::Rmrk,127 [128 <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,129 <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,130 ]131 .into_iter(),132 )?;133134 let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;135136 for part in parts {137 let part_id = part.id();138 let part_token_id = Self::create_part(&cross_sender, &collection, part)?;139140 <InernalPartId<T>>::insert(collection_id, part_id, part_token_id);141142 <PalletNft<T>>::set_scoped_token_property(143 collection_id,144 part_token_id,145 PropertyScope::Rmrk,146 <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,147 )?;148 }149150 Self::deposit_event(Event::BaseCreated {151 issuer: sender,152 base_id: collection_id.0,153 });154155 Ok(())156 }157158 159 160 161 162 163 164 165 166 167 168 169 170 171 #[transactional]172 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]173 pub fn theme_add(174 origin: OriginFor<T>,175 base_id: RmrkBaseId,176 theme: RmrkBoundedTheme,177 ) -> DispatchResult {178 let sender = ensure_signed(origin)?;179180 let sender = T::CrossAccountId::from_sub(sender);181 let owner = &sender;182183 let collection_id: CollectionId = base_id.into();184185 let collection = <PalletCore<T>>::get_typed_nft_collection(186 collection_id,187 misc::CollectionType::Base,188 )189 .map_err(|_| <Error<T>>::BaseDoesntExist)?;190 collection.check_is_external()?;191192 if theme.name.as_slice() == b"default" {193 <BaseHasDefaultTheme<T>>::insert(collection_id, true);194 } else if !Self::base_has_default_theme(collection_id) {195 return Err(<Error<T>>::NeedsDefaultThemeFirst.into());196 }197198 let token_id = <PalletCore<T>>::create_nft(199 &sender,200 owner,201 &collection,202 [203 <PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,204 <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,205 <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,206 ]207 .into_iter(),208 )209 .map_err(|_| <Error<T>>::PermissionError)?;210211 for property in theme.properties {212 <PalletNft<T>>::set_scoped_token_property(213 collection_id,214 token_id,215 PropertyScope::Rmrk,216 <PalletCore<T>>::rmrk_property(217 UserProperty(property.key.as_slice()),218 &property.value,219 )?,220 )?;221 }222223 Ok(())224 }225 }226}227228impl<T: Config> Pallet<T> {229 fn create_part(230 sender: &T::CrossAccountId,231 collection: &NonfungibleHandle<T>,232 part: RmrkPartType,233 ) -> Result<TokenId, DispatchError> {234 let owner = sender;235236 let src = part.src();237 let z_index = part.z_index();238239 let nft_type = match part {240 RmrkPartType::FixedPart(_) => NftType::FixedPart,241 RmrkPartType::SlotPart(_) => NftType::SlotPart,242 };243244 let token_id = <PalletCore<T>>::create_nft(245 sender,246 owner,247 collection,248 [249 <PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,250 <PalletCore<T>>::rmrk_property(Src, &src)?,251 <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,252 ]253 .into_iter(),254 )255 .map_err(|err| match err {256 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),257 err => err,258 })?;259260 if let RmrkPartType::SlotPart(part) = part {261 <PalletNft<T>>::set_scoped_token_property(262 collection.id,263 token_id,264 PropertyScope::Rmrk,265 <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,266 )?;267 }268269 Ok(token_id)270 }271}