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::storage]58 #[pallet::getter(fn collection_index_map)]59 pub type CollectionIndexMap<T: Config> = 60 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6162 #[pallet::pallet]63 #[pallet::generate_store(pub(super) trait Store)]64 pub struct Pallet<T>(_);6566 #[pallet::event]67 #[pallet::generate_deposit(pub(super) fn deposit_event)]68 pub enum Event<T: Config> {69 CollectionCreated {70 issuer: T::AccountId,71 collection_id: RmrkCollectionId,72 },73 CollectionDestroyed {74 issuer: T::AccountId,75 collection_id: RmrkCollectionId,76 },77 IssuerChanged {78 old_issuer: T::AccountId,79 new_issuer: T::AccountId,80 collection_id: RmrkCollectionId,81 },82 CollectionLocked {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 NftMinted {87 owner: T::AccountId,88 collection_id: RmrkCollectionId,89 nft_id: RmrkNftId,90 },91 NFTBurned {92 owner: T::AccountId,93 nft_id: RmrkNftId,94 },95 PropertySet {96 collection_id: RmrkCollectionId,97 maybe_nft_id: Option<RmrkNftId>,98 key: RmrkKeyString,99 value: RmrkValueString,100 },101 }102103 #[pallet::error]104 pub enum Error<T> {105 106 CorruptedCollectionType,107 NftTypeEncodeError,108 RmrkPropertyKeyIsTooLong,109 RmrkPropertyValueIsTooLong,110111 112 CollectionNotEmpty,113 NoAvailableCollectionId,114 NoAvailableNftId,115 CollectionUnknown,116 NoPermission,117 CollectionFullOrLocked,118 }119120 #[pallet::call]121 impl<T: Config> Pallet<T> {122 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]123 #[transactional]124 pub fn create_collection(125 origin: OriginFor<T>,126 metadata: RmrkString,127 max: Option<u32>,128 symbol: RmrkCollectionSymbol,129 ) -> DispatchResult {130 let sender = ensure_signed(origin)?;131132 let limits = CollectionLimits {133 owner_can_transfer: Some(false),134 token_limit: max,135 ..Default::default()136 };137138 let data = CreateCollectionData {139 limits: Some(limits),140 token_prefix: symbol141 .into_inner()142 .try_into()143 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,144 ..Default::default()145 };146147 let collection_id_res =148 <PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);149150 if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {151 return Err(<Error<T>>::NoAvailableCollectionId.into());152 }153154 let unique_collection_id = collection_id_res?;155 let rmrk_collection_id = <CollectionIndex<T>>::get();156157 <CollectionIndex<T>>::mutate(|n| *n += 1);158 <CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);159160 <PalletCommon<T>>::set_scoped_collection_properties(161 unique_collection_id,162 PropertyScope::Rmrk,163 [164 Self::rmrk_property(Metadata, &metadata)?,165 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,166 ]167 .into_iter(),168 )?;169170 Self::deposit_event(Event::CollectionCreated {171 issuer: sender,172 collection_id: rmrk_collection_id,173 });174175 Ok(())176 }177178 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]179 #[transactional]180 pub fn destroy_collection(181 origin: OriginFor<T>,182 collection_id: RmrkCollectionId,183 ) -> DispatchResult {184 let sender = ensure_signed(origin)?;185 let cross_sender = T::CrossAccountId::from_sub(sender.clone());186187 let collection = Self::get_typed_nft_collection(188 Self::unique_collection_id(collection_id)?,189 misc::CollectionType::Regular,190 )?;191192 ensure!(193 collection.total_supply() == 0,194 <Error<T>>::CollectionNotEmpty195 );196197 <PalletNft<T>>::destroy_collection(collection, &cross_sender)198 .map_err(Self::map_common_err_to_proxy)?;199200 Self::deposit_event(Event::CollectionDestroyed {201 issuer: sender,202 collection_id,203 });204205 Ok(())206 }207208 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]209 #[transactional]210 pub fn change_collection_issuer(211 origin: OriginFor<T>,212 collection_id: RmrkCollectionId,213 new_issuer: <T::Lookup as StaticLookup>::Source,214 ) -> DispatchResult {215 let sender = ensure_signed(origin)?;216217 let new_issuer = T::Lookup::lookup(new_issuer)?;218219 Self::change_collection_owner(220 Self::unique_collection_id(collection_id)?,221 misc::CollectionType::Regular,222 sender.clone(),223 new_issuer.clone(),224 )?;225226 Self::deposit_event(Event::IssuerChanged {227 old_issuer: sender,228 new_issuer,229 collection_id,230 });231232 Ok(())233 }234235 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]236 #[transactional]237 pub fn lock_collection(238 origin: OriginFor<T>,239 collection_id: RmrkCollectionId,240 ) -> DispatchResult {241 let sender = ensure_signed(origin)?;242 let cross_sender = T::CrossAccountId::from_sub(sender.clone());243244 let collection = Self::get_typed_nft_collection(245 Self::unique_collection_id(collection_id)?,246 misc::CollectionType::Regular,247 )?;248249 Self::check_collection_owner(&collection, &cross_sender)?;250251 let token_count = collection.total_supply();252253 let mut collection = collection.into_inner();254 collection.limits.token_limit = Some(token_count);255 collection.save()?;256257 Self::deposit_event(Event::CollectionLocked {258 issuer: sender,259 collection_id,260 });261262 Ok(())263 }264265 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]266 #[transactional]267 pub fn mint_nft(268 origin: OriginFor<T>,269 owner: T::AccountId,270 collection_id: RmrkCollectionId,271 recipient: Option<T::AccountId>,272 royalty_amount: Option<Permill>,273 metadata: RmrkString,274 ) -> DispatchResult {275 let sender = ensure_signed(origin)?;276 let sender = T::CrossAccountId::from_sub(sender);277 let cross_owner = T::CrossAccountId::from_sub(owner.clone());278279 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {280 recipient: recipient.unwrap_or_else(|| owner.clone()),281 amount,282 });283284 let collection = Self::get_typed_nft_collection(285 Self::unique_collection_id(collection_id)?,286 misc::CollectionType::Regular,287 )?;288289 let nft_id = Self::create_nft(290 &sender,291 &cross_owner,292 &collection,293 NftType::Regular,294 [295 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,296 Self::rmrk_property(Metadata, &metadata)?,297 Self::rmrk_property(Equipped, &false)?,298 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,299 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,300 ]301 .into_iter(),302 )303 .map_err(|err| match err {304 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),305 err => Self::map_common_err_to_proxy(err),306 })?;307308 Self::deposit_event(Event::NftMinted {309 owner,310 collection_id,311 nft_id: nft_id.0,312 });313314 Ok(())315 }316317 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]318 #[transactional]319 pub fn burn_nft(320 origin: OriginFor<T>,321 collection_id: RmrkCollectionId,322 nft_id: RmrkNftId,323 ) -> DispatchResult {324 let sender = ensure_signed(origin)?;325 let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327 Self::destroy_nft(328 cross_sender,329 Self::unique_collection_id(collection_id)?,330 misc::CollectionType::Regular,331 nft_id.into(),332 )?;333334 Self::deposit_event(Event::NFTBurned {335 owner: sender,336 nft_id,337 });338339 Ok(())340 }341342 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343 #[transactional]344 pub fn set_property(345 origin: OriginFor<T>,346 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,347 maybe_nft_id: Option<RmrkNftId>,348 key: RmrkKeyString,349 value: RmrkValueString,350 ) -> DispatchResult {351 let sender = ensure_signed(origin)?;352 let sender = T::CrossAccountId::from_sub(sender);353354 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;355356 match maybe_nft_id {357 Some(nft_id) => {358 let token_id: TokenId = nft_id.into();359360 Self::ensure_nft_owner(collection_id, token_id, &sender)?;361 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;362363 <PalletNft<T>>::set_scoped_token_property(364 collection_id,365 token_id,366 PropertyScope::Rmrk,367 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,368 )?;369 }370 None => {371 let collection = Self::get_typed_nft_collection(372 collection_id,373 misc::CollectionType::Regular,374 )?;375376 Self::check_collection_owner(&collection, &sender)?;377378 <PalletCommon<T>>::set_scoped_collection_property(379 collection_id,380 PropertyScope::Rmrk,381 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,382 )?;383 }384 }385386 Self::deposit_event(Event::PropertySet {387 collection_id: rmrk_collection_id,388 maybe_nft_id,389 key,390 value,391 });392393 Ok(())394 }395 }396}397398impl<T: Config> Pallet<T> {399 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {400 let key = rmrk_key.to_key::<T>()?;401402 let scoped_key = PropertyScope::Rmrk403 .apply(key)404 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;405406 Ok(scoped_key)407 }408409 pub fn rmrk_property<E: Encode>(410 rmrk_key: RmrkProperty,411 value: &E,412 ) -> Result<Property, DispatchError> {413 let key = rmrk_key.to_key::<T>()?;414415 let value = value416 .encode()417 .try_into()418 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;419420 let property = Property { key, value };421422 Ok(property)423 }424425 pub fn create_nft(426 sender: &T::CrossAccountId,427 owner: &T::CrossAccountId,428 collection: &NonfungibleHandle<T>,429 nft_type: NftType,430 properties: impl Iterator<Item = Property>,431 ) -> Result<TokenId, DispatchError> {432 todo!("store nft type");433 let data = CreateNftExData {434 properties: BoundedVec::default(),435 owner: owner.clone(),436 };437438 let budget = budget::Value::new(2);439440 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;441442 let nft_id = <PalletNft<T>>::current_token_id(collection.id);443444 <PalletNft<T>>::set_scoped_token_properties(445 collection.id,446 nft_id,447 PropertyScope::Rmrk,448 properties,449 )?;450451 Ok(nft_id)452 }453454 fn destroy_nft(455 sender: T::CrossAccountId,456 collection_id: CollectionId,457 collection_type: misc::CollectionType,458 token_id: TokenId,459 ) -> DispatchResult {460 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;461462 <PalletNft<T>>::burn(&collection, &sender, token_id)463 .map_err(Self::map_common_err_to_proxy)?;464465 Ok(())466 }467468 fn change_collection_owner(469 collection_id: CollectionId,470 collection_type: misc::CollectionType,471 sender: T::AccountId,472 new_owner: T::AccountId,473 ) -> DispatchResult {474 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;475 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;476477 let mut collection = collection.into_inner();478479 collection.owner = new_owner;480 collection.save()481 }482483 fn check_collection_owner(484 collection: &NonfungibleHandle<T>,485 account: &T::CrossAccountId,486 ) -> DispatchResult {487 collection488 .check_is_owner(account)489 .map_err(Self::map_common_err_to_proxy)490 }491492 pub fn last_collection_idx() -> RmrkCollectionId {493 <CollectionIndex<T>>::get()494 }495496 pub fn unique_collection_id(rmrk_collection_id: RmrkCollectionId) -> Result<CollectionId, DispatchError> {497 <CollectionIndexMap<T>>::try_get(rmrk_collection_id).map_err(|_| <Error<T>>::CollectionUnknown.into())498 }499500 pub fn get_nft_collection(501 collection_id: CollectionId,502 ) -> Result<NonfungibleHandle<T>, DispatchError> {503 let collection = <CollectionHandle<T>>::try_get(collection_id)504 .map_err(|_| <Error<T>>::CollectionUnknown)?;505506 match collection.mode {507 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),508 _ => Err(<Error<T>>::CollectionUnknown.into()),509 }510 }511512 pub fn collection_exists(collection_id: CollectionId) -> bool {513 <CollectionHandle<T>>::try_get(collection_id).is_ok()514 }515516 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {517 <TokenData<T>>::contains_key((collection_id, nft_id))518 }519520 pub fn get_collection_property(521 collection_id: CollectionId,522 key: RmrkProperty,523 ) -> Result<PropertyValue, DispatchError> {524 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)525 .get(&Self::rmrk_property_key(key)?)526 .ok_or(<Error<T>>::CollectionUnknown)?527 .clone();528529 Ok(collection_property)530 }531532 pub fn get_collection_type(533 collection_id: CollectionId,534 ) -> Result<misc::CollectionType, DispatchError> {535 let value = Self::get_collection_property(collection_id, CollectionType)?;536537 let mut value = value.as_slice();538539 misc::CollectionType::decode(&mut value)540 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())541 }542543 pub fn ensure_collection_type(544 collection_id: CollectionId,545 collection_type: misc::CollectionType,546 ) -> DispatchResult {547 let actual_type = Self::get_collection_type(collection_id)?;548 ensure!(549 actual_type == collection_type,550 <CommonError<T>>::NoPermission551 );552553 Ok(())554 }555556 pub fn get_nft_property(557 collection_id: CollectionId,558 nft_id: TokenId,559 key: RmrkProperty,560 ) -> Result<PropertyValue, DispatchError> {561 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))562 .get(&Self::rmrk_property_key(key)?)563 .ok_or(<Error<T>>::NoAvailableNftId)?564 .clone();565566 Ok(nft_property)567 }568569 pub fn get_nft_type(570 _collection_id: CollectionId,571 _token_id: TokenId,572 ) -> Result<NftType, DispatchError> {573 todo!("should get it from properties?")574 }575576 pub fn ensure_nft_type(577 collection_id: CollectionId,578 token_id: TokenId,579 nft_type: NftType,580 ) -> DispatchResult {581 let actual_type = Self::get_nft_type(collection_id, token_id)?;582 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);583584 Ok(())585 }586587 pub fn ensure_nft_owner(588 collection_id: CollectionId,589 token_id: TokenId,590 possible_owner: &T::CrossAccountId,591 ) -> DispatchResult {592 let token_data =593 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;594595 ensure!(596 token_data.owner == *possible_owner,597 <Error<T>>::NoPermission598 );599600 Ok(())601 }602603 pub fn filter_user_properties<Key, Value, R, Mapper>(604 collection_id: CollectionId,605 token_id: Option<TokenId>,606 filter_keys: Option<Vec<RmrkPropertyKey>>,607 mapper: Mapper,608 ) -> Result<Vec<R>, DispatchError>609 where610 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,611 Value: Decode + Default,612 Mapper: Fn(Key, Value) -> R,613 {614 filter_keys615 .map(|keys| {616 let properties = keys617 .into_iter()618 .filter_map(|key| {619 let key: Key = key.try_into().ok()?;620621 let value = match token_id {622 Some(token_id) => Self::get_nft_property(623 collection_id,624 token_id,625 UserProperty(key.as_ref()),626 ),627 None => Self::get_collection_property(628 collection_id,629 UserProperty(key.as_ref()),630 ),631 }632 .ok()?633 .decode_or_default();634635 Some(mapper(key, value))636 })637 .collect();638639 Ok(properties)640 })641 .unwrap_or_else(|| {642 let properties =643 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();644645 Ok(properties)646 })647 }648649 pub fn iterate_user_properties<Key, Value, R, Mapper>(650 collection_id: CollectionId,651 token_id: Option<TokenId>,652 mapper: Mapper,653 ) -> Result<impl Iterator<Item = R>, DispatchError>654 where655 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,656 Value: Decode + Default,657 Mapper: Fn(Key, Value) -> R,658 {659 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;660661 let properties = match token_id {662 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),663 None => <PalletCommon<T>>::collection_properties(collection_id),664 };665666 let properties = properties.into_iter().filter_map(move |(key, value)| {667 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;668669 let key: Key = key.to_vec().try_into().ok()?;670 let value: Value = value.decode_or_default();671672 Some(mapper(key, value))673 });674675 Ok(properties)676 }677678 pub fn get_typed_nft_collection(679 collection_id: CollectionId,680 collection_type: misc::CollectionType,681 ) -> Result<NonfungibleHandle<T>, DispatchError> {682 Self::ensure_collection_type(collection_id, collection_type)?;683684 Self::get_nft_collection(collection_id)685 }686687 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {688 map_common_err_to_proxy! {689 match err {690 NoPermission => NoPermission,691 CollectionTokenLimitExceeded => CollectionFullOrLocked,692 PublicMintingNotAllowed => NoPermission,693 TokenNotFound => NoAvailableNftId694 }695 }696 }697}