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 =143 <PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);144145 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {146 return Err(<Error<T>>::NoAvailableCollectionId.into());147 }148149 let collection_id = collection_id_res?;150151 <PalletCommon<T>>::set_scoped_collection_properties(152 collection_id,153 PropertyScope::Rmrk,154 [155 Self::rmrk_property(Metadata, &metadata)?,156 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,157 ]158 .into_iter(),159 )?;160161 <CollectionIndex<T>>::mutate(|n| *n += 1);162163 Self::deposit_event(Event::CollectionCreated {164 issuer: sender,165 collection_id: collection_id.0,166 });167168 Ok(())169 }170171 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]172 #[transactional]173 pub fn destroy_collection(174 origin: OriginFor<T>,175 collection_id: RmrkCollectionId,176 ) -> DispatchResult {177 let sender = ensure_signed(origin)?;178 let cross_sender = T::CrossAccountId::from_sub(sender.clone());179180 let unique_collection_id = collection_id.into();181182 let collection = Self::get_typed_nft_collection(183 unique_collection_id,184 misc::CollectionType::Regular,185 )?;186187 ensure!(188 collection.total_supply() == 0,189 <Error<T>>::CollectionNotEmpty190 );191192 <PalletNft<T>>::destroy_collection(collection, &cross_sender)193 .map_err(Self::map_common_err_to_proxy)?;194195 Self::deposit_event(Event::CollectionDestroyed {196 issuer: sender,197 collection_id,198 });199200 Ok(())201 }202203 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]204 #[transactional]205 pub fn change_collection_issuer(206 origin: OriginFor<T>,207 collection_id: RmrkCollectionId,208 new_issuer: <T::Lookup as StaticLookup>::Source,209 ) -> DispatchResult {210 let sender = ensure_signed(origin)?;211212 let new_issuer = T::Lookup::lookup(new_issuer)?;213214 Self::change_collection_owner(215 collection_id.into(),216 misc::CollectionType::Regular,217 sender.clone(),218 new_issuer.clone(),219 )?;220221 Self::deposit_event(Event::IssuerChanged {222 old_issuer: sender,223 new_issuer,224 collection_id,225 });226227 Ok(())228 }229230 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]231 #[transactional]232 pub fn lock_collection(233 origin: OriginFor<T>,234 collection_id: RmrkCollectionId,235 ) -> DispatchResult {236 let sender = ensure_signed(origin)?;237 let cross_sender = T::CrossAccountId::from_sub(sender.clone());238239 let collection = Self::get_typed_nft_collection(240 collection_id.into(),241 misc::CollectionType::Regular,242 )?;243244 Self::check_collection_owner(&collection, &cross_sender)?;245246 let token_count = collection.total_supply();247248 let mut collection = collection.into_inner();249 collection.limits.token_limit = Some(token_count);250 collection.save()?;251252 Self::deposit_event(Event::CollectionLocked {253 issuer: sender,254 collection_id,255 });256257 Ok(())258 }259260 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]261 #[transactional]262 pub fn mint_nft(263 origin: OriginFor<T>,264 owner: T::AccountId,265 collection_id: RmrkCollectionId,266 recipient: Option<T::AccountId>,267 royalty_amount: Option<Permill>,268 metadata: RmrkString,269 ) -> DispatchResult {270 let sender = ensure_signed(origin)?;271 let sender = T::CrossAccountId::from_sub(sender);272 let cross_owner = T::CrossAccountId::from_sub(owner.clone());273274 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {275 recipient: recipient.unwrap_or_else(|| owner.clone()),276 amount,277 });278279 let collection = Self::get_typed_nft_collection(280 collection_id.into(),281 misc::CollectionType::Regular,282 )?;283284 let nft_id = Self::create_nft(285 &sender,286 &cross_owner,287 &collection,288 NftType::Regular,289 [290 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,291 Self::rmrk_property(Metadata, &metadata)?,292 Self::rmrk_property(Equipped, &false)?,293 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,294 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,295 ]296 .into_iter(),297 )298 .map_err(|err| match err {299 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),300 err => Self::map_common_err_to_proxy(err),301 })?;302303 Self::deposit_event(Event::NftMinted {304 owner,305 collection_id,306 nft_id: nft_id.0,307 });308309 Ok(())310 }311312 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]313 #[transactional]314 pub fn burn_nft(315 origin: OriginFor<T>,316 collection_id: RmrkCollectionId,317 nft_id: RmrkNftId,318 ) -> DispatchResult {319 let sender = ensure_signed(origin)?;320 let cross_sender = T::CrossAccountId::from_sub(sender.clone());321322 Self::destroy_nft(323 cross_sender,324 collection_id.into(),325 misc::CollectionType::Regular,326 nft_id.into(),327 )?;328329 Self::deposit_event(Event::NFTBurned {330 owner: sender,331 nft_id,332 });333334 Ok(())335 }336337 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]338 #[transactional]339 pub fn set_property(340 origin: OriginFor<T>,341 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,342 maybe_nft_id: Option<RmrkNftId>,343 key: RmrkKeyString,344 value: RmrkValueString,345 ) -> DispatchResult {346 let sender = ensure_signed(origin)?;347 let sender = T::CrossAccountId::from_sub(sender);348349 let collection_id: CollectionId = rmrk_collection_id.into();350351 match maybe_nft_id {352 Some(nft_id) => {353 let token_id: TokenId = nft_id.into();354355 Self::ensure_nft_owner(collection_id, token_id, &sender)?;356 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;357358 <PalletNft<T>>::set_scoped_token_property(359 collection_id,360 token_id,361 PropertyScope::Rmrk,362 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,363 )?;364 }365 None => {366 let collection = Self::get_typed_nft_collection(367 collection_id,368 misc::CollectionType::Regular,369 )?;370371 Self::check_collection_owner(&collection, &sender)?;372373 <PalletCommon<T>>::set_scoped_collection_property(374 collection_id,375 PropertyScope::Rmrk,376 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,377 )?;378 }379 }380381 Self::deposit_event(Event::PropertySet {382 collection_id: rmrk_collection_id,383 maybe_nft_id,384 key,385 value,386 });387388 Ok(())389 }390 }391}392393impl<T: Config> Pallet<T> {394 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {395 let key = rmrk_key.to_key::<T>()?;396397 let scoped_key = PropertyScope::Rmrk398 .apply(key)399 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;400401 Ok(scoped_key)402 }403404 pub fn rmrk_property<E: Encode>(405 rmrk_key: RmrkProperty,406 value: &E,407 ) -> Result<Property, DispatchError> {408 let key = rmrk_key.to_key::<T>()?;409410 let value = value411 .encode()412 .try_into()413 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;414415 let property = Property { key, value };416417 Ok(property)418 }419420 pub fn create_nft(421 sender: &T::CrossAccountId,422 owner: &T::CrossAccountId,423 collection: &NonfungibleHandle<T>,424 nft_type: NftType,425 properties: impl Iterator<Item = Property>,426 ) -> Result<TokenId, DispatchError> {427 todo!("store nft type");428 let data = CreateNftExData {429 properties: BoundedVec::default(),430 owner: owner.clone(),431 };432433 let budget = budget::Value::new(2);434435 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;436437 let nft_id = <PalletNft<T>>::current_token_id(collection.id);438439 <PalletNft<T>>::set_scoped_token_properties(440 collection.id,441 nft_id,442 PropertyScope::Rmrk,443 properties,444 )?;445446 Ok(nft_id)447 }448449 fn destroy_nft(450 sender: T::CrossAccountId,451 collection_id: CollectionId,452 collection_type: misc::CollectionType,453 token_id: TokenId,454 ) -> DispatchResult {455 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;456457 <PalletNft<T>>::burn(&collection, &sender, token_id)458 .map_err(Self::map_common_err_to_proxy)?;459460 Ok(())461 }462463 fn change_collection_owner(464 collection_id: CollectionId,465 collection_type: misc::CollectionType,466 sender: T::AccountId,467 new_owner: T::AccountId,468 ) -> DispatchResult {469 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;470 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;471472 let mut collection = collection.into_inner();473474 collection.owner = new_owner;475 collection.save()476 }477478 fn check_collection_owner(479 collection: &NonfungibleHandle<T>,480 account: &T::CrossAccountId,481 ) -> DispatchResult {482 collection483 .check_is_owner(account)484 .map_err(Self::map_common_err_to_proxy)485 }486487 pub fn last_collection_idx() -> RmrkCollectionId {488 <CollectionIndex<T>>::get()489 }490491 pub fn get_nft_collection(492 collection_id: CollectionId,493 ) -> Result<NonfungibleHandle<T>, DispatchError> {494 let collection = <CollectionHandle<T>>::try_get(collection_id)495 .map_err(|_| <Error<T>>::CollectionUnknown)?;496497 match collection.mode {498 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),499 _ => Err(<Error<T>>::CollectionUnknown.into()),500 }501 }502503 pub fn collection_exists(collection_id: CollectionId) -> bool {504 <CollectionHandle<T>>::try_get(collection_id).is_ok()505 }506507 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {508 <TokenData<T>>::contains_key((collection_id, nft_id))509 }510511 pub fn get_collection_property(512 collection_id: CollectionId,513 key: RmrkProperty,514 ) -> Result<PropertyValue, DispatchError> {515 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)516 .get(&Self::rmrk_property_key(key)?)517 .ok_or(<Error<T>>::CollectionUnknown)?518 .clone();519520 Ok(collection_property)521 }522523 pub fn get_collection_type(524 collection_id: CollectionId,525 ) -> Result<misc::CollectionType, DispatchError> {526 let value = Self::get_collection_property(collection_id, CollectionType)?;527528 let mut value = value.as_slice();529530 misc::CollectionType::decode(&mut value)531 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())532 }533534 pub fn ensure_collection_type(535 collection_id: CollectionId,536 collection_type: misc::CollectionType,537 ) -> DispatchResult {538 let actual_type = Self::get_collection_type(collection_id)?;539 ensure!(540 actual_type == collection_type,541 <CommonError<T>>::NoPermission542 );543544 Ok(())545 }546547 pub fn get_nft_property(548 collection_id: CollectionId,549 nft_id: TokenId,550 key: RmrkProperty,551 ) -> Result<PropertyValue, DispatchError> {552 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))553 .get(&Self::rmrk_property_key(key)?)554 .ok_or(<Error<T>>::NoAvailableNftId)?555 .clone();556557 Ok(nft_property)558 }559560 pub fn get_nft_type(561 _collection_id: CollectionId,562 _token_id: TokenId,563 ) -> Result<NftType, DispatchError> {564 todo!("should get it from properties?")565 }566567 pub fn ensure_nft_type(568 collection_id: CollectionId,569 token_id: TokenId,570 nft_type: NftType,571 ) -> DispatchResult {572 let actual_type = Self::get_nft_type(collection_id, token_id)?;573 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);574575 Ok(())576 }577578 pub fn ensure_nft_owner(579 collection_id: CollectionId,580 token_id: TokenId,581 possible_owner: &T::CrossAccountId,582 ) -> DispatchResult {583 let token_data =584 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;585586 ensure!(587 token_data.owner == *possible_owner,588 <Error<T>>::NoPermission589 );590591 Ok(())592 }593594 pub fn filter_user_properties<Key, Value, R, Mapper>(595 collection_id: CollectionId,596 token_id: Option<TokenId>,597 filter_keys: Option<Vec<RmrkPropertyKey>>,598 mapper: Mapper,599 ) -> Result<Vec<R>, DispatchError>600 where601 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,602 Value: Decode + Default,603 Mapper: Fn(Key, Value) -> R,604 {605 filter_keys606 .map(|keys| {607 let properties = keys608 .into_iter()609 .filter_map(|key| {610 let key: Key = key.try_into().ok()?;611612 let value = match token_id {613 Some(token_id) => Self::get_nft_property(614 collection_id,615 token_id,616 UserProperty(key.as_ref()),617 ),618 None => Self::get_collection_property(619 collection_id,620 UserProperty(key.as_ref()),621 ),622 }623 .ok()?624 .decode_or_default();625626 Some(mapper(key, value))627 })628 .collect();629630 Ok(properties)631 })632 .unwrap_or_else(|| {633 let properties =634 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();635636 Ok(properties)637 })638 }639640 pub fn iterate_user_properties<Key, Value, R, Mapper>(641 collection_id: CollectionId,642 token_id: Option<TokenId>,643 mapper: Mapper,644 ) -> Result<impl Iterator<Item = R>, DispatchError>645 where646 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,647 Value: Decode + Default,648 Mapper: Fn(Key, Value) -> R,649 {650 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;651652 let properties = match token_id {653 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),654 None => <PalletCommon<T>>::collection_properties(collection_id),655 };656657 let properties = properties.into_iter().filter_map(move |(key, value)| {658 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;659660 let key: Key = key.to_vec().try_into().ok()?;661 let value: Value = value.decode_or_default();662663 Some(mapper(key, value))664 });665666 Ok(properties)667 }668669 pub fn get_typed_nft_collection(670 collection_id: CollectionId,671 collection_type: misc::CollectionType,672 ) -> Result<NonfungibleHandle<T>, DispatchError> {673 Self::ensure_collection_type(collection_id, collection_type)?;674675 Self::get_nft_collection(collection_id)676 }677678 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {679 map_common_err_to_proxy! {680 match err {681 NoPermission => NoPermission,682 CollectionTokenLimitExceeded => CollectionFullOrLocked,683 PublicMintingNotAllowed => NoPermission,684 TokenNotFound => NoAvailableNftId685 }686 }687 }688}