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::{Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations};25use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};26use pallet_evm::account::CrossAccountId;27use core::convert::AsRef;2829pub use pallet::*;3031pub mod misc;32pub mod property;3334use misc::*;35pub use property::*;3637use RmrkProperty::*;3839#[frame_support::pallet]40pub mod pallet {41 use super::*;42 use pallet_evm::account;4344 #[pallet::config]45 pub trait Config: frame_system::Config46 + pallet_common::Config47 + pallet_nonfungible::Config48 + account::Config {49 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;50 }5152 #[pallet::storage]53 #[pallet::getter(fn collection_index)]54 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5556 #[pallet::pallet]57 #[pallet::generate_store(pub(super) trait Store)]58 pub struct Pallet<T>(_);5960 #[pallet::event]61 #[pallet::generate_deposit(pub(super) fn deposit_event)]62 pub enum Event<T: Config> {63 CollectionCreated {64 issuer: T::AccountId,65 collection_id: RmrkCollectionId,66 },67 CollectionDestroyed {68 issuer: T::AccountId,69 collection_id: RmrkCollectionId,70 },71 IssuerChanged {72 old_issuer: T::AccountId,73 new_issuer: T::AccountId,74 collection_id: RmrkCollectionId,75 },76 CollectionLocked {77 issuer: T::AccountId,78 collection_id: RmrkCollectionId,79 },80 NftMinted {81 owner: T::AccountId,82 collection_id: RmrkCollectionId,83 nft_id: RmrkNftId,84 },85 NFTBurned {86 owner: T::AccountId,87 nft_id: RmrkNftId,88 },89 PropertySet {90 collection_id: RmrkCollectionId,91 maybe_nft_id: Option<RmrkNftId>,92 key: RmrkKeyString,93 value: RmrkValueString,94 },95 }9697 #[pallet::error]98 pub enum Error<T> {99 100 CorruptedCollectionType,101 NftTypeEncodeError,102 RmrkPropertyKeyIsTooLong,103 RmrkPropertyValueIsTooLong,104105 106 CollectionNotEmpty,107 NoAvailableCollectionId,108 NoAvailableNftId,109 CollectionUnknown,110 NoPermission,111 CollectionFullOrLocked,112 }113114 #[pallet::call]115 impl<T: Config> Pallet<T> {116 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]117 #[transactional]118 pub fn create_collection(119 origin: OriginFor<T>,120 metadata: RmrkString,121 max: Option<u32>,122 symbol: RmrkCollectionSymbol,123 ) -> DispatchResult {124 let sender = ensure_signed(origin)?;125126 let limits = CollectionLimits {127 owner_can_transfer: Some(false),128 token_limit: max,129 ..Default::default()130 };131132 let data = CreateCollectionData {133 limits: Some(limits),134 token_prefix: symbol.into_inner()135 .try_into()136 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,137 ..Default::default()138 };139140 let collection_id_res = <PalletNft<T>>::init_collection(sender.clone(), data);141142 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {143 return Err(<Error<T>>::NoAvailableCollectionId.into());144 }145146 let collection_id = collection_id_res?;147148 <PalletCommon<T>>::set_scoped_collection_properties(149 collection_id,150 PropertyScope::Rmrk,151 [152 Self::rmrk_property(Metadata, &metadata)?,153 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,154 ].into_iter()155 )?;156157 <CollectionIndex<T>>::mutate(|n| *n += 1);158159 Self::deposit_event(Event::CollectionCreated {160 issuer: sender,161 collection_id: collection_id.0162 });163164 Ok(())165 }166167 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]168 #[transactional]169 pub fn destroy_collection(170 origin: OriginFor<T>,171 collection_id: RmrkCollectionId,172 ) -> DispatchResult {173 let sender = ensure_signed(origin)?;174 let cross_sender = T::CrossAccountId::from_sub(sender.clone());175176 let unique_collection_id = collection_id.into();177178 let collection = Self::get_typed_nft_collection(unique_collection_id, misc::CollectionType::Regular)?;179180 ensure!(collection.total_supply() == 0, <Error<T>>::CollectionNotEmpty);181182 <PalletNft<T>>::destroy_collection(collection, &cross_sender)183 .map_err(Self::map_common_err_to_proxy)?;184185 Self::deposit_event(Event::CollectionDestroyed { issuer: sender, collection_id });186187 Ok(())188 }189190 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191 #[transactional]192 pub fn change_collection_issuer(193 origin: OriginFor<T>,194 collection_id: RmrkCollectionId,195 new_issuer: <T::Lookup as StaticLookup>::Source,196 ) -> DispatchResult {197 let sender = ensure_signed(origin)?;198199 let new_issuer = T::Lookup::lookup(new_issuer)?;200201 Self::change_collection_owner(202 collection_id.into(),203 misc::CollectionType::Regular,204 sender.clone(),205 new_issuer.clone()206 )?;207208 Self::deposit_event(Event::IssuerChanged {209 old_issuer: sender,210 new_issuer,211 collection_id,212 });213214 Ok(())215 }216217 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]218 #[transactional]219 pub fn lock_collection(220 origin: OriginFor<T>,221 collection_id: RmrkCollectionId,222 ) -> DispatchResult {223 let sender = ensure_signed(origin)?;224 let cross_sender = T::CrossAccountId::from_sub(sender.clone());225226 let collection = Self::get_typed_nft_collection(227 collection_id.into(),228 misc::CollectionType::Regular229 )?;230231 Self::check_collection_owner(&collection, &cross_sender)?;232233 let token_count = collection.total_supply();234235 let mut collection = collection.into_inner();236 collection.limits.token_limit = Some(token_count);237 collection.save()?;238239 Self::deposit_event(Event::CollectionLocked { issuer: sender, collection_id });240241 Ok(())242 }243244 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]245 #[transactional]246 pub fn mint_nft(247 origin: OriginFor<T>,248 owner: T::AccountId,249 collection_id: RmrkCollectionId,250 recipient: Option<T::AccountId>,251 royalty_amount: Option<Permill>,252 metadata: RmrkString,253 ) -> DispatchResult {254 let sender = ensure_signed(origin)?;255 let sender = T::CrossAccountId::from_sub(sender);256 let cross_owner = T::CrossAccountId::from_sub(owner.clone());257258 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {259 recipient: recipient.unwrap_or_else(|| owner.clone()),260 amount261 });262263 let collection = Self::get_typed_nft_collection(264 collection_id.into(),265 misc::CollectionType::Regular,266 )?;267268 let nft_id = Self::create_nft(269 &sender,270 &cross_owner,271 &collection,272 NftType::Regular,273 [274 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,275 Self::rmrk_property(Metadata, &metadata)?,276 Self::rmrk_property(Equipped, &false)?,277 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,278 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,279 ].into_iter()280 ).map_err(|err| match err {281 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),282 err => Self::map_common_err_to_proxy(err)283 })?;284285 Self::deposit_event(Event::NftMinted {286 owner,287 collection_id,288 nft_id: nft_id.0289 });290291 Ok(())292 }293294 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]295 #[transactional]296 pub fn burn_nft(297 origin: OriginFor<T>,298 collection_id: RmrkCollectionId,299 nft_id: RmrkNftId,300 ) -> DispatchResult {301 let sender = ensure_signed(origin)?;302 let cross_sender = T::CrossAccountId::from_sub(sender.clone());303304 Self::destroy_nft(305 cross_sender,306 collection_id.into(),307 misc::CollectionType::Regular,308 nft_id.into()309 )?;310311 Self::deposit_event(Event::NFTBurned { owner: sender, nft_id });312313 Ok(())314 }315316 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]317 #[transactional]318 pub fn set_property(319 origin: OriginFor<T>,320 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,321 maybe_nft_id: Option<RmrkNftId>,322 key: RmrkKeyString,323 value: RmrkValueString,324 ) -> DispatchResult {325 let sender = ensure_signed(origin)?;326 let sender = T::CrossAccountId::from_sub(sender);327328 let collection_id: CollectionId = rmrk_collection_id.into();329330 match maybe_nft_id {331 Some(nft_id) => {332 let token_id: TokenId = nft_id.into();333334 Self::ensure_nft_owner(collection_id, token_id, &sender)?;335 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;336337 <PalletNft<T>>::set_scoped_token_property(338 collection_id,339 token_id,340 PropertyScope::Rmrk,341 Self::rmrk_property(UserProperty(key.as_slice()), &value)?342 )?;343 },344 None => {345 let collection = Self::get_typed_nft_collection(346 collection_id,347 misc::CollectionType::Regular348 )?;349350 Self::check_collection_owner(&collection, &sender)?;351352 <PalletCommon<T>>::set_scoped_collection_property(353 collection_id,354 PropertyScope::Rmrk,355 Self::rmrk_property(UserProperty(key.as_slice()), &value)?356 )?;357 }358 }359360 Self::deposit_event(361 Event::PropertySet {362 collection_id: rmrk_collection_id,363 maybe_nft_id,364 key,365 value366 }367 );368369 Ok(())370 }371 }372}373374impl<T: Config> Pallet<T> {375 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {376 let key = rmrk_key.to_key::<T>()?;377378 let scoped_key = PropertyScope::Rmrk.apply(key)379 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;380381 Ok(scoped_key)382 }383384 pub fn rmrk_property<E: Encode>(rmrk_key: RmrkProperty, value: &E) -> Result<Property, DispatchError> {385 let key = rmrk_key.to_key::<T>()?;386387 let value = value.encode()388 .try_into()389 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;390391 let property = Property {392 key,393 value,394 };395396 Ok(property)397 }398399 pub fn create_nft(400 sender: &T::CrossAccountId,401 owner: &T::CrossAccountId,402 collection: &NonfungibleHandle<T>,403 nft_type: NftType,404 properties: impl Iterator<Item=Property>405 ) -> Result<TokenId, DispatchError> {406 todo!("store nft type");407 let data = CreateNftExData {408 properties: BoundedVec::default(),409 owner: owner.clone(),410 };411412 let budget = budget::Value::new(2);413414 <PalletNft<T>>::create_item(415 collection,416 sender,417 data,418 &budget,419 )?;420421 let nft_id = <PalletNft<T>>::current_token_id(collection.id);422423 <PalletNft<T>>::set_scoped_token_properties(424 collection.id,425 nft_id,426 PropertyScope::Rmrk,427 properties428 )?;429430 Ok(nft_id)431 }432433 fn destroy_nft(434 sender: T::CrossAccountId,435 collection_id: CollectionId,436 collection_type: misc::CollectionType,437 token_id: TokenId438 ) -> DispatchResult {439 let collection = Self::get_typed_nft_collection(440 collection_id,441 collection_type442 )?;443444 <PalletNft<T>>::burn(&collection, &sender, token_id)445 .map_err(Self::map_common_err_to_proxy)?;446447 Ok(())448 }449450 fn change_collection_owner(451 collection_id: CollectionId,452 collection_type: misc::CollectionType,453 sender: T::AccountId,454 new_owner: T::AccountId,455 ) -> DispatchResult {456 let collection = Self::get_typed_nft_collection(457 collection_id,458 collection_type459 )?;460 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;461462 let mut collection = collection.into_inner();463464 collection.owner = new_owner;465 collection.save()466 }467468 fn check_collection_owner(collection: &NonfungibleHandle<T>, account: &T::CrossAccountId) -> DispatchResult {469 collection.check_is_owner(account)470 .map_err(Self::map_common_err_to_proxy)471 }472473 pub fn last_collection_idx() -> RmrkCollectionId {474 <CollectionIndex<T>>::get()475 }476477 pub fn get_nft_collection(collection_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {478 let collection = <CollectionHandle<T>>::try_get(collection_id)479 .map_err(|_| <Error<T>>::CollectionUnknown)?;480481 match collection.mode {482 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),483 _ => Err(<Error<T>>::CollectionUnknown.into())484 }485 }486487 pub fn collection_exists(collection_id: CollectionId) -> bool {488 <CollectionHandle<T>>::try_get(collection_id).is_ok()489 }490491 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {492 <TokenData<T>>::contains_key((collection_id, nft_id))493 }494495 pub fn get_collection_property(collection_id: CollectionId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {496 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)497 .get(&Self::rmrk_property_key(key)?)498 .ok_or(<Error<T>>::CollectionUnknown)?499 .clone();500501 Ok(collection_property)502 }503504 pub fn get_collection_type(collection_id: CollectionId) -> Result<misc::CollectionType, DispatchError> {505 let value = Self::get_collection_property(collection_id, CollectionType)?;506507 let mut value = value.as_slice();508509 misc::CollectionType::decode(&mut value)510 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())511 }512513 pub fn ensure_collection_type(collection_id: CollectionId, collection_type: misc::CollectionType) -> DispatchResult {514 let actual_type = Self::get_collection_type(collection_id)?;515 ensure!(actual_type == collection_type, <CommonError<T>>::NoPermission);516517 Ok(())518 }519520 pub fn get_nft_property(collection_id: CollectionId, nft_id: TokenId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {521 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))522 .get(&Self::rmrk_property_key(key)?)523 .ok_or(<Error<T>>::NoAvailableNftId)?524 .clone();525526 Ok(nft_property)527 }528529 pub fn get_nft_type(_collection_id: CollectionId, _token_id: TokenId) -> Result<NftType, DispatchError> {530 todo!("should get it from properties?")531 }532533 pub fn ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult {534 let actual_type = Self::get_nft_type(collection_id, token_id)?;535 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);536537 Ok(())538 }539540 pub fn ensure_nft_owner(541 collection_id: CollectionId,542 token_id: TokenId,543 possible_owner: &T::CrossAccountId544 ) -> DispatchResult {545 let token_data = <TokenData<T>>::get((collection_id, token_id))546 .ok_or(<Error<T>>::NoAvailableNftId)?;547548 ensure!(token_data.owner == *possible_owner, <Error<T>>::NoPermission);549550 Ok(())551 }552553 pub fn filter_user_properties<Key, Value, R, Mapper>(554 collection_id: CollectionId,555 token_id: Option<TokenId>,556 filter_keys: Option<Vec<RmrkPropertyKey>>,557 mapper: Mapper,558 ) -> Result<Vec<R>, DispatchError>559 where560 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,561 Value: Decode + Default,562 Mapper: Fn(Key, Value) -> R563 {564 filter_keys.map(|keys| {565 let properties = keys.into_iter()566 .filter_map(|key| {567 let key: Key = key.try_into().ok()?;568569 let value = match token_id {570 Some(token_id) => Self::get_nft_property(571 collection_id,572 token_id,573 UserProperty(key.as_ref())574 ),575 None => Self::get_collection_property(576 collection_id,577 UserProperty(key.as_ref())578 )579 }.ok()?.decode_or_default();580581 Some(mapper(key, value))582 })583 .collect();584585 Ok(properties)586 }).unwrap_or_else(|| {587 let properties = Self::iterate_user_properties(collection_id, token_id, mapper)?588 .collect();589590 Ok(properties)591 })592 }593594 pub fn iterate_user_properties<Key, Value, R, Mapper>(595 collection_id: CollectionId,596 token_id: Option<TokenId>,597 mapper: Mapper,598 ) -> Result<impl Iterator<Item=R>, DispatchError>599 where600 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,601 Value: Decode + Default,602 Mapper: Fn(Key, Value) -> R603 {604 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;605606 let properties = match token_id {607 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),608 None => <PalletCommon<T>>::collection_properties(collection_id)609 };610611 let properties = properties612 .into_iter()613 .filter_map(move |(key, value)| {614 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;615616 let key: Key = key.to_vec().try_into().ok()?;617 let value: Value = value.decode_or_default();618619 Some(mapper(key, value))620 });621622 Ok(properties)623 }624625 pub fn get_typed_nft_collection(626 collection_id: CollectionId,627 collection_type: misc::CollectionType628 ) -> Result<NonfungibleHandle<T>, DispatchError> {629 Self::ensure_collection_type(collection_id, collection_type)?;630631 Self::get_nft_collection(collection_id)632 }633634 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {635 map_common_err_to_proxy! {636 match err {637 NoPermission => NoPermission,638 CollectionTokenLimitExceeded => CollectionFullOrLocked,639 PublicMintingNotAllowed => NoPermission,640 TokenNotFound => NoAvailableNftId641 }642 }643 }644}