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(232 CollectionType,233 &misc::CollectionType::Base,234 )?,235 <PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,236 ]237 .into_iter(),238 )?;239240 let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;241242 for part in parts {243 Self::create_part(&cross_sender, &collection, part)?;244 }245246 Self::deposit_event(Event::BaseCreated {247 issuer: sender,248 base_id: collection_id.0,249 });250251 Ok(())252 }253254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 #[transactional]270 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]271 pub fn theme_add(272 origin: OriginFor<T>,273 base_id: RmrkBaseId,274 theme: RmrkBoundedTheme,275 ) -> DispatchResult {276 let sender = ensure_signed(origin)?;277278 let sender = T::CrossAccountId::from_sub(sender);279 let owner = &sender;280281 let collection_id: CollectionId = base_id.into();282283 let collection = Self::get_base(collection_id)?;284285 if theme.name.as_slice() == b"default" {286 <BaseHasDefaultTheme<T>>::insert(collection_id, true);287 } else if !Self::base_has_default_theme(collection_id) {288 return Err(<Error<T>>::NeedsDefaultThemeFirst.into());289 }290291 let token_id = <PalletCore<T>>::create_nft(292 &sender,293 owner,294 &collection,295 [296 <PalletCore<T>>::encode_rmrk_property(TokenType, &NftType::Theme)?,297 <PalletCore<T>>::encode_rmrk_property(ThemeName, &theme.name)?,298 <PalletCore<T>>::encode_rmrk_property(ThemeInherit, &theme.inherit)?,299 ]300 .into_iter(),301 )302 .map_err(|_| <Error<T>>::PermissionError)?;303304 for property in theme.properties {305 <PalletNft<T>>::set_scoped_token_property(306 collection_id,307 token_id,308 PropertyScope::Rmrk,309 <PalletCore<T>>::encode_rmrk_property(310 UserProperty(property.key.as_slice()),311 &property.value,312 )?,313 )?;314 }315316 Ok(())317 }318319 320 321 322 323 324 325 326 327 328 329 330 #[transactional]331 #[pallet::weight(<SelfWeightOf<T>>::equippable())]332 pub fn equippable(333 origin: OriginFor<T>,334 base_id: RmrkBaseId,335 slot_id: RmrkSlotId,336 equippables: RmrkEquippableList,337 ) -> DispatchResult {338 let sender = ensure_signed(origin)?;339340 let base_collection_id = base_id.into();341 let collection = Self::get_base(base_collection_id)?;342343 <PalletCore<T>>::check_collection_owner(344 &collection,345 &T::CrossAccountId::from_sub(sender),346 )347 .map_err(|err| {348 if err == <CoreError<T>>::NoPermission.into() {349 <Error<T>>::PermissionError.into()350 } else {351 err352 }353 })?;354355 let part_id = Self::internal_part_id(base_collection_id, slot_id)356 .ok_or(<Error<T>>::PartDoesntExist)?;357358 let nft_type = <PalletCore<T>>::get_nft_type(base_collection_id, part_id)359 .map_err(|_| <Error<T>>::PartDoesntExist)?;360361 match nft_type {362 NftType::Regular | NftType::Theme => return Err(<Error<T>>::PermissionError.into()),363 NftType::FixedPart => return Err(<Error<T>>::NoEquippableOnFixedPart.into()),364 NftType::SlotPart => {365 <PalletNft<T>>::set_scoped_token_property(366 base_collection_id,367 part_id,368 PropertyScope::Rmrk,369 <PalletCore<T>>::encode_rmrk_property(EquippableList, &equippables)?,370 )?;371 }372 }373374 Self::deposit_event(Event::EquippablesUpdated { base_id, slot_id });375376 Ok(())377 }378 }379}380381impl<T: Config> Pallet<T> {382 383 384 fn create_part(385 sender: &T::CrossAccountId,386 collection: &NonfungibleHandle<T>,387 part: RmrkPartType,388 ) -> DispatchResult {389 let owner = sender;390391 let part_id = part.id();392 let src = part.src();393 let z_index = part.z_index();394395 let nft_type = match part {396 RmrkPartType::FixedPart(_) => NftType::FixedPart,397 RmrkPartType::SlotPart(_) => NftType::SlotPart,398 };399400 let token_id = match Self::internal_part_id(collection.id, part_id) {401 Some(token_id) => token_id,402 None => {403 let token_id =404 <PalletCore<T>>::create_nft(sender, owner, collection, [].into_iter())405 .map_err(|err| match err {406 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),407 err => err,408 })?;409410 <InernalPartId<T>>::insert(collection.id, part_id, token_id);411412 <PalletNft<T>>::set_scoped_token_property(413 collection.id,414 token_id,415 PropertyScope::Rmrk,416 <PalletCore<T>>::encode_rmrk_property(ExternalPartId, &part_id)?,417 )?;418419 token_id420 }421 };422423 <PalletNft<T>>::set_scoped_token_properties(424 collection.id,425 token_id,426 PropertyScope::Rmrk,427 [428 <PalletCore<T>>::encode_rmrk_property(TokenType, &nft_type)?,429 <PalletCore<T>>::encode_rmrk_property(Src, &src)?,430 <PalletCore<T>>::encode_rmrk_property(ZIndex, &z_index)?,431 ]432 .into_iter(),433 )?;434435 if let RmrkPartType::SlotPart(part) = part {436 <PalletNft<T>>::set_scoped_token_property(437 collection.id,438 token_id,439 PropertyScope::Rmrk,440 <PalletCore<T>>::encode_rmrk_property(EquippableList, &part.equippable)?,441 )?;442 }443444 Ok(())445 }446447 448 449 fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {450 let collection =451 <PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)452 .map_err(|err| {453 if err == <CoreError<T>>::CollectionUnknown.into() {454 <Error<T>>::BaseDoesntExist.into()455 } else {456 err457 }458 })?;459 collection.check_is_external()?;460461 Ok(collection)462 }463}