difftreelog
feat(rmrk-proxy) delete-resource
in: master
1 file changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142#[frame_support::pallet]43pub mod pallet {44 use super::*;45 use pallet_evm::account;4647 #[pallet::config]48 pub trait Config:49 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50 {51 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52 }5354 #[pallet::storage]55 #[pallet::getter(fn collection_index)]56 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758 #[pallet::storage]59 #[pallet::getter(fn collection_index_map)]60 pub type CollectionIndexMap<T: Config> =61 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263 #[pallet::pallet]64 #[pallet::generate_store(pub(super) trait Store)]65 pub struct Pallet<T>(_);6667 #[pallet::event]68 #[pallet::generate_deposit(pub(super) fn deposit_event)]69 pub enum Event<T: Config> {70 CollectionCreated {71 issuer: T::AccountId,72 collection_id: RmrkCollectionId,73 },74 CollectionDestroyed {75 issuer: T::AccountId,76 collection_id: RmrkCollectionId,77 },78 IssuerChanged {79 old_issuer: T::AccountId,80 new_issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 CollectionLocked {84 issuer: T::AccountId,85 collection_id: RmrkCollectionId,86 },87 NftMinted {88 owner: T::AccountId,89 collection_id: RmrkCollectionId,90 nft_id: RmrkNftId,91 },92 NFTBurned {93 owner: T::AccountId,94 nft_id: RmrkNftId,95 },96 PropertySet {97 collection_id: RmrkCollectionId,98 maybe_nft_id: Option<RmrkNftId>,99 key: RmrkKeyString,100 value: RmrkValueString,101 },102 ResourceAdded {103 nft_id: RmrkNftId,104 resource_id: RmrkResourceId,105 },106 }107108 #[pallet::error]109 pub enum Error<T> {110 /* Unique-specific events */111 CorruptedCollectionType,112 NftTypeEncodeError,113 RmrkPropertyKeyIsTooLong,114 RmrkPropertyValueIsTooLong,115116 /* RMRK compatible events */117 CollectionNotEmpty,118 NoAvailableCollectionId,119 NoAvailableNftId,120 CollectionUnknown,121 NoPermission,122 CollectionFullOrLocked,123 // todo add resource errors?124 }125126 #[pallet::call]127 impl<T: Config> Pallet<T> {128 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]129 #[transactional]130 pub fn create_collection(131 origin: OriginFor<T>,132 metadata: RmrkString,133 max: Option<u32>,134 symbol: RmrkCollectionSymbol,135 ) -> DispatchResult {136 let sender = ensure_signed(origin)?;137138 let limits = CollectionLimits {139 owner_can_transfer: Some(false),140 token_limit: max,141 ..Default::default()142 };143144 let data = CreateCollectionData {145 limits: Some(limits),146 token_prefix: symbol147 .into_inner()148 .try_into()149 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,150 ..Default::default()151 };152153 <CollectionIndex<T>>::mutate(|n| *n += 1);154155 let unique_collection_id = Self::init_collection(156 T::CrossAccountId::from_sub(sender.clone()),157 data,158 [159 Self::rmrk_property(Metadata, &metadata)?,160 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,161 ]162 .into_iter(),163 )?; //collection_id_res?;164 let rmrk_collection_id = <CollectionIndex<T>>::get();165166 <CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);167168 Self::deposit_event(Event::CollectionCreated {169 issuer: sender,170 collection_id: rmrk_collection_id,171 });172173 Ok(())174 }175176 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]177 #[transactional]178 pub fn destroy_collection(179 origin: OriginFor<T>,180 collection_id: RmrkCollectionId,181 ) -> DispatchResult {182 let sender = ensure_signed(origin)?;183 let cross_sender = T::CrossAccountId::from_sub(sender.clone());184185 let collection = Self::get_typed_nft_collection(186 Self::unique_collection_id(collection_id)?,187 misc::CollectionType::Regular,188 )?;189190 ensure!(191 collection.total_supply() == 0,192 <Error<T>>::CollectionNotEmpty193 );194195 <PalletNft<T>>::destroy_collection(collection, &cross_sender)196 .map_err(Self::map_common_err_to_proxy)?;197198 Self::deposit_event(Event::CollectionDestroyed {199 issuer: sender,200 collection_id,201 });202203 Ok(())204 }205206 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]207 #[transactional]208 pub fn change_collection_issuer(209 origin: OriginFor<T>,210 collection_id: RmrkCollectionId,211 new_issuer: <T::Lookup as StaticLookup>::Source,212 ) -> DispatchResult {213 let sender = ensure_signed(origin)?;214215 let new_issuer = T::Lookup::lookup(new_issuer)?;216217 Self::change_collection_owner(218 Self::unique_collection_id(collection_id)?,219 misc::CollectionType::Regular,220 sender.clone(),221 new_issuer.clone(),222 )?;223224 Self::deposit_event(Event::IssuerChanged {225 old_issuer: sender,226 new_issuer,227 collection_id,228 });229230 Ok(())231 }232233 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]234 #[transactional]235 pub fn lock_collection(236 origin: OriginFor<T>,237 collection_id: RmrkCollectionId,238 ) -> DispatchResult {239 let sender = ensure_signed(origin)?;240 let cross_sender = T::CrossAccountId::from_sub(sender.clone());241242 let collection = Self::get_typed_nft_collection(243 Self::unique_collection_id(collection_id)?,244 misc::CollectionType::Regular,245 )?;246247 Self::check_collection_owner(&collection, &cross_sender)?;248249 let token_count = collection.total_supply();250251 let mut collection = collection.into_inner();252 collection.limits.token_limit = Some(token_count);253 collection.save()?;254255 Self::deposit_event(Event::CollectionLocked {256 issuer: sender,257 collection_id,258 });259260 Ok(())261 }262263 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]264 #[transactional]265 pub fn mint_nft(266 origin: OriginFor<T>,267 owner: T::AccountId,268 collection_id: RmrkCollectionId,269 recipient: Option<T::AccountId>,270 royalty_amount: Option<Permill>,271 metadata: RmrkString,272 ) -> DispatchResult {273 let sender = ensure_signed(origin)?;274 let sender = T::CrossAccountId::from_sub(sender);275 let cross_owner = T::CrossAccountId::from_sub(owner.clone());276277 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {278 recipient: recipient.unwrap_or_else(|| owner.clone()),279 amount,280 });281282 let collection = Self::get_typed_nft_collection(283 Self::unique_collection_id(collection_id)?,284 misc::CollectionType::Regular,285 )?;286287 let nft_id = Self::create_nft(288 &sender,289 &cross_owner,290 &collection,291 [292 Self::rmrk_property(TokenType, &NftType::Regular)?,293 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,294 Self::rmrk_property(Metadata, &metadata)?,295 Self::rmrk_property(Equipped, &false)?,296 Self::rmrk_property(297 ResourceCollection,298 &Self::init_collection(299 sender.clone(),300 CreateCollectionData {301 ..Default::default()302 },303 [Self::rmrk_property(304 CollectionType,305 &misc::CollectionType::Resource,306 )?]307 .into_iter(),308 )?,309 )?, // todo possibly add limits to the collection if rmrk warrants them310 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?, // todo create resource priorities?311 ]312 .into_iter(),313 )314 .map_err(|err| match err {315 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),316 err => Self::map_common_err_to_proxy(err),317 })?;318319 Self::deposit_event(Event::NftMinted {320 owner,321 collection_id,322 nft_id: nft_id.0,323 });324325 Ok(())326 }327328 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]329 #[transactional]330 pub fn burn_nft(331 origin: OriginFor<T>,332 collection_id: RmrkCollectionId,333 nft_id: RmrkNftId,334 ) -> DispatchResult {335 let sender = ensure_signed(origin)?;336 let cross_sender = T::CrossAccountId::from_sub(sender.clone());337338 Self::destroy_nft(339 cross_sender,340 Self::unique_collection_id(collection_id)?,341 misc::CollectionType::Regular,342 nft_id.into(),343 )?;344345 Self::deposit_event(Event::NFTBurned {346 owner: sender,347 nft_id,348 });349350 Ok(())351 }352353 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]354 #[transactional]355 pub fn set_property(356 origin: OriginFor<T>,357 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,358 maybe_nft_id: Option<RmrkNftId>,359 key: RmrkKeyString,360 value: RmrkValueString,361 ) -> DispatchResult {362 let sender = ensure_signed(origin)?;363 let sender = T::CrossAccountId::from_sub(sender);364365 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;366367 match maybe_nft_id {368 Some(nft_id) => {369 let token_id: TokenId = nft_id.into();370371 Self::ensure_nft_owner(collection_id, token_id, &sender)?;372 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;373374 <PalletNft<T>>::set_scoped_token_property(375 collection_id,376 token_id,377 PropertyScope::Rmrk,378 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,379 )?;380 }381 None => {382 let collection = Self::get_typed_nft_collection(383 collection_id,384 misc::CollectionType::Regular,385 )?;386387 Self::check_collection_owner(&collection, &sender)?;388389 <PalletCommon<T>>::set_scoped_collection_property(390 collection_id,391 PropertyScope::Rmrk,392 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,393 )?;394 }395 }396397 Self::deposit_event(Event::PropertySet {398 collection_id: rmrk_collection_id,399 maybe_nft_id,400 key,401 value,402 });403404 Ok(())405 }406407 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]408 #[transactional]409 pub fn add_basic_resource(410 origin: OriginFor<T>,411 collection_id: RmrkCollectionId,412 nft_id: RmrkNftId,413 resource: RmrkBasicResource,414 ) -> DispatchResult {415 let sender = ensure_signed(origin.clone())?;416417 let resource_id = Self::resource_add(418 sender,419 Self::unique_collection_id(collection_id)?,420 nft_id.into(),421 [422 Self::rmrk_property(TokenType, &NftType::Resource)?,423 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,424 Self::rmrk_property(Src, &resource.src)?,425 Self::rmrk_property(Metadata, &resource.metadata)?,426 Self::rmrk_property(License, &resource.license)?,427 Self::rmrk_property(Thumb, &resource.thumb)?,428 ]429 .into_iter(),430 )?;431432 Self::deposit_event(Event::ResourceAdded {433 nft_id,434 resource_id,435 });436 Ok(())437 }438439 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]440 #[transactional]441 pub fn add_composable_resource(442 origin: OriginFor<T>,443 collection_id: RmrkCollectionId,444 nft_id: RmrkNftId,445 _resource_id: RmrkBoundedResource,446 resource: RmrkComposableResource,447 ) -> DispatchResult {448 let sender = ensure_signed(origin.clone())?;449450 let resource_id = Self::resource_add(451 sender,452 Self::unique_collection_id(collection_id)?,453 nft_id.into(),454 [455 Self::rmrk_property(TokenType, &NftType::Resource)?,456 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,457 Self::rmrk_property(Parts, &resource.parts)?,458 Self::rmrk_property(Base, &resource.base)?,459 Self::rmrk_property(Src, &resource.src)?,460 Self::rmrk_property(Metadata, &resource.metadata)?,461 Self::rmrk_property(License, &resource.license)?,462 Self::rmrk_property(Thumb, &resource.thumb)?,463 ]464 .into_iter(),465 )?;466467 Self::deposit_event(Event::ResourceAdded {468 nft_id,469 resource_id,470 });471 Ok(())472 }473474 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]475 #[transactional]476 pub fn add_slot_resource(477 origin: OriginFor<T>,478 collection_id: RmrkCollectionId,479 nft_id: RmrkNftId,480 resource: RmrkSlotResource,481 ) -> DispatchResult {482 let sender = ensure_signed(origin.clone())?;483484 let resource_id = Self::resource_add(485 sender,486 Self::unique_collection_id(collection_id)?,487 nft_id.into(),488 [489 Self::rmrk_property(TokenType, &NftType::Resource)?,490 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,491 Self::rmrk_property(Base, &resource.base)?,492 Self::rmrk_property(Src, &resource.src)?,493 Self::rmrk_property(Metadata, &resource.metadata)?,494 Self::rmrk_property(Slot, &resource.slot)?,495 Self::rmrk_property(License, &resource.license)?,496 Self::rmrk_property(Thumb, &resource.thumb)?,497 ]498 .into_iter(),499 )?;500501 Self::deposit_event(Event::ResourceAdded {502 nft_id,503 resource_id,504 });505 Ok(())506 }507 }508}509510impl<T: Config> Pallet<T> {511 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {512 let key = rmrk_key.to_key::<T>()?;513514 let scoped_key = PropertyScope::Rmrk515 .apply(key)516 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;517518 Ok(scoped_key)519 }520521 pub fn rmrk_property<E: Encode>(522 // todo think about renaming this523 rmrk_key: RmrkProperty,524 value: &E,525 ) -> Result<Property, DispatchError> {526 let key = rmrk_key.to_key::<T>()?;527528 let value = value529 .encode()530 .try_into()531 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;532533 let property = Property { key, value };534535 Ok(property)536 }537538 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {539 vec.decode()540 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())541 }542543 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>544 where545 BoundedVec<u8, S>: TryFrom<Vec<u8>>,546 {547 vec.rebind()548 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())549 }550551 fn init_collection(552 sender: T::CrossAccountId,553 data: CreateCollectionData<T::AccountId>,554 properties: impl Iterator<Item = Property>,555 ) -> Result<CollectionId, DispatchError> {556 let collection_id = <PalletNft<T>>::init_collection(sender, data);557558 if let Err(DispatchError::Arithmetic(_)) = &collection_id {559 return Err(<Error<T>>::NoAvailableCollectionId.into());560 }561562 <PalletCommon<T>>::set_scoped_collection_properties(563 collection_id?,564 PropertyScope::Rmrk,565 properties,566 )?;567568 collection_id569 }570571 pub fn create_nft(572 sender: &T::CrossAccountId,573 owner: &T::CrossAccountId,574 collection: &NonfungibleHandle<T>,575 properties: impl Iterator<Item = Property>,576 ) -> Result<TokenId, DispatchError> {577 let data = CreateNftExData {578 properties: BoundedVec::default(),579 owner: owner.clone(),580 };581582 let budget = budget::Value::new(2);583584 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;585586 let nft_id = <PalletNft<T>>::current_token_id(collection.id);587588 <PalletNft<T>>::set_scoped_token_properties(589 collection.id,590 nft_id,591 PropertyScope::Rmrk,592 properties,593 )?;594595 Ok(nft_id)596 }597598 fn destroy_nft(599 sender: T::CrossAccountId,600 collection_id: CollectionId,601 collection_type: misc::CollectionType,602 token_id: TokenId,603 ) -> DispatchResult {604 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;605606 <PalletNft<T>>::burn(&collection, &sender, token_id)607 .map_err(Self::map_common_err_to_proxy)?;608609 Ok(())610 }611612 fn resource_add(613 sender: T::AccountId,614 collection_id: CollectionId,615 token_id: TokenId,616 resource_properties: impl Iterator<Item = Property>,617 ) -> Result<RmrkResourceId, DispatchError> {618 let collection =619 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;620 ensure!(collection.owner == sender, Error::<T>::NoPermission);621622 // Check NFT lock status // todo depends on market, maybe later623 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);624625 let sender = T::CrossAccountId::from_sub(sender);626 let budget = budget::Value::new(10);627 let pending = <PalletStructure<T>>::check_indirectly_owned(628 sender.clone(),629 collection_id,630 token_id,631 None,632 &budget,633 )?;634635 let resource_collection_id: CollectionId =636 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;637 let resource_collection =638 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;639640 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them641642 let resource_id = Self::create_nft(643 &sender, // todo owner of the nft?644 &sender,645 &resource_collection,646 resource_properties.chain(647 [648 Self::rmrk_property(PendingResourceAccept, &pending)?,649 Self::rmrk_property(PendingResourceRemoval, &false)?,650 ]651 .into_iter(),652 ),653 )654 .map_err(|err| match err {655 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),656 err => Self::map_common_err_to_proxy(err),657 })?;658659 Ok(resource_id.0)660 }661662 fn change_collection_owner(663 collection_id: CollectionId,664 collection_type: misc::CollectionType,665 sender: T::AccountId,666 new_owner: T::AccountId,667 ) -> DispatchResult {668 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;669 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;670671 let mut collection = collection.into_inner();672673 collection.owner = new_owner;674 collection.save()675 }676677 fn check_collection_owner(678 collection: &NonfungibleHandle<T>,679 account: &T::CrossAccountId,680 ) -> DispatchResult {681 collection682 .check_is_owner(account)683 .map_err(Self::map_common_err_to_proxy)684 }685686 pub fn last_collection_idx() -> RmrkCollectionId {687 <CollectionIndex<T>>::get()688 }689690 pub fn unique_collection_id(691 rmrk_collection_id: RmrkCollectionId,692 ) -> Result<CollectionId, DispatchError> {693 <CollectionIndexMap<T>>::try_get(rmrk_collection_id)694 .map_err(|_| <Error<T>>::CollectionUnknown.into())695 }696697 pub fn get_nft_collection(698 collection_id: CollectionId,699 ) -> Result<NonfungibleHandle<T>, DispatchError> {700 let collection = <CollectionHandle<T>>::try_get(collection_id)701 .map_err(|_| <Error<T>>::CollectionUnknown)?;702703 match collection.mode {704 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),705 _ => Err(<Error<T>>::CollectionUnknown.into()),706 }707 }708709 pub fn collection_exists(collection_id: CollectionId) -> bool {710 <CollectionHandle<T>>::try_get(collection_id).is_ok()711 }712713 pub fn get_collection_property(714 collection_id: CollectionId,715 key: RmrkProperty,716 ) -> Result<PropertyValue, DispatchError> {717 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)718 .get(&Self::rmrk_property_key(key)?)719 .ok_or(<Error<T>>::CollectionUnknown)?720 .clone();721722 Ok(collection_property)723 }724725 pub fn get_collection_property_decoded<V: Decode>(726 collection_id: CollectionId,727 key: RmrkProperty,728 ) -> Result<V, DispatchError> {729 Self::decode_property(Self::get_collection_property(collection_id, key)?)730 }731732 pub fn get_collection_type(733 collection_id: CollectionId,734 ) -> Result<misc::CollectionType, DispatchError> {735 Self::get_collection_property_decoded(collection_id, CollectionType)736 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())737 }738739 pub fn ensure_collection_type(740 collection_id: CollectionId,741 collection_type: misc::CollectionType,742 ) -> DispatchResult {743 let actual_type = Self::get_collection_type(collection_id)?;744 ensure!(745 actual_type == collection_type,746 <CommonError<T>>::NoPermission747 );748749 Ok(())750 }751752 pub fn get_typed_nft_collection(753 collection_id: CollectionId,754 collection_type: misc::CollectionType,755 ) -> Result<NonfungibleHandle<T>, DispatchError> {756 Self::ensure_collection_type(collection_id, collection_type)?;757758 Self::get_nft_collection(collection_id)759 }760761 pub fn get_nft_property(762 collection_id: CollectionId,763 nft_id: TokenId,764 key: RmrkProperty,765 ) -> Result<PropertyValue, DispatchError> {766 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))767 .get(&Self::rmrk_property_key(key)?)768 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?769 .clone();770771 Ok(nft_property)772 }773774 pub fn get_nft_property_decoded<V: Decode>(775 collection_id: CollectionId,776 nft_id: TokenId,777 key: RmrkProperty,778 ) -> Result<V, DispatchError> {779 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)780 }781782 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {783 <TokenData<T>>::contains_key((collection_id, nft_id))784 }785786 pub fn get_nft_type(787 collection_id: CollectionId,788 token_id: TokenId,789 ) -> Result<NftType, DispatchError> {790 Self::get_nft_property_decoded(collection_id, token_id, TokenType)791 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())792 }793794 pub fn ensure_nft_type(795 collection_id: CollectionId,796 token_id: TokenId,797 nft_type: NftType,798 ) -> DispatchResult {799 let actual_type = Self::get_nft_type(collection_id, token_id)?;800 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);801802 Ok(())803 }804805 pub fn ensure_nft_owner(806 collection_id: CollectionId,807 token_id: TokenId,808 possible_owner: &T::CrossAccountId,809 ) -> DispatchResult {810 let token_data =811 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;812813 ensure!(814 token_data.owner == *possible_owner,815 <Error<T>>::NoPermission816 );817818 Ok(())819 }820821 pub fn filter_user_properties<Key, Value, R, Mapper>(822 collection_id: CollectionId,823 token_id: Option<TokenId>,824 filter_keys: Option<Vec<RmrkPropertyKey>>,825 mapper: Mapper,826 ) -> Result<Vec<R>, DispatchError>827 where828 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,829 Value: Decode + Default,830 Mapper: Fn(Key, Value) -> R,831 {832 filter_keys833 .map(|keys| {834 let properties = keys835 .into_iter()836 .filter_map(|key| {837 let key: Key = key.try_into().ok()?;838839 let value = match token_id {840 Some(token_id) => Self::get_nft_property_decoded(841 collection_id,842 token_id,843 UserProperty(key.as_ref()),844 ),845 None => Self::get_collection_property_decoded(846 collection_id,847 UserProperty(key.as_ref()),848 ),849 }850 .ok()?;851852 Some(mapper(key, value))853 })854 .collect();855856 Ok(properties)857 })858 .unwrap_or_else(|| {859 let properties =860 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();861862 Ok(properties)863 })864 }865866 pub fn iterate_user_properties<Key, Value, R, Mapper>(867 collection_id: CollectionId,868 token_id: Option<TokenId>,869 mapper: Mapper,870 ) -> Result<impl Iterator<Item = R>, DispatchError>871 where872 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,873 Value: Decode + Default,874 Mapper: Fn(Key, Value) -> R,875 {876 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;877878 let properties = match token_id {879 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),880 None => <PalletCommon<T>>::collection_properties(collection_id),881 };882883 let properties = properties.into_iter().filter_map(move |(key, value)| {884 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;885886 let key: Key = key.to_vec().try_into().ok()?;887 let value: Value = value.decode().ok()?;888889 Some(mapper(key, value))890 });891892 Ok(properties)893 }894895 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {896 map_common_err_to_proxy! {897 match err {898 NoPermission => NoPermission,899 CollectionTokenLimitExceeded => CollectionFullOrLocked,900 PublicMintingNotAllowed => NoPermission,901 TokenNotFound => NoAvailableNftId902 }903 }904 }905}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142#[frame_support::pallet]43pub mod pallet {44 use super::*;45 use pallet_evm::account;4647 #[pallet::config]48 pub trait Config:49 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50 {51 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52 }5354 #[pallet::storage]55 #[pallet::getter(fn collection_index)]56 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758 #[pallet::storage]59 #[pallet::getter(fn collection_index_map)]60 pub type CollectionIndexMap<T: Config> =61 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263 #[pallet::pallet]64 #[pallet::generate_store(pub(super) trait Store)]65 pub struct Pallet<T>(_);6667 #[pallet::event]68 #[pallet::generate_deposit(pub(super) fn deposit_event)]69 pub enum Event<T: Config> {70 CollectionCreated {71 issuer: T::AccountId,72 collection_id: RmrkCollectionId,73 },74 CollectionDestroyed {75 issuer: T::AccountId,76 collection_id: RmrkCollectionId,77 },78 IssuerChanged {79 old_issuer: T::AccountId,80 new_issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 CollectionLocked {84 issuer: T::AccountId,85 collection_id: RmrkCollectionId,86 },87 NftMinted {88 owner: T::AccountId,89 collection_id: RmrkCollectionId,90 nft_id: RmrkNftId,91 },92 NFTBurned {93 owner: T::AccountId,94 nft_id: RmrkNftId,95 },96 PropertySet {97 collection_id: RmrkCollectionId,98 maybe_nft_id: Option<RmrkNftId>,99 key: RmrkKeyString,100 value: RmrkValueString,101 },102 ResourceAdded {103 nft_id: RmrkNftId,104 resource_id: RmrkResourceId,105 },106 ResourceRemoval {107 nft_id: RmrkNftId,108 resource_id: RmrkResourceId,109 },110 }111112 #[pallet::error]113 pub enum Error<T> {114 /* Unique-specific events */115 CorruptedCollectionType,116 NftTypeEncodeError,117 RmrkPropertyKeyIsTooLong,118 RmrkPropertyValueIsTooLong,119120 /* RMRK compatible events */121 CollectionNotEmpty,122 NoAvailableCollectionId,123 NoAvailableNftId,124 CollectionUnknown,125 NoPermission,126 CollectionFullOrLocked,127 ResourceDoesntExist,128 }129130 #[pallet::call]131 impl<T: Config> Pallet<T> {132 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]133 #[transactional]134 pub fn create_collection(135 origin: OriginFor<T>,136 metadata: RmrkString,137 max: Option<u32>,138 symbol: RmrkCollectionSymbol,139 ) -> DispatchResult {140 let sender = ensure_signed(origin)?;141142 let limits = CollectionLimits {143 owner_can_transfer: Some(false),144 token_limit: max,145 ..Default::default()146 };147148 let data = CreateCollectionData {149 limits: Some(limits),150 token_prefix: symbol151 .into_inner()152 .try_into()153 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,154 ..Default::default()155 };156157 <CollectionIndex<T>>::mutate(|n| *n += 1);158159 let unique_collection_id = Self::init_collection(160 T::CrossAccountId::from_sub(sender.clone()),161 data,162 [163 Self::rmrk_property(Metadata, &metadata)?,164 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,165 ]166 .into_iter(),167 )?;168 let rmrk_collection_id = <CollectionIndex<T>>::get();169170 <CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);171172 Self::deposit_event(Event::CollectionCreated {173 issuer: sender,174 collection_id: rmrk_collection_id,175 });176177 Ok(())178 }179180 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]181 #[transactional]182 pub fn destroy_collection(183 origin: OriginFor<T>,184 collection_id: RmrkCollectionId,185 ) -> DispatchResult {186 let sender = ensure_signed(origin)?;187 let cross_sender = T::CrossAccountId::from_sub(sender.clone());188189 let collection = Self::get_typed_nft_collection(190 Self::unique_collection_id(collection_id)?,191 misc::CollectionType::Regular,192 )?;193194 ensure!(195 collection.total_supply() == 0,196 <Error<T>>::CollectionNotEmpty197 );198199 <PalletNft<T>>::destroy_collection(collection, &cross_sender)200 .map_err(Self::map_common_err_to_proxy)?;201202 Self::deposit_event(Event::CollectionDestroyed {203 issuer: sender,204 collection_id,205 });206207 Ok(())208 }209210 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]211 #[transactional]212 pub fn change_collection_issuer(213 origin: OriginFor<T>,214 collection_id: RmrkCollectionId,215 new_issuer: <T::Lookup as StaticLookup>::Source,216 ) -> DispatchResult {217 let sender = ensure_signed(origin)?;218219 let new_issuer = T::Lookup::lookup(new_issuer)?;220221 Self::change_collection_owner(222 Self::unique_collection_id(collection_id)?,223 misc::CollectionType::Regular,224 sender.clone(),225 new_issuer.clone(),226 )?;227228 Self::deposit_event(Event::IssuerChanged {229 old_issuer: sender,230 new_issuer,231 collection_id,232 });233234 Ok(())235 }236237 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]238 #[transactional]239 pub fn lock_collection(240 origin: OriginFor<T>,241 collection_id: RmrkCollectionId,242 ) -> DispatchResult {243 let sender = ensure_signed(origin)?;244 let cross_sender = T::CrossAccountId::from_sub(sender.clone());245246 let collection = Self::get_typed_nft_collection(247 Self::unique_collection_id(collection_id)?,248 misc::CollectionType::Regular,249 )?;250251 Self::check_collection_owner(&collection, &cross_sender)?;252253 let token_count = collection.total_supply();254255 let mut collection = collection.into_inner();256 collection.limits.token_limit = Some(token_count);257 collection.save()?;258259 Self::deposit_event(Event::CollectionLocked {260 issuer: sender,261 collection_id,262 });263264 Ok(())265 }266267 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]268 #[transactional]269 pub fn mint_nft(270 origin: OriginFor<T>,271 owner: T::AccountId,272 collection_id: RmrkCollectionId,273 recipient: Option<T::AccountId>,274 royalty_amount: Option<Permill>,275 metadata: RmrkString,276 ) -> DispatchResult {277 let sender = ensure_signed(origin)?;278 let sender = T::CrossAccountId::from_sub(sender);279 let cross_owner = T::CrossAccountId::from_sub(owner.clone());280281 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {282 recipient: recipient.unwrap_or_else(|| owner.clone()),283 amount,284 });285286 let collection = Self::get_typed_nft_collection(287 Self::unique_collection_id(collection_id)?,288 misc::CollectionType::Regular,289 )?;290291 let nft_id = Self::create_nft(292 &sender,293 &cross_owner,294 &collection,295 [296 Self::rmrk_property(TokenType, &NftType::Regular)?,297 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,298 Self::rmrk_property(Metadata, &metadata)?,299 Self::rmrk_property(Equipped, &false)?,300 Self::rmrk_property(301 ResourceCollection,302 &Self::init_collection(303 sender.clone(),304 CreateCollectionData {305 ..Default::default()306 },307 [Self::rmrk_property(308 CollectionType,309 &misc::CollectionType::Resource,310 )?]311 .into_iter(),312 )?,313 )?, // todo possibly add limits to the collection if rmrk warrants them314 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,315 ]316 .into_iter(),317 )318 .map_err(|err| match err {319 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),320 err => Self::map_common_err_to_proxy(err),321 })?;322323 Self::deposit_event(Event::NftMinted {324 owner,325 collection_id,326 nft_id: nft_id.0,327 });328329 Ok(())330 }331332 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]333 #[transactional]334 pub fn burn_nft(335 origin: OriginFor<T>,336 collection_id: RmrkCollectionId,337 nft_id: RmrkNftId,338 ) -> DispatchResult {339 let sender = ensure_signed(origin)?;340 let cross_sender = T::CrossAccountId::from_sub(sender.clone());341342 Self::destroy_nft(343 cross_sender,344 Self::unique_collection_id(collection_id)?,345 misc::CollectionType::Regular,346 nft_id.into(),347 )?;348349 Self::deposit_event(Event::NFTBurned {350 owner: sender,351 nft_id,352 });353354 Ok(())355 }356357 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]358 #[transactional]359 pub fn set_property(360 origin: OriginFor<T>,361 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,362 maybe_nft_id: Option<RmrkNftId>,363 key: RmrkKeyString,364 value: RmrkValueString,365 ) -> DispatchResult {366 let sender = ensure_signed(origin)?;367 let sender = T::CrossAccountId::from_sub(sender);368369 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;370371 match maybe_nft_id {372 Some(nft_id) => {373 let token_id: TokenId = nft_id.into();374375 Self::ensure_nft_owner(collection_id, token_id, &sender)?;376 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;377378 <PalletNft<T>>::set_scoped_token_property(379 collection_id,380 token_id,381 PropertyScope::Rmrk,382 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,383 )?;384 }385 None => {386 let collection = Self::get_typed_nft_collection(387 collection_id,388 misc::CollectionType::Regular,389 )?;390391 Self::check_collection_owner(&collection, &sender)?;392393 <PalletCommon<T>>::set_scoped_collection_property(394 collection_id,395 PropertyScope::Rmrk,396 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,397 )?;398 }399 }400401 Self::deposit_event(Event::PropertySet {402 collection_id: rmrk_collection_id,403 maybe_nft_id,404 key,405 value,406 });407408 Ok(())409 }410411 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]412 #[transactional]413 pub fn add_basic_resource(414 origin: OriginFor<T>,415 collection_id: RmrkCollectionId,416 nft_id: RmrkNftId,417 resource: RmrkBasicResource,418 ) -> DispatchResult {419 let sender = ensure_signed(origin.clone())?;420421 let resource_id = Self::resource_add(422 sender,423 Self::unique_collection_id(collection_id)?,424 nft_id.into(),425 [426 Self::rmrk_property(TokenType, &NftType::Resource)?,427 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,428 Self::rmrk_property(Src, &resource.src)?,429 Self::rmrk_property(Metadata, &resource.metadata)?,430 Self::rmrk_property(License, &resource.license)?,431 Self::rmrk_property(Thumb, &resource.thumb)?,432 ]433 .into_iter(),434 )?;435436 Self::deposit_event(Event::ResourceAdded {437 nft_id,438 resource_id,439 });440 Ok(())441 }442443 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444 #[transactional]445 pub fn add_composable_resource(446 origin: OriginFor<T>,447 collection_id: RmrkCollectionId,448 nft_id: RmrkNftId,449 _resource_id: RmrkBoundedResource,450 resource: RmrkComposableResource,451 ) -> DispatchResult {452 let sender = ensure_signed(origin.clone())?;453454 let resource_id = Self::resource_add(455 sender,456 Self::unique_collection_id(collection_id)?,457 nft_id.into(),458 [459 Self::rmrk_property(TokenType, &NftType::Resource)?,460 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,461 Self::rmrk_property(Parts, &resource.parts)?,462 Self::rmrk_property(Base, &resource.base)?,463 Self::rmrk_property(Src, &resource.src)?,464 Self::rmrk_property(Metadata, &resource.metadata)?,465 Self::rmrk_property(License, &resource.license)?,466 Self::rmrk_property(Thumb, &resource.thumb)?,467 ]468 .into_iter(),469 )?;470471 Self::deposit_event(Event::ResourceAdded {472 nft_id,473 resource_id,474 });475 Ok(())476 }477478 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]479 #[transactional]480 pub fn add_slot_resource(481 origin: OriginFor<T>,482 collection_id: RmrkCollectionId,483 nft_id: RmrkNftId,484 resource: RmrkSlotResource,485 ) -> DispatchResult {486 let sender = ensure_signed(origin.clone())?;487488 let resource_id = Self::resource_add(489 sender,490 Self::unique_collection_id(collection_id)?,491 nft_id.into(),492 [493 Self::rmrk_property(TokenType, &NftType::Resource)?,494 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,495 Self::rmrk_property(Base, &resource.base)?,496 Self::rmrk_property(Src, &resource.src)?,497 Self::rmrk_property(Metadata, &resource.metadata)?,498 Self::rmrk_property(Slot, &resource.slot)?,499 Self::rmrk_property(License, &resource.license)?,500 Self::rmrk_property(Thumb, &resource.thumb)?,501 ]502 .into_iter(),503 )?;504505 Self::deposit_event(Event::ResourceAdded {506 nft_id,507 resource_id,508 });509 Ok(())510 }511512 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]513 #[transactional]514 pub fn remove_resource(515 origin: OriginFor<T>,516 collection_id: RmrkCollectionId,517 nft_id: RmrkNftId,518 resource_id: RmrkResourceId,519 ) -> DispatchResult {520 let sender = ensure_signed(origin.clone())?;521522 Self::resource_remove(523 sender,524 Self::unique_collection_id(collection_id)?,525 nft_id.into(),526 resource_id.into(),527 )?;528529 Self::deposit_event(Event::ResourceRemoval {530 nft_id,531 resource_id,532 });533 Ok(())534 }535 }536}537538impl<T: Config> Pallet<T> {539 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {540 let key = rmrk_key.to_key::<T>()?;541542 let scoped_key = PropertyScope::Rmrk543 .apply(key)544 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;545546 Ok(scoped_key)547 }548549 // todo think about renaming these550 pub fn rmrk_property<E: Encode>(551 rmrk_key: RmrkProperty,552 value: &E,553 ) -> Result<Property, DispatchError> {554 let key = rmrk_key.to_key::<T>()?;555556 let value = value557 .encode()558 .try_into()559 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;560561 let property = Property { key, value };562563 Ok(property)564 }565566 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {567 vec.decode()568 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())569 }570571 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>572 where573 BoundedVec<u8, S>: TryFrom<Vec<u8>>,574 {575 vec.rebind()576 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())577 }578579 fn init_collection(580 sender: T::CrossAccountId,581 data: CreateCollectionData<T::AccountId>,582 properties: impl Iterator<Item = Property>,583 ) -> Result<CollectionId, DispatchError> {584 let collection_id = <PalletNft<T>>::init_collection(sender, data);585586 if let Err(DispatchError::Arithmetic(_)) = &collection_id {587 return Err(<Error<T>>::NoAvailableCollectionId.into());588 }589590 <PalletCommon<T>>::set_scoped_collection_properties(591 collection_id?,592 PropertyScope::Rmrk,593 properties,594 )?;595596 collection_id597 }598599 pub fn create_nft(600 sender: &T::CrossAccountId,601 owner: &T::CrossAccountId,602 collection: &NonfungibleHandle<T>,603 properties: impl Iterator<Item = Property>,604 ) -> Result<TokenId, DispatchError> {605 let data = CreateNftExData {606 properties: BoundedVec::default(),607 owner: owner.clone(),608 };609610 let budget = budget::Value::new(2);611612 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;613614 let nft_id = <PalletNft<T>>::current_token_id(collection.id);615616 <PalletNft<T>>::set_scoped_token_properties(617 collection.id,618 nft_id,619 PropertyScope::Rmrk,620 properties,621 )?;622623 Ok(nft_id)624 }625626 fn destroy_nft(627 sender: T::CrossAccountId,628 collection_id: CollectionId,629 collection_type: misc::CollectionType,630 token_id: TokenId,631 ) -> DispatchResult {632 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;633634 <PalletNft<T>>::burn(&collection, &sender, token_id)635 .map_err(Self::map_common_err_to_proxy)?;636637 Ok(())638 }639640 fn resource_add(641 sender: T::AccountId,642 collection_id: CollectionId,643 token_id: TokenId,644 resource_properties: impl Iterator<Item = Property>,645 ) -> Result<RmrkResourceId, DispatchError> {646 let collection =647 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648 ensure!(collection.owner == sender, Error::<T>::NoPermission);649650 // Check NFT lock status // todo depends on market, maybe later651 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);652653 let sender = T::CrossAccountId::from_sub(sender);654 let budget = budget::Value::new(10);655 let pending = !<PalletStructure<T>>::check_indirectly_owned(656 sender.clone(),657 collection_id,658 token_id,659 None,660 &budget,661 )?;662663 let resource_collection_id: CollectionId =664 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;665 let resource_collection =666 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;667668 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them669670 let resource_id = Self::create_nft(671 &sender, // todo owner of the nft?672 &sender,673 &resource_collection,674 resource_properties.chain(675 [676 Self::rmrk_property(PendingResourceAccept, &pending)?,677 Self::rmrk_property(PendingResourceRemoval, &false)?,678 ]679 .into_iter(),680 ),681 )682 .map_err(|err| match err {683 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),684 err => Self::map_common_err_to_proxy(err),685 })?;686687 Ok(resource_id.0)688 }689690 fn resource_remove(691 sender: T::AccountId,692 collection_id: CollectionId,693 nft_id: TokenId,694 resource_id: TokenId,695 ) -> DispatchResult {696 let collection =697 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;698 ensure!(collection.owner == sender, Error::<T>::NoPermission);699700 let resource_collection_id: CollectionId =701 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;702 let resource_collection =703 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;704 ensure!(705 <PalletNft<T>>::token_exists(&resource_collection, resource_id),706 Error::<T>::ResourceDoesntExist707 );708709 let budget = up_data_structs::budget::Value::new(10);710 let topmost_owner =711 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;712713 let sender = T::CrossAccountId::from_sub(sender);714 if topmost_owner == sender {715 <PalletNft<T>>::burn(&collection, &sender, nft_id)716 .map_err(Self::map_common_err_to_proxy)?;717 } else {718 <PalletNft<T>>::set_scoped_token_property(719 collection_id,720 nft_id,721 PropertyScope::Rmrk,722 Self::rmrk_property(PendingResourceRemoval, &true)?,723 )?;724 }725726 Ok(())727 }728729 fn change_collection_owner(730 collection_id: CollectionId,731 collection_type: misc::CollectionType,732 sender: T::AccountId,733 new_owner: T::AccountId,734 ) -> DispatchResult {735 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;736 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;737738 let mut collection = collection.into_inner();739740 collection.owner = new_owner;741 collection.save()742 }743744 fn check_collection_owner(745 collection: &NonfungibleHandle<T>,746 account: &T::CrossAccountId,747 ) -> DispatchResult {748 collection749 .check_is_owner(account)750 .map_err(Self::map_common_err_to_proxy)751 }752753 pub fn last_collection_idx() -> RmrkCollectionId {754 <CollectionIndex<T>>::get()755 }756757 pub fn unique_collection_id(758 rmrk_collection_id: RmrkCollectionId,759 ) -> Result<CollectionId, DispatchError> {760 <CollectionIndexMap<T>>::try_get(rmrk_collection_id)761 .map_err(|_| <Error<T>>::CollectionUnknown.into())762 }763764 pub fn get_nft_collection(765 collection_id: CollectionId,766 ) -> Result<NonfungibleHandle<T>, DispatchError> {767 let collection = <CollectionHandle<T>>::try_get(collection_id)768 .map_err(|_| <Error<T>>::CollectionUnknown)?;769770 match collection.mode {771 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),772 _ => Err(<Error<T>>::CollectionUnknown.into()),773 }774 }775776 pub fn collection_exists(collection_id: CollectionId) -> bool {777 <CollectionHandle<T>>::try_get(collection_id).is_ok()778 }779780 pub fn get_collection_property(781 collection_id: CollectionId,782 key: RmrkProperty,783 ) -> Result<PropertyValue, DispatchError> {784 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)785 .get(&Self::rmrk_property_key(key)?)786 .ok_or(<Error<T>>::CollectionUnknown)?787 .clone();788789 Ok(collection_property)790 }791792 pub fn get_collection_property_decoded<V: Decode>(793 collection_id: CollectionId,794 key: RmrkProperty,795 ) -> Result<V, DispatchError> {796 Self::decode_property(Self::get_collection_property(collection_id, key)?)797 }798799 pub fn get_collection_type(800 collection_id: CollectionId,801 ) -> Result<misc::CollectionType, DispatchError> {802 Self::get_collection_property_decoded(collection_id, CollectionType)803 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())804 }805806 pub fn ensure_collection_type(807 collection_id: CollectionId,808 collection_type: misc::CollectionType,809 ) -> DispatchResult {810 let actual_type = Self::get_collection_type(collection_id)?;811 ensure!(812 actual_type == collection_type,813 <CommonError<T>>::NoPermission814 );815816 Ok(())817 }818819 pub fn get_typed_nft_collection(820 collection_id: CollectionId,821 collection_type: misc::CollectionType,822 ) -> Result<NonfungibleHandle<T>, DispatchError> {823 Self::ensure_collection_type(collection_id, collection_type)?;824825 Self::get_nft_collection(collection_id)826 }827828 pub fn get_nft_property(829 collection_id: CollectionId,830 nft_id: TokenId,831 key: RmrkProperty,832 ) -> Result<PropertyValue, DispatchError> {833 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))834 .get(&Self::rmrk_property_key(key)?)835 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?836 .clone();837838 Ok(nft_property)839 }840841 pub fn get_nft_property_decoded<V: Decode>(842 collection_id: CollectionId,843 nft_id: TokenId,844 key: RmrkProperty,845 ) -> Result<V, DispatchError> {846 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)847 }848849 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {850 <TokenData<T>>::contains_key((collection_id, nft_id))851 }852853 pub fn get_nft_type(854 collection_id: CollectionId,855 token_id: TokenId,856 ) -> Result<NftType, DispatchError> {857 Self::get_nft_property_decoded(collection_id, token_id, TokenType)858 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())859 }860861 pub fn ensure_nft_type(862 collection_id: CollectionId,863 token_id: TokenId,864 nft_type: NftType,865 ) -> DispatchResult {866 let actual_type = Self::get_nft_type(collection_id, token_id)?;867 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);868869 Ok(())870 }871872 pub fn ensure_nft_owner(873 collection_id: CollectionId,874 token_id: TokenId,875 possible_owner: &T::CrossAccountId,876 ) -> DispatchResult {877 let token_data =878 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;879880 ensure!(881 token_data.owner == *possible_owner,882 <Error<T>>::NoPermission883 );884885 Ok(())886 }887888 pub fn filter_user_properties<Key, Value, R, Mapper>(889 collection_id: CollectionId,890 token_id: Option<TokenId>,891 filter_keys: Option<Vec<RmrkPropertyKey>>,892 mapper: Mapper,893 ) -> Result<Vec<R>, DispatchError>894 where895 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,896 Value: Decode + Default,897 Mapper: Fn(Key, Value) -> R,898 {899 filter_keys900 .map(|keys| {901 let properties = keys902 .into_iter()903 .filter_map(|key| {904 let key: Key = key.try_into().ok()?;905906 let value = match token_id {907 Some(token_id) => Self::get_nft_property_decoded(908 collection_id,909 token_id,910 UserProperty(key.as_ref()),911 ),912 None => Self::get_collection_property_decoded(913 collection_id,914 UserProperty(key.as_ref()),915 ),916 }917 .ok()?;918919 Some(mapper(key, value))920 })921 .collect();922923 Ok(properties)924 })925 .unwrap_or_else(|| {926 let properties =927 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();928929 Ok(properties)930 })931 }932933 pub fn iterate_user_properties<Key, Value, R, Mapper>(934 collection_id: CollectionId,935 token_id: Option<TokenId>,936 mapper: Mapper,937 ) -> Result<impl Iterator<Item = R>, DispatchError>938 where939 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,940 Value: Decode + Default,941 Mapper: Fn(Key, Value) -> R,942 {943 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;944945 let properties = match token_id {946 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),947 None => <PalletCommon<T>>::collection_properties(collection_id),948 };949950 let properties = properties.into_iter().filter_map(move |(key, value)| {951 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;952953 let key: Key = key.to_vec().try_into().ok()?;954 let value: Value = value.decode().ok()?;955956 Some(mapper(key, value))957 });958959 Ok(properties)960 }961962 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {963 map_common_err_to_proxy! {964 match err {965 NoPermission => NoPermission,966 CollectionTokenLimitExceeded => CollectionFullOrLocked,967 PublicMintingNotAllowed => NoPermission,968 TokenNotFound => NoAvailableNftId969 }970 }971 }972}