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 let empty_budget = budget::Value::new(0);183 <PalletNft<T>>::destroy_collection(collection, &cross_sender, &empty_budget)184 .map_err(Self::map_common_err_to_proxy)?;185186 Self::deposit_event(Event::CollectionDestroyed { issuer: sender, collection_id });187188 Ok(())189 }190191 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]192 #[transactional]193 pub fn change_collection_issuer(194 origin: OriginFor<T>,195 collection_id: RmrkCollectionId,196 new_issuer: <T::Lookup as StaticLookup>::Source,197 ) -> DispatchResult {198 let sender = ensure_signed(origin)?;199200 let new_issuer = T::Lookup::lookup(new_issuer)?;201202 Self::change_collection_owner(203 collection_id.into(),204 misc::CollectionType::Regular,205 sender.clone(),206 new_issuer.clone()207 )?;208209 Self::deposit_event(Event::IssuerChanged {210 old_issuer: sender,211 new_issuer,212 collection_id,213 });214215 Ok(())216 }217218 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]219 #[transactional]220 pub fn lock_collection(221 origin: OriginFor<T>,222 collection_id: RmrkCollectionId,223 ) -> DispatchResult {224 let sender = ensure_signed(origin)?;225 let cross_sender = T::CrossAccountId::from_sub(sender.clone());226227 let collection = Self::get_typed_nft_collection(228 collection_id.into(),229 misc::CollectionType::Regular230 )?;231232 Self::check_collection_owner(&collection, &cross_sender)?;233234 let token_count = collection.total_supply();235236 let mut collection = collection.into_inner();237 collection.limits.token_limit = Some(token_count);238 collection.save()?;239240 Self::deposit_event(Event::CollectionLocked { issuer: sender, collection_id });241242 Ok(())243 }244245 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]246 #[transactional]247 pub fn mint_nft(248 origin: OriginFor<T>,249 owner: T::AccountId,250 collection_id: RmrkCollectionId,251 recipient: Option<T::AccountId>,252 royalty_amount: Option<Permill>,253 metadata: RmrkString,254 ) -> DispatchResult {255 let sender = ensure_signed(origin)?;256 let sender = T::CrossAccountId::from_sub(sender);257 let cross_owner = T::CrossAccountId::from_sub(owner.clone());258259 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {260 recipient: recipient.unwrap_or_else(|| owner.clone()),261 amount262 });263264 let collection = Self::get_typed_nft_collection(265 collection_id.into(),266 misc::CollectionType::Regular,267 )?;268269 let nft_id = Self::create_nft(270 &sender,271 &cross_owner,272 &collection,273 NftType::Regular,274 [275 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,276 Self::rmrk_property(Metadata, &metadata)?,277 Self::rmrk_property(Equipped, &false)?,278 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,279 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,280 ].into_iter()281 ).map_err(|err| match err {282 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),283 err => Self::map_common_err_to_proxy(err)284 })?;285286 Self::deposit_event(Event::NftMinted {287 owner,288 collection_id,289 nft_id: nft_id.0290 });291292 Ok(())293 }294295 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]296 #[transactional]297 pub fn burn_nft(298 origin: OriginFor<T>,299 collection_id: RmrkCollectionId,300 nft_id: RmrkNftId,301 ) -> DispatchResult {302 let sender = ensure_signed(origin)?;303 let cross_sender = T::CrossAccountId::from_sub(sender.clone());304305 Self::destroy_nft(306 cross_sender,307 collection_id.into(),308 misc::CollectionType::Regular,309 nft_id.into()310 )?;311312 Self::deposit_event(Event::NFTBurned { owner: sender, nft_id });313314 Ok(())315 }316317 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]318 #[transactional]319 pub fn set_property(320 origin: OriginFor<T>,321 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,322 maybe_nft_id: Option<RmrkNftId>,323 key: RmrkKeyString,324 value: RmrkValueString,325 ) -> DispatchResult {326 let sender = ensure_signed(origin)?;327 let sender = T::CrossAccountId::from_sub(sender);328329 let collection_id: CollectionId = rmrk_collection_id.into();330331 match maybe_nft_id {332 Some(nft_id) => {333 let token_id: TokenId = nft_id.into();334335 Self::ensure_nft_owner(collection_id, token_id, &sender)?;336 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;337338 <PalletNft<T>>::set_scoped_token_property(339 collection_id,340 token_id,341 PropertyScope::Rmrk,342 Self::rmrk_property(UserProperty(key.as_slice()), &value)?343 )?;344 },345 None => {346 let collection = Self::get_typed_nft_collection(347 collection_id,348 misc::CollectionType::Regular349 )?;350351 Self::check_collection_owner(&collection, &sender)?;352353 <PalletCommon<T>>::set_scoped_collection_property(354 collection_id,355 PropertyScope::Rmrk,356 Self::rmrk_property(UserProperty(key.as_slice()), &value)?357 )?;358 }359 }360361 Self::deposit_event(362 Event::PropertySet {363 collection_id: rmrk_collection_id,364 maybe_nft_id,365 key,366 value367 }368 );369370 Ok(())371 }372 }373}374375impl<T: Config> Pallet<T> {376 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {377 let key = rmrk_key.to_key::<T>()?;378379 let scoped_key = PropertyScope::Rmrk.apply(key)380 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;381382 Ok(scoped_key)383 }384385 pub fn rmrk_property<E: Encode>(rmrk_key: RmrkProperty, value: &E) -> Result<Property, DispatchError> {386 let key = rmrk_key.to_key::<T>()?;387388 let value = value.encode()389 .try_into()390 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;391392 let property = Property {393 key,394 value,395 };396397 Ok(property)398 }399400 pub fn create_nft(401 sender: &T::CrossAccountId,402 owner: &T::CrossAccountId,403 collection: &NonfungibleHandle<T>,404 nft_type: NftType,405 properties: impl Iterator<Item=Property>406 ) -> Result<TokenId, DispatchError> {407 todo!("store nft type");408 let data = CreateNftExData {409 properties: BoundedVec::default(),410 owner: owner.clone(),411 };412413 let budget = budget::Value::new(2);414415 <PalletNft<T>>::create_item(416 collection,417 sender,418 data,419 &budget,420 )?;421422 let nft_id = <PalletNft<T>>::current_token_id(collection.id);423424 <PalletNft<T>>::set_scoped_token_properties(425 collection.id,426 nft_id,427 PropertyScope::Rmrk,428 properties429 )?;430431 Ok(nft_id)432 }433434 fn destroy_nft(435 sender: T::CrossAccountId,436 collection_id: CollectionId,437 collection_type: misc::CollectionType,438 token_id: TokenId439 ) -> DispatchResult {440 let collection = Self::get_typed_nft_collection(441 collection_id,442 collection_type443 )?;444445 <PalletNft<T>>::burn(&collection, &sender, token_id)446 .map_err(Self::map_common_err_to_proxy)?;447448 Ok(())449 }450451 fn change_collection_owner(452 collection_id: CollectionId,453 collection_type: misc::CollectionType,454 sender: T::AccountId,455 new_owner: T::AccountId,456 ) -> DispatchResult {457 let collection = Self::get_typed_nft_collection(458 collection_id,459 collection_type460 )?;461 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;462463 let mut collection = collection.into_inner();464465 collection.owner = new_owner;466 collection.save()467 }468469 fn check_collection_owner(collection: &NonfungibleHandle<T>, account: &T::CrossAccountId) -> DispatchResult {470 collection.check_is_owner(account)471 .map_err(Self::map_common_err_to_proxy)472 }473474 pub fn last_collection_idx() -> RmrkCollectionId {475 <CollectionIndex<T>>::get()476 }477478 pub fn get_nft_collection(collection_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {479 let collection = <CollectionHandle<T>>::try_get(collection_id)480 .map_err(|_| <Error<T>>::CollectionUnknown)?;481482 match collection.mode {483 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),484 _ => Err(<Error<T>>::CollectionUnknown.into())485 }486 }487488 pub fn collection_exists(collection_id: CollectionId) -> bool {489 <CollectionHandle<T>>::try_get(collection_id).is_ok()490 }491492 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {493 <TokenData<T>>::contains_key((collection_id, nft_id))494 }495496 pub fn get_collection_property(collection_id: CollectionId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {497 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)498 .get(&Self::rmrk_property_key(key)?)499 .ok_or(<Error<T>>::CollectionUnknown)?500 .clone();501502 Ok(collection_property)503 }504505 pub fn get_collection_type(collection_id: CollectionId) -> Result<misc::CollectionType, DispatchError> {506 let value = Self::get_collection_property(collection_id, CollectionType)?;507508 let mut value = value.as_slice();509510 misc::CollectionType::decode(&mut value)511 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())512 }513514 pub fn ensure_collection_type(collection_id: CollectionId, collection_type: misc::CollectionType) -> DispatchResult {515 let actual_type = Self::get_collection_type(collection_id)?;516 ensure!(actual_type == collection_type, <CommonError<T>>::NoPermission);517518 Ok(())519 }520521 pub fn get_nft_property(collection_id: CollectionId, nft_id: TokenId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {522 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))523 .get(&Self::rmrk_property_key(key)?)524 .ok_or(<Error<T>>::NoAvailableNftId)?525 .clone();526527 Ok(nft_property)528 }529530 pub fn get_nft_type(_collection_id: CollectionId, _token_id: TokenId) -> Result<NftType, DispatchError> {531 todo!("should get it from properties?")532 }533534 pub fn ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult {535 let actual_type = Self::get_nft_type(collection_id, token_id)?;536 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);537538 Ok(())539 }540541 pub fn ensure_nft_owner(542 collection_id: CollectionId,543 token_id: TokenId,544 possible_owner: &T::CrossAccountId545 ) -> DispatchResult {546 let token_data = <TokenData<T>>::get((collection_id, token_id))547 .ok_or(<Error<T>>::NoAvailableNftId)?;548549 ensure!(token_data.owner == *possible_owner, <Error<T>>::NoPermission);550551 Ok(())552 }553554 pub fn filter_user_properties<Key, Value, R, Mapper>(555 collection_id: CollectionId,556 token_id: Option<TokenId>,557 filter_keys: Option<Vec<RmrkPropertyKey>>,558 mapper: Mapper,559 ) -> Result<Vec<R>, DispatchError>560 where561 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,562 Value: Decode + Default,563 Mapper: Fn(Key, Value) -> R564 {565 filter_keys.map(|keys| {566 let properties = keys.into_iter()567 .filter_map(|key| {568 let key: Key = key.try_into().ok()?;569570 let value = match token_id {571 Some(token_id) => Self::get_nft_property(572 collection_id,573 token_id,574 UserProperty(key.as_ref())575 ),576 None => Self::get_collection_property(577 collection_id,578 UserProperty(key.as_ref())579 )580 }.ok()?.decode_or_default();581582 Some(mapper(key, value))583 })584 .collect();585586 Ok(properties)587 }).unwrap_or_else(|| {588 let properties = Self::iterate_user_properties(collection_id, token_id, mapper)?589 .collect();590591 Ok(properties)592 })593 }594595 pub fn iterate_user_properties<Key, Value, R, Mapper>(596 collection_id: CollectionId,597 token_id: Option<TokenId>,598 mapper: Mapper,599 ) -> Result<impl Iterator<Item=R>, DispatchError>600 where601 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,602 Value: Decode + Default,603 Mapper: Fn(Key, Value) -> R604 {605 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;606607 let properties = match token_id {608 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),609 None => <PalletCommon<T>>::collection_properties(collection_id)610 };611612 let properties = properties613 .into_iter()614 .filter_map(move |(key, value)| {615 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;616617 let key: Key = key.to_vec().try_into().ok()?;618 let value: Value = value.decode_or_default();619620 Some(mapper(key, value))621 });622623 Ok(properties)624 }625626 pub fn get_typed_nft_collection(627 collection_id: CollectionId,628 collection_type: misc::CollectionType629 ) -> Result<NonfungibleHandle<T>, DispatchError> {630 Self::ensure_collection_type(collection_id, collection_type)?;631632 Self::get_nft_collection(collection_id)633 }634635 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {636 map_common_err_to_proxy! {637 match err {638 NoPermission => NoPermission,639 CollectionTokenLimitExceeded => CollectionFullOrLocked,640 PublicMintingNotAllowed => NoPermission,641 TokenNotFound => NoAvailableNftId642 }643 }644 }645}