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, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::*;24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_evm::account::CrossAccountId;29use core::convert::AsRef;3031pub use pallet::*;3233pub mod misc;34pub mod property;3536use misc::*;37pub use property::*;3839use RmrkProperty::*;4041#[frame_support::pallet]42pub mod pallet {43 use super::*;44 use pallet_evm::account;4546 #[pallet::config]47 pub trait Config:48 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config49 {50 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;51 }5253 #[pallet::storage]54 #[pallet::getter(fn collection_index)]55 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5657 #[pallet::pallet]58 #[pallet::generate_store(pub(super) trait Store)]59 pub struct Pallet<T>(_);6061 #[pallet::event]62 #[pallet::generate_deposit(pub(super) fn deposit_event)]63 pub enum Event<T: Config> {64 CollectionCreated {65 issuer: T::AccountId,66 collection_id: RmrkCollectionId,67 },68 CollectionDestroyed {69 issuer: T::AccountId,70 collection_id: RmrkCollectionId,71 },72 IssuerChanged {73 old_issuer: T::AccountId,74 new_issuer: T::AccountId,75 collection_id: RmrkCollectionId,76 },77 CollectionLocked {78 issuer: T::AccountId,79 collection_id: RmrkCollectionId,80 },81 NftMinted {82 owner: T::AccountId,83 collection_id: RmrkCollectionId,84 nft_id: RmrkNftId,85 },86 NFTBurned {87 owner: T::AccountId,88 nft_id: RmrkNftId,89 },90 PropertySet {91 collection_id: RmrkCollectionId,92 maybe_nft_id: Option<RmrkNftId>,93 key: RmrkKeyString,94 value: RmrkValueString,95 },96 }9798 #[pallet::error]99 pub enum Error<T> {100 101 CorruptedCollectionType,102 NftTypeEncodeError,103 RmrkPropertyKeyIsTooLong,104 RmrkPropertyValueIsTooLong,105106 107 CollectionNotEmpty,108 NoAvailableCollectionId,109 NoAvailableNftId,110 CollectionUnknown,111 NoPermission,112 CollectionFullOrLocked,113 }114115 #[pallet::call]116 impl<T: Config> Pallet<T> {117 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]118 #[transactional]119 pub fn create_collection(120 origin: OriginFor<T>,121 metadata: RmrkString,122 max: Option<u32>,123 symbol: RmrkCollectionSymbol,124 ) -> DispatchResult {125 let sender = ensure_signed(origin)?;126127 let limits = CollectionLimits {128 owner_can_transfer: Some(false),129 token_limit: max,130 ..Default::default()131 };132133 let data = CreateCollectionData {134 limits: Some(limits),135 token_prefix: symbol136 .into_inner()137 .try_into()138 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,139 ..Default::default()140 };141142 let collection_id_res = <PalletNft<T>>::init_collection(sender.clone(), data);143144 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {145 return Err(<Error<T>>::NoAvailableCollectionId.into());146 }147148 let collection_id = collection_id_res?;149150 <PalletCommon<T>>::set_scoped_collection_properties(151 collection_id,152 PropertyScope::Rmrk,153 [154 Self::rmrk_property(Metadata, &metadata)?,155 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,156 ]157 .into_iter(),158 )?;159160 <CollectionIndex<T>>::mutate(|n| *n += 1);161162 Self::deposit_event(Event::CollectionCreated {163 issuer: sender,164 collection_id: collection_id.0,165 });166167 Ok(())168 }169170 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]171 #[transactional]172 pub fn destroy_collection(173 origin: OriginFor<T>,174 collection_id: RmrkCollectionId,175 ) -> DispatchResult {176 let sender = ensure_signed(origin)?;177 let cross_sender = T::CrossAccountId::from_sub(sender.clone());178179 let unique_collection_id = collection_id.into();180181 let collection = Self::get_typed_nft_collection(182 unique_collection_id,183 misc::CollectionType::Regular,184 )?;185186 ensure!(187 collection.total_supply() == 0,188 <Error<T>>::CollectionNotEmpty189 );190191 <PalletNft<T>>::destroy_collection(collection, &cross_sender)192 .map_err(Self::map_common_err_to_proxy)?;193194 Self::deposit_event(Event::CollectionDestroyed {195 issuer: sender,196 collection_id,197 });198199 Ok(())200 }201202 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]203 #[transactional]204 pub fn change_collection_issuer(205 origin: OriginFor<T>,206 collection_id: RmrkCollectionId,207 new_issuer: <T::Lookup as StaticLookup>::Source,208 ) -> DispatchResult {209 let sender = ensure_signed(origin)?;210211 let new_issuer = T::Lookup::lookup(new_issuer)?;212213 Self::change_collection_owner(214 collection_id.into(),215 misc::CollectionType::Regular,216 sender.clone(),217 new_issuer.clone(),218 )?;219220 Self::deposit_event(Event::IssuerChanged {221 old_issuer: sender,222 new_issuer,223 collection_id,224 });225226 Ok(())227 }228229 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]230 #[transactional]231 pub fn lock_collection(232 origin: OriginFor<T>,233 collection_id: RmrkCollectionId,234 ) -> DispatchResult {235 let sender = ensure_signed(origin)?;236 let cross_sender = T::CrossAccountId::from_sub(sender.clone());237238 let collection = Self::get_typed_nft_collection(239 collection_id.into(),240 misc::CollectionType::Regular,241 )?;242243 Self::check_collection_owner(&collection, &cross_sender)?;244245 let token_count = collection.total_supply();246247 let mut collection = collection.into_inner();248 collection.limits.token_limit = Some(token_count);249 collection.save()?;250251 Self::deposit_event(Event::CollectionLocked {252 issuer: sender,253 collection_id,254 });255256 Ok(())257 }258259 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]260 #[transactional]261 pub fn mint_nft(262 origin: OriginFor<T>,263 owner: T::AccountId,264 collection_id: RmrkCollectionId,265 recipient: Option<T::AccountId>,266 royalty_amount: Option<Permill>,267 metadata: RmrkString,268 ) -> DispatchResult {269 let sender = ensure_signed(origin)?;270 let sender = T::CrossAccountId::from_sub(sender);271 let cross_owner = T::CrossAccountId::from_sub(owner.clone());272273 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {274 recipient: recipient.unwrap_or_else(|| owner.clone()),275 amount,276 });277278 let collection = Self::get_typed_nft_collection(279 collection_id.into(),280 misc::CollectionType::Regular,281 )?;282283 let nft_id = Self::create_nft(284 &sender,285 &cross_owner,286 &collection,287 NftType::Regular,288 [289 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,290 Self::rmrk_property(Metadata, &metadata)?,291 Self::rmrk_property(Equipped, &false)?,292 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,293 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,294 ]295 .into_iter(),296 )297 .map_err(|err| match err {298 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),299 err => Self::map_common_err_to_proxy(err),300 })?;301302 Self::deposit_event(Event::NftMinted {303 owner,304 collection_id,305 nft_id: nft_id.0,306 });307308 Ok(())309 }310311 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]312 #[transactional]313 pub fn burn_nft(314 origin: OriginFor<T>,315 collection_id: RmrkCollectionId,316 nft_id: RmrkNftId,317 ) -> DispatchResult {318 let sender = ensure_signed(origin)?;319 let cross_sender = T::CrossAccountId::from_sub(sender.clone());320321 Self::destroy_nft(322 cross_sender,323 collection_id.into(),324 misc::CollectionType::Regular,325 nft_id.into(),326 )?;327328 Self::deposit_event(Event::NFTBurned {329 owner: sender,330 nft_id,331 });332333 Ok(())334 }335336 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]337 #[transactional]338 pub fn set_property(339 origin: OriginFor<T>,340 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,341 maybe_nft_id: Option<RmrkNftId>,342 key: RmrkKeyString,343 value: RmrkValueString,344 ) -> DispatchResult {345 let sender = ensure_signed(origin)?;346 let sender = T::CrossAccountId::from_sub(sender);347348 let collection_id: CollectionId = rmrk_collection_id.into();349350 match maybe_nft_id {351 Some(nft_id) => {352 let token_id: TokenId = nft_id.into();353354 Self::ensure_nft_owner(collection_id, token_id, &sender)?;355 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;356357 <PalletNft<T>>::set_scoped_token_property(358 collection_id,359 token_id,360 PropertyScope::Rmrk,361 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,362 )?;363 }364 None => {365 let collection = Self::get_typed_nft_collection(366 collection_id,367 misc::CollectionType::Regular,368 )?;369370 Self::check_collection_owner(&collection, &sender)?;371372 <PalletCommon<T>>::set_scoped_collection_property(373 collection_id,374 PropertyScope::Rmrk,375 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,376 )?;377 }378 }379380 Self::deposit_event(Event::PropertySet {381 collection_id: rmrk_collection_id,382 maybe_nft_id,383 key,384 value,385 });386387 Ok(())388 }389 }390}391392impl<T: Config> Pallet<T> {393 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {394 let key = rmrk_key.to_key::<T>()?;395396 let scoped_key = PropertyScope::Rmrk397 .apply(key)398 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;399400 Ok(scoped_key)401 }402403 pub fn rmrk_property<E: Encode>(404 rmrk_key: RmrkProperty,405 value: &E,406 ) -> Result<Property, DispatchError> {407 let key = rmrk_key.to_key::<T>()?;408409 let value = value410 .encode()411 .try_into()412 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;413414 let property = Property { key, value };415416 Ok(property)417 }418419 pub fn create_nft(420 sender: &T::CrossAccountId,421 owner: &T::CrossAccountId,422 collection: &NonfungibleHandle<T>,423 nft_type: NftType,424 properties: impl Iterator<Item = Property>,425 ) -> Result<TokenId, DispatchError> {426 todo!("store nft type");427 let data = CreateNftExData {428 properties: BoundedVec::default(),429 owner: owner.clone(),430 };431432 let budget = budget::Value::new(2);433434 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;435436 let nft_id = <PalletNft<T>>::current_token_id(collection.id);437438 <PalletNft<T>>::set_scoped_token_properties(439 collection.id,440 nft_id,441 PropertyScope::Rmrk,442 properties,443 )?;444445 Ok(nft_id)446 }447448 fn destroy_nft(449 sender: T::CrossAccountId,450 collection_id: CollectionId,451 collection_type: misc::CollectionType,452 token_id: TokenId,453 ) -> DispatchResult {454 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;455456 <PalletNft<T>>::burn(&collection, &sender, token_id)457 .map_err(Self::map_common_err_to_proxy)?;458459 Ok(())460 }461462 fn change_collection_owner(463 collection_id: CollectionId,464 collection_type: misc::CollectionType,465 sender: T::AccountId,466 new_owner: T::AccountId,467 ) -> DispatchResult {468 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;469 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;470471 let mut collection = collection.into_inner();472473 collection.owner = new_owner;474 collection.save()475 }476477 fn check_collection_owner(478 collection: &NonfungibleHandle<T>,479 account: &T::CrossAccountId,480 ) -> DispatchResult {481 collection482 .check_is_owner(account)483 .map_err(Self::map_common_err_to_proxy)484 }485486 pub fn last_collection_idx() -> RmrkCollectionId {487 <CollectionIndex<T>>::get()488 }489490 pub fn get_nft_collection(491 collection_id: CollectionId,492 ) -> Result<NonfungibleHandle<T>, DispatchError> {493 let collection = <CollectionHandle<T>>::try_get(collection_id)494 .map_err(|_| <Error<T>>::CollectionUnknown)?;495496 match collection.mode {497 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),498 _ => Err(<Error<T>>::CollectionUnknown.into()),499 }500 }501502 pub fn collection_exists(collection_id: CollectionId) -> bool {503 <CollectionHandle<T>>::try_get(collection_id).is_ok()504 }505506 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {507 <TokenData<T>>::contains_key((collection_id, nft_id))508 }509510 pub fn get_collection_property(511 collection_id: CollectionId,512 key: RmrkProperty,513 ) -> Result<PropertyValue, DispatchError> {514 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)515 .get(&Self::rmrk_property_key(key)?)516 .ok_or(<Error<T>>::CollectionUnknown)?517 .clone();518519 Ok(collection_property)520 }521522 pub fn get_collection_type(523 collection_id: CollectionId,524 ) -> Result<misc::CollectionType, DispatchError> {525 let value = Self::get_collection_property(collection_id, CollectionType)?;526527 let mut value = value.as_slice();528529 misc::CollectionType::decode(&mut value)530 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())531 }532533 pub fn ensure_collection_type(534 collection_id: CollectionId,535 collection_type: misc::CollectionType,536 ) -> DispatchResult {537 let actual_type = Self::get_collection_type(collection_id)?;538 ensure!(539 actual_type == collection_type,540 <CommonError<T>>::NoPermission541 );542543 Ok(())544 }545546 pub fn get_nft_property(547 collection_id: CollectionId,548 nft_id: TokenId,549 key: RmrkProperty,550 ) -> Result<PropertyValue, DispatchError> {551 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))552 .get(&Self::rmrk_property_key(key)?)553 .ok_or(<Error<T>>::NoAvailableNftId)?554 .clone();555556 Ok(nft_property)557 }558559 pub fn get_nft_type(560 _collection_id: CollectionId,561 _token_id: TokenId,562 ) -> Result<NftType, DispatchError> {563 todo!("should get it from properties?")564 }565566 pub fn ensure_nft_type(567 collection_id: CollectionId,568 token_id: TokenId,569 nft_type: NftType,570 ) -> DispatchResult {571 let actual_type = Self::get_nft_type(collection_id, token_id)?;572 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);573574 Ok(())575 }576577 pub fn ensure_nft_owner(578 collection_id: CollectionId,579 token_id: TokenId,580 possible_owner: &T::CrossAccountId,581 ) -> DispatchResult {582 let token_data =583 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;584585 ensure!(586 token_data.owner == *possible_owner,587 <Error<T>>::NoPermission588 );589590 Ok(())591 }592593 pub fn filter_user_properties<Key, Value, R, Mapper>(594 collection_id: CollectionId,595 token_id: Option<TokenId>,596 filter_keys: Option<Vec<RmrkPropertyKey>>,597 mapper: Mapper,598 ) -> Result<Vec<R>, DispatchError>599 where600 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,601 Value: Decode + Default,602 Mapper: Fn(Key, Value) -> R,603 {604 filter_keys605 .map(|keys| {606 let properties = keys607 .into_iter()608 .filter_map(|key| {609 let key: Key = key.try_into().ok()?;610611 let value = match token_id {612 Some(token_id) => Self::get_nft_property(613 collection_id,614 token_id,615 UserProperty(key.as_ref()),616 ),617 None => Self::get_collection_property(618 collection_id,619 UserProperty(key.as_ref()),620 ),621 }622 .ok()?623 .decode_or_default();624625 Some(mapper(key, value))626 })627 .collect();628629 Ok(properties)630 })631 .unwrap_or_else(|| {632 let properties =633 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();634635 Ok(properties)636 })637 }638639 pub fn iterate_user_properties<Key, Value, R, Mapper>(640 collection_id: CollectionId,641 token_id: Option<TokenId>,642 mapper: Mapper,643 ) -> Result<impl Iterator<Item = R>, DispatchError>644 where645 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,646 Value: Decode + Default,647 Mapper: Fn(Key, Value) -> R,648 {649 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;650651 let properties = match token_id {652 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),653 None => <PalletCommon<T>>::collection_properties(collection_id),654 };655656 let properties = properties.into_iter().filter_map(move |(key, value)| {657 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;658659 let key: Key = key.to_vec().try_into().ok()?;660 let value: Value = value.decode_or_default();661662 Some(mapper(key, value))663 });664665 Ok(properties)666 }667668 pub fn get_typed_nft_collection(669 collection_id: CollectionId,670 collection_type: misc::CollectionType,671 ) -> Result<NonfungibleHandle<T>, DispatchError> {672 Self::ensure_collection_type(collection_id, collection_type)?;673674 Self::get_nft_collection(collection_id)675 }676677 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {678 map_common_err_to_proxy! {679 match err {680 NoPermission => NoPermission,681 CollectionTokenLimitExceeded => CollectionFullOrLocked,682 PublicMintingNotAllowed => NoPermission,683 TokenNotFound => NoAvailableNftId684 }685 }686 }687}