123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899#![cfg_attr(not(feature = "std"), no_std)]100101use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};102use frame_system::{pallet_prelude::*, ensure_signed};103use sp_runtime::DispatchError;104use up_data_structs::*;105use pallet_common::{Pallet as PalletCommon, Error as CommonError};106use pallet_rmrk_core::{107 Pallet as PalletCore, Error as CoreError,108 misc::{self, *},109 property::RmrkProperty::*,110};111use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle};112use pallet_evm::account::CrossAccountId;113use weights::WeightInfo;114115pub use pallet::*;116117#[cfg(feature = "runtime-benchmarks")]118pub mod benchmarking;119pub mod rpc;120pub mod weights;121122pub type SelfWeightOf<T> = <T as Config>::WeightInfo;123124#[frame_support::pallet]125pub mod pallet {126 use super::*;127128 #[pallet::config]129 pub trait Config: frame_system::Config + pallet_rmrk_core::Config {130 131 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;132133 134 type WeightInfo: WeightInfo;135 }136137 138 #[pallet::storage]139 #[pallet::getter(fn internal_part_id)]140 pub type InernalPartId<T: Config> =141 StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;142143 144 #[pallet::storage]145 #[pallet::getter(fn base_has_default_theme)]146 pub type BaseHasDefaultTheme<T: Config> =147 StorageMap<_, Twox64Concat, CollectionId, bool, ValueQuery>;148149 #[pallet::pallet]150 #[pallet::generate_store(pub(super) trait Store)]151 pub struct Pallet<T>(_);152153 #[pallet::event]154 #[pallet::generate_deposit(pub(super) fn deposit_event)]155 pub enum Event<T: Config> {156 BaseCreated {157 issuer: T::AccountId,158 base_id: RmrkBaseId,159 },160 EquippablesUpdated {161 base_id: RmrkBaseId,162 slot_id: RmrkSlotId,163 },164 }165166 #[pallet::error]167 pub enum Error<T> {168 169 PermissionError,170 171 NoAvailableBaseId,172 173 NoAvailablePartId,174 175 BaseDoesntExist,176 177 NeedsDefaultThemeFirst,178 179 PartDoesntExist,180 181 NoEquippableOnFixedPart,182 }183184 #[pallet::call]185 impl<T: Config> Pallet<T> {186 187 188 189 190 191 192 193 194 195 196 197 198 #[transactional]199 #[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]200 pub fn create_base(201 origin: OriginFor<T>,202 base_type: RmrkString,203 symbol: RmrkBaseSymbol,204 parts: BoundedVec<RmrkPartType, RmrkPartsLimit>,205 ) -> DispatchResult {206 let sender = ensure_signed(origin)?;207 let cross_sender = T::CrossAccountId::from_sub(sender.clone());208209 let data = CreateCollectionData {210 limits: None,211 token_prefix: symbol212 .into_inner()213 .try_into()214 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,215 ..Default::default()216 };217218 let collection_id_res =219 <PalletNft<T>>::init_collection(cross_sender.clone(), data, true);220221 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {222 return Err(<Error<T>>::NoAvailableBaseId.into());223 }224225 let collection_id = collection_id_res?;226227 <PalletCommon<T>>::set_scoped_collection_properties(228 collection_id,229 PropertyScope::Rmrk,230 [231 <PalletCore<T>>::encode_rmrk_property(CollectionType, &misc::CollectionType::Base)?,232 <PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,233 ]234 .into_iter(),235 )?;236237 let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;238239 for part in parts {240 Self::create_part(&cross_sender, &collection, part)?;241 }242243 Self::deposit_event(Event::BaseCreated {244 issuer: sender,245 base_id: collection_id.0,246 });247248 Ok(())249 }250251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 #[transactional]267 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]268 pub fn theme_add(269 origin: OriginFor<T>,270 base_id: RmrkBaseId,271 theme: RmrkBoundedTheme,272 ) -> DispatchResult {273 let sender = ensure_signed(origin)?;274275 let sender = T::CrossAccountId::from_sub(sender);276 let owner = &sender;277278 let collection_id: CollectionId = base_id.into();279280 let collection = Self::get_base(collection_id)?;281282 if theme.name.as_slice() == b"default" {283 <BaseHasDefaultTheme<T>>::insert(collection_id, true);284 } else if !Self::base_has_default_theme(collection_id) {285 return Err(<Error<T>>::NeedsDefaultThemeFirst.into());286 }287288 let token_id = <PalletCore<T>>::create_nft(289 &sender,290 owner,291 &collection,292 [293 <PalletCore<T>>::encode_rmrk_property(TokenType, &NftType::Theme)?,294 <PalletCore<T>>::encode_rmrk_property(ThemeName, &theme.name)?,295 <PalletCore<T>>::encode_rmrk_property(ThemeInherit, &theme.inherit)?,296 ]297 .into_iter(),298 )299 .map_err(|_| <Error<T>>::PermissionError)?;300301 for property in theme.properties {302 <PalletNft<T>>::set_scoped_token_property(303 collection_id,304 token_id,305 PropertyScope::Rmrk,306 <PalletCore<T>>::encode_rmrk_property(307 UserProperty(property.key.as_slice()),308 &property.value,309 )?,310 )?;311 }312313 Ok(())314 }315316 317 318 319 320 321 322 323 324 325 326 327 #[transactional]328 #[pallet::weight(<SelfWeightOf<T>>::equippable())]329 pub fn equippable(330 origin: OriginFor<T>,331 base_id: RmrkBaseId,332 slot_id: RmrkSlotId,333 equippables: RmrkEquippableList,334 ) -> DispatchResult {335 let sender = ensure_signed(origin)?;336337 let base_collection_id = base_id.into();338 let collection = Self::get_base(base_collection_id)?;339340 <PalletCore<T>>::check_collection_owner(341 &collection,342 &T::CrossAccountId::from_sub(sender),343 )344 .map_err(|err| {345 if err == <CoreError<T>>::NoPermission.into() {346 <Error<T>>::PermissionError.into()347 } else {348 err349 }350 })?;351352 let part_id = Self::internal_part_id(base_collection_id, slot_id)353 .ok_or(<Error<T>>::PartDoesntExist)?;354355 let nft_type = <PalletCore<T>>::get_nft_type(base_collection_id, part_id)356 .map_err(|_| <Error<T>>::PartDoesntExist)?;357358 match nft_type {359 NftType::Regular | NftType::Theme => return Err(<Error<T>>::PermissionError.into()),360 NftType::FixedPart => return Err(<Error<T>>::NoEquippableOnFixedPart.into()),361 NftType::SlotPart => {362 <PalletNft<T>>::set_scoped_token_property(363 base_collection_id,364 part_id,365 PropertyScope::Rmrk,366 <PalletCore<T>>::encode_rmrk_property(EquippableList, &equippables)?,367 )?;368 }369 }370371 Self::deposit_event(Event::EquippablesUpdated { base_id, slot_id });372373 Ok(())374 }375 }376}377378impl<T: Config> Pallet<T> {379 380 381 fn create_part(382 sender: &T::CrossAccountId,383 collection: &NonfungibleHandle<T>,384 part: RmrkPartType,385 ) -> DispatchResult {386 let owner = sender;387388 let part_id = part.id();389 let src = part.src();390 let z_index = part.z_index();391392 let nft_type = match part {393 RmrkPartType::FixedPart(_) => NftType::FixedPart,394 RmrkPartType::SlotPart(_) => NftType::SlotPart,395 };396397 let token_id = match Self::internal_part_id(collection.id, part_id) {398 Some(token_id) => token_id,399 None => {400 let token_id =401 <PalletCore<T>>::create_nft(sender, owner, collection, [].into_iter())402 .map_err(|err| match err {403 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),404 err => err,405 })?;406407 <InernalPartId<T>>::insert(collection.id, part_id, token_id);408409 <PalletNft<T>>::set_scoped_token_property(410 collection.id,411 token_id,412 PropertyScope::Rmrk,413 <PalletCore<T>>::encode_rmrk_property(ExternalPartId, &part_id)?,414 )?;415416 token_id417 }418 };419420 <PalletNft<T>>::set_scoped_token_properties(421 collection.id,422 token_id,423 PropertyScope::Rmrk,424 [425 <PalletCore<T>>::encode_rmrk_property(TokenType, &nft_type)?,426 <PalletCore<T>>::encode_rmrk_property(Src, &src)?,427 <PalletCore<T>>::encode_rmrk_property(ZIndex, &z_index)?,428 ]429 .into_iter(),430 )?;431432 if let RmrkPartType::SlotPart(part) = part {433 <PalletNft<T>>::set_scoped_token_property(434 collection.id,435 token_id,436 PropertyScope::Rmrk,437 <PalletCore<T>>::encode_rmrk_property(EquippableList, &part.equippable)?,438 )?;439 }440441 Ok(())442 }443444 445 446 fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {447 let collection =448 <PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)449 .map_err(|err| {450 if err == <CoreError<T>>::CollectionUnknown.into() {451 <Error<T>>::BaseDoesntExist.into()452 } else {453 err454 }455 })?;456 collection.check_is_external()?;457458 Ok(collection)459 }460}