difftreelog
fix burn_nft with children
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::{*, mapping::TokenAddressMapping};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, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161162 /* RMRK compatible events */163 CollectionNotEmpty,164 NoAvailableCollectionId,165 NoAvailableNftId,166 CollectionUnknown,167 NoPermission,168 NonTransferable,169 CollectionFullOrLocked,170 ResourceDoesntExist,171 CannotSendToDescendentOrSelf,172 CannotAcceptNonOwnedNft,173 CannotRejectNonOwnedNft,174 ResourceNotPending,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 /// Create a collection180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingPermissions {204 token_owner: true,205 admin: false,206 restricted: None,207208 permissive: false,209 }),210 ..Default::default()211 }),212 ..Default::default()213 };214215 let unique_collection_id = Self::init_collection(216 T::CrossAccountId::from_sub(sender.clone()),217 data,218 [219 Self::rmrk_property(Metadata, &metadata)?,220 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221 ]222 .into_iter(),223 )?;224 let rmrk_collection_id = <CollectionIndex<T>>::get();225226 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);228229 <CollectionIndex<T>>::mutate(|n| *n += 1);230231 Self::deposit_event(Event::CollectionCreated {232 issuer: sender,233 collection_id: rmrk_collection_id,234 });235236 Ok(())237 }238239 /// destroy collection240 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]241 #[transactional]242 pub fn destroy_collection(243 origin: OriginFor<T>,244 collection_id: RmrkCollectionId,245 ) -> DispatchResult {246 let sender = ensure_signed(origin)?;247 let cross_sender = T::CrossAccountId::from_sub(sender.clone());248249 let collection = Self::get_typed_nft_collection(250 Self::unique_collection_id(collection_id)?,251 misc::CollectionType::Regular,252 )?;253 collection.check_is_external()?;254255 <PalletNft<T>>::destroy_collection(collection, &cross_sender)256 .map_err(Self::map_unique_err_to_proxy)?;257258 Self::deposit_event(Event::CollectionDestroyed {259 issuer: sender,260 collection_id,261 });262263 Ok(())264 }265266 /// Change the issuer of a collection267 ///268 /// Parameters:269 /// - `origin`: sender of the transaction270 /// - `collection_id`: collection id of the nft to change issuer of271 /// - `new_issuer`: Collection's new issuer272 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]273 #[transactional]274 pub fn change_collection_issuer(275 origin: OriginFor<T>,276 collection_id: RmrkCollectionId,277 new_issuer: <T::Lookup as StaticLookup>::Source,278 ) -> DispatchResult {279 let sender = ensure_signed(origin)?;280281 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;282 collection.check_is_external()?;283284 let new_issuer = T::Lookup::lookup(new_issuer)?;285286 Self::change_collection_owner(287 Self::unique_collection_id(collection_id)?,288 misc::CollectionType::Regular,289 sender.clone(),290 new_issuer.clone(),291 )?;292293 Self::deposit_event(Event::IssuerChanged {294 old_issuer: sender,295 new_issuer,296 collection_id,297 });298299 Ok(())300 }301302 /// lock collection303 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304 #[transactional]305 pub fn lock_collection(306 origin: OriginFor<T>,307 collection_id: RmrkCollectionId,308 ) -> DispatchResult {309 let sender = ensure_signed(origin)?;310 let cross_sender = T::CrossAccountId::from_sub(sender.clone());311312 let collection = Self::get_typed_nft_collection(313 Self::unique_collection_id(collection_id)?,314 misc::CollectionType::Regular,315 )?;316 collection.check_is_external()?;317318 Self::check_collection_owner(&collection, &cross_sender)?;319320 let token_count = collection.total_supply();321322 let mut collection = collection.into_inner();323 collection.limits.token_limit = Some(token_count);324 collection.save()?;325326 Self::deposit_event(Event::CollectionLocked {327 issuer: sender,328 collection_id,329 });330331 Ok(())332 }333334 /// Mints an NFT in the specified collection335 /// Sets metadata and the royalty attribute336 ///337 /// Parameters:338 /// - `collection_id`: The class of the asset to be minted.339 /// - `nft_id`: The nft value of the asset to be minted.340 /// - `recipient`: Receiver of the royalty341 /// - `royalty`: Permillage reward from each trade for the Recipient342 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash343 /// - `transferable`: Ability to transfer this NFT344 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345 #[transactional]346 pub fn mint_nft(347 origin: OriginFor<T>,348 owner: T::AccountId,349 collection_id: RmrkCollectionId,350 recipient: Option<T::AccountId>,351 royalty_amount: Option<Permill>,352 metadata: RmrkString,353 transferable: bool,354 ) -> DispatchResult {355 let sender = ensure_signed(origin)?;356 let sender = T::CrossAccountId::from_sub(sender);357 let cross_owner = T::CrossAccountId::from_sub(owner.clone());358359 let collection = Self::get_typed_nft_collection(360 Self::unique_collection_id(collection_id)?,361 misc::CollectionType::Regular,362 )?;363 collection.check_is_external()?;364365 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {366 recipient: recipient.unwrap_or_else(|| owner.clone()),367 amount,368 });369370 let nft_id = Self::create_nft(371 &sender,372 &cross_owner,373 &collection,374 [375 Self::rmrk_property(TokenType, &NftType::Regular)?,376 Self::rmrk_property(Transferable, &transferable)?,377 Self::rmrk_property(PendingNftAccept, &false)?,378 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,379 Self::rmrk_property(Metadata, &metadata)?,380 Self::rmrk_property(Equipped, &false)?,381 Self::rmrk_property(382 ResourceCollection,383 &Self::init_collection(384 sender.clone(),385 CreateCollectionData {386 ..Default::default()387 },388 [Self::rmrk_property(389 CollectionType,390 &misc::CollectionType::Resource,391 )?]392 .into_iter(),393 )?,394 )?, // todo possibly add limits to the collection if rmrk warrants them395 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,396 ]397 .into_iter(),398 )399 .map_err(|err| match err {400 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),401 err => Self::map_unique_err_to_proxy(err),402 })?;403404 Self::deposit_event(Event::NftMinted {405 owner,406 collection_id,407 nft_id: nft_id.0,408 });409410 Ok(())411 }412413 /// burn nft414 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]415 #[transactional]416 pub fn burn_nft(417 origin: OriginFor<T>,418 collection_id: RmrkCollectionId,419 nft_id: RmrkNftId,420 ) -> DispatchResult {421 let sender = ensure_signed(origin)?;422 let cross_sender = T::CrossAccountId::from_sub(sender.clone());423424 let collection = Self::get_typed_nft_collection(425 Self::unique_collection_id(collection_id)?,426 misc::CollectionType::Regular,427 )?;428 collection.check_is_external()?;429430 Self::destroy_nft(431 cross_sender,432 Self::unique_collection_id(collection_id)?,433 nft_id.into(),434 )435 .map_err(Self::map_unique_err_to_proxy)?;436437 Self::deposit_event(Event::NFTBurned {438 owner: sender,439 nft_id,440 });441442 Ok(())443 }444445 /// Transfers a NFT from an Account or NFT A to another Account or NFT B446 ///447 /// Parameters:448 /// - `origin`: sender of the transaction449 /// - `rmrk_collection_id`: collection id of the nft to be transferred450 /// - `rmrk_nft_id`: nft id of the nft to be transferred451 /// - `new_owner`: new owner of the nft which can be either an account or a NFT452 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453 #[transactional]454 pub fn send(455 origin: OriginFor<T>,456 rmrk_collection_id: RmrkCollectionId,457 rmrk_nft_id: RmrkNftId,458 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,459 ) -> DispatchResult {460 let sender = ensure_signed(origin.clone())?;461 let cross_sender = T::CrossAccountId::from_sub(sender.clone());462463 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464 let nft_id = rmrk_nft_id.into();465466 let collection =467 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;468 collection.check_is_external()?;469470 let token_data =471 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;472473 let from = token_data.owner;474475 ensure!(476 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,477 <Error<T>>::NonTransferable478 );479480 ensure!(481 !Self::get_nft_property_decoded(482 collection_id,483 nft_id,484 RmrkProperty::PendingNftAccept485 )?,486 <Error<T>>::NoPermission487 );488489 let target_owner;490 let approval_required;491492 match new_owner {493 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {494 target_owner = T::CrossAccountId::from_sub(account_id.clone());495 approval_required = false;496 }497 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(498 target_collection_id,499 target_nft_id,500 ) => {501 let target_collection_id = Self::unique_collection_id(target_collection_id)?;502503 let target_nft_budget = budget::Value::new(NESTING_BUDGET);504505 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(506 target_collection_id,507 target_nft_id.into(),508 Some((collection_id, nft_id)),509 &target_nft_budget,510 )511 .map_err(Self::map_unique_err_to_proxy)?;512513 approval_required = cross_sender != target_nft_owner;514515 if approval_required {516 target_owner = target_nft_owner;517518 <PalletNft<T>>::set_scoped_token_property(519 collection.id,520 nft_id,521 PropertyScope::Rmrk,522 Self::rmrk_property(PendingNftAccept, &approval_required)?,523 )?;524 } else {525 target_owner = T::CrossTokenAddressMapping::token_to_address(526 target_collection_id,527 target_nft_id.into(),528 );529 }530 }531 }532533 let src_nft_budget = budget::Value::new(NESTING_BUDGET);534535 <PalletNft<T>>::transfer_from(536 &collection,537 &cross_sender,538 &from,539 &target_owner,540 nft_id,541 &src_nft_budget,542 )543 .map_err(Self::map_unique_err_to_proxy)?;544545 Self::deposit_event(Event::NFTSent {546 sender,547 recipient: new_owner,548 collection_id: rmrk_collection_id,549 nft_id: rmrk_nft_id,550 approval_required,551 });552553 Ok(())554 }555556 /// Accepts an NFT sent from another account to self or owned NFT557 ///558 /// Parameters:559 /// - `origin`: sender of the transaction560 /// - `rmrk_collection_id`: collection id of the nft to be accepted561 /// - `rmrk_nft_id`: nft id of the nft to be accepted562 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was563 /// sent to564 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]565 #[transactional]566 pub fn accept_nft(567 origin: OriginFor<T>,568 rmrk_collection_id: RmrkCollectionId,569 rmrk_nft_id: RmrkNftId,570 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,571 ) -> DispatchResult {572 let sender = ensure_signed(origin.clone())?;573 let cross_sender = T::CrossAccountId::from_sub(sender.clone());574575 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;576 let nft_id = rmrk_nft_id.into();577578 let collection =579 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;580 collection.check_is_external()?;581582 let new_cross_owner = match new_owner {583 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {584 T::CrossAccountId::from_sub(account_id.clone())585 }586 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(587 target_collection_id,588 target_nft_id,589 ) => {590 let target_collection_id = Self::unique_collection_id(target_collection_id)?;591592 T::CrossTokenAddressMapping::token_to_address(593 target_collection_id,594 TokenId(target_nft_id),595 )596 }597 };598599 let budget = budget::Value::new(NESTING_BUDGET);600601 <PalletNft<T>>::transfer(602 &collection,603 &cross_sender,604 &new_cross_owner,605 nft_id,606 &budget,607 )608 .map_err(|err| {609 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {610 <Error<T>>::CannotAcceptNonOwnedNft.into()611 } else {612 Self::map_unique_err_to_proxy(err)613 }614 })?;615616 <PalletNft<T>>::set_scoped_token_property(617 collection.id,618 nft_id,619 PropertyScope::Rmrk,620 Self::rmrk_property(PendingNftAccept, &false)?,621 )?;622623 Self::deposit_event(Event::NFTAccepted {624 sender,625 recipient: new_owner,626 collection_id: rmrk_collection_id,627 nft_id: rmrk_nft_id,628 });629630 Ok(())631 }632633 /// Rejects an NFT sent from another account to self or owned NFT634 ///635 /// Parameters:636 /// - `origin`: sender of the transaction637 /// - `rmrk_collection_id`: collection id of the nft to be accepted638 /// - `rmrk_nft_id`: nft id of the nft to be accepted639 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]640 #[transactional]641 pub fn reject_nft(642 origin: OriginFor<T>,643 rmrk_collection_id: RmrkCollectionId,644 rmrk_nft_id: RmrkNftId,645 ) -> DispatchResult {646 let sender = ensure_signed(origin)?;647 let cross_sender = T::CrossAccountId::from_sub(sender.clone());648649 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;650 let nft_id = rmrk_nft_id.into();651652 let collection =653 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;654 collection.check_is_external()?;655656 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {657 if err == <CommonError<T>>::NoPermission.into()658 || err == <CommonError<T>>::ApprovedValueTooLow.into()659 {660 <Error<T>>::CannotRejectNonOwnedNft.into()661 } else {662 Self::map_unique_err_to_proxy(err)663 }664 })?;665666 Self::deposit_event(Event::NFTRejected {667 sender,668 collection_id: rmrk_collection_id,669 nft_id: rmrk_nft_id,670 });671672 Ok(())673 }674675 /// accept the addition of a new resource to an existing NFT676 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]677 #[transactional]678 pub fn accept_resource(679 origin: OriginFor<T>,680 rmrk_collection_id: RmrkCollectionId,681 rmrk_nft_id: RmrkNftId,682 rmrk_resource_id: RmrkResourceId,683 ) -> DispatchResult {684 let sender = ensure_signed(origin)?;685 let cross_sender = T::CrossAccountId::from_sub(sender);686687 let collection_id = Self::unique_collection_id(rmrk_collection_id)688 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;689 let collection =690 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691 collection.check_is_external()?;692693 let nft_id = rmrk_nft_id.into();694 let resource_id = rmrk_resource_id.into();695696 let budget = budget::Value::new(NESTING_BUDGET);697698 let nft_owner =699 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)700 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;701702 let resource_collection_id: CollectionId =703 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)704 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;705706 let is_pending: bool = Self::get_nft_property_decoded(707 resource_collection_id,708 resource_id,709 PendingResourceAccept,710 )711 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;712713 ensure!(is_pending, <Error<T>>::ResourceNotPending);714715 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);716717 <PalletNft<T>>::set_scoped_token_property(718 resource_collection_id,719 rmrk_resource_id.into(),720 PropertyScope::Rmrk,721 Self::rmrk_property(PendingResourceAccept, &false)?,722 )?;723724 Self::deposit_event(Event::<T>::ResourceAccepted {725 nft_id: rmrk_nft_id,726 resource_id: rmrk_resource_id,727 });728729 Ok(())730 }731732 /// accept the removal of a resource of an existing NFT733 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]734 #[transactional]735 pub fn accept_resource_removal(736 origin: OriginFor<T>,737 rmrk_collection_id: RmrkCollectionId,738 rmrk_nft_id: RmrkNftId,739 rmrk_resource_id: RmrkResourceId,740 ) -> DispatchResult {741 let sender = ensure_signed(origin)?;742 let cross_sender = T::CrossAccountId::from_sub(sender);743744 let collection_id = Self::unique_collection_id(rmrk_collection_id)745 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;746 let collection =747 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;748 collection.check_is_external()?;749750 let nft_id = rmrk_nft_id.into();751 let resource_id = rmrk_resource_id.into();752753 let budget = budget::Value::new(NESTING_BUDGET);754755 let nft_owner =756 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);760761 let resource_collection_id: CollectionId =762 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)763 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;764765 let is_pending: bool = Self::get_nft_property_decoded(766 resource_collection_id,767 resource_id,768 PendingResourceRemoval,769 )770 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;771772 ensure!(is_pending, <Error<T>>::ResourceNotPending);773774 let resource_collection = Self::get_typed_nft_collection(775 resource_collection_id,776 misc::CollectionType::Resource,777 )?;778779 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())780 .map_err(Self::map_unique_err_to_proxy)?;781782 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {783 nft_id: rmrk_nft_id,784 resource_id: rmrk_resource_id,785 });786787 Ok(())788 }789790 /// set a custom value on an NFT791 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792 #[transactional]793 pub fn set_property(794 origin: OriginFor<T>,795 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,796 maybe_nft_id: Option<RmrkNftId>,797 key: RmrkKeyString,798 value: RmrkValueString,799 ) -> DispatchResult {800 let sender = ensure_signed(origin)?;801 let sender = T::CrossAccountId::from_sub(sender);802803 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;804 let collection =805 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;806 collection.check_is_external()?;807808 let budget = budget::Value::new(NESTING_BUDGET);809810 match maybe_nft_id {811 Some(nft_id) => {812 let token_id: TokenId = nft_id.into();813814 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;815 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;816817 <PalletNft<T>>::set_scoped_token_property(818 collection_id,819 token_id,820 PropertyScope::Rmrk,821 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,822 )?;823 }824 None => {825 let collection = Self::get_typed_nft_collection(826 collection_id,827 misc::CollectionType::Regular,828 )?;829830 Self::check_collection_owner(&collection, &sender)?;831832 <PalletCommon<T>>::set_scoped_collection_property(833 collection_id,834 PropertyScope::Rmrk,835 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,836 )?;837 }838 }839840 Self::deposit_event(Event::PropertySet {841 collection_id: rmrk_collection_id,842 maybe_nft_id,843 key,844 value,845 });846847 Ok(())848 }849850 /// set a different order of resource priority851 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]852 #[transactional]853 pub fn set_priority(854 origin: OriginFor<T>,855 rmrk_collection_id: RmrkCollectionId,856 rmrk_nft_id: RmrkNftId,857 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,858 ) -> DispatchResult {859 let sender = ensure_signed(origin)?;860 let sender = T::CrossAccountId::from_sub(sender);861862 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;863 let nft_id = rmrk_nft_id.into();864865 let collection =866 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;867 collection.check_is_external()?;868869 let budget = budget::Value::new(NESTING_BUDGET);870871 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;872 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;873874 <PalletNft<T>>::set_scoped_token_property(875 collection_id,876 nft_id,877 PropertyScope::Rmrk,878 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,879 )?;880881 Self::deposit_event(Event::<T>::PrioritySet {882 collection_id: rmrk_collection_id,883 nft_id: rmrk_nft_id,884 });885886 Ok(())887 }888889 /// Create basic resource890 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]891 #[transactional]892 pub fn add_basic_resource(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 nft_id: RmrkNftId,896 resource: RmrkBasicResource,897 ) -> DispatchResult {898 let sender = ensure_signed(origin.clone())?;899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let collection =902 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;903 collection.check_is_external()?;904905 let resource_id = Self::resource_add(906 sender,907 collection_id,908 nft_id.into(),909 [910 Self::rmrk_property(TokenType, &NftType::Resource)?,911 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,912 Self::rmrk_property(Src, &resource.src)?,913 Self::rmrk_property(Metadata, &resource.metadata)?,914 Self::rmrk_property(License, &resource.license)?,915 Self::rmrk_property(Thumb, &resource.thumb)?,916 ]917 .into_iter(),918 )?;919920 Self::deposit_event(Event::ResourceAdded {921 nft_id,922 resource_id,923 });924 Ok(())925 }926927 /// Create composable resource928 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]929 #[transactional]930 pub fn add_composable_resource(931 origin: OriginFor<T>,932 rmrk_collection_id: RmrkCollectionId,933 nft_id: RmrkNftId,934 _resource_id: RmrkBoundedResource,935 resource: RmrkComposableResource,936 ) -> DispatchResult {937 let sender = ensure_signed(origin.clone())?;938939 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;940 let collection =941 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;942 collection.check_is_external()?;943944 let resource_id = Self::resource_add(945 sender,946 collection_id,947 nft_id.into(),948 [949 Self::rmrk_property(TokenType, &NftType::Resource)?,950 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,951 Self::rmrk_property(Parts, &resource.parts)?,952 Self::rmrk_property(Base, &resource.base)?,953 Self::rmrk_property(Src, &resource.src)?,954 Self::rmrk_property(Metadata, &resource.metadata)?,955 Self::rmrk_property(License, &resource.license)?,956 Self::rmrk_property(Thumb, &resource.thumb)?,957 ]958 .into_iter(),959 )?;960961 Self::deposit_event(Event::ResourceAdded {962 nft_id,963 resource_id,964 });965 Ok(())966 }967968 /// Create slot resource969 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]970 #[transactional]971 pub fn add_slot_resource(972 origin: OriginFor<T>,973 rmrk_collection_id: RmrkCollectionId,974 nft_id: RmrkNftId,975 resource: RmrkSlotResource,976 ) -> DispatchResult {977 let sender = ensure_signed(origin.clone())?;978979 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;980 let collection =981 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982 collection.check_is_external()?;983984 let resource_id = Self::resource_add(985 sender,986 collection_id,987 nft_id.into(),988 [989 Self::rmrk_property(TokenType, &NftType::Resource)?,990 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,991 Self::rmrk_property(Base, &resource.base)?,992 Self::rmrk_property(Src, &resource.src)?,993 Self::rmrk_property(Metadata, &resource.metadata)?,994 Self::rmrk_property(Slot, &resource.slot)?,995 Self::rmrk_property(License, &resource.license)?,996 Self::rmrk_property(Thumb, &resource.thumb)?,997 ]998 .into_iter(),999 )?;10001001 Self::deposit_event(Event::ResourceAdded {1002 nft_id,1003 resource_id,1004 });1005 Ok(())1006 }10071008 /// remove resource1009 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1010 #[transactional]1011 pub fn remove_resource(1012 origin: OriginFor<T>,1013 rmrk_collection_id: RmrkCollectionId,1014 nft_id: RmrkNftId,1015 resource_id: RmrkResourceId,1016 ) -> DispatchResult {1017 let sender = ensure_signed(origin.clone())?;10181019 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1020 let collection =1021 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1022 collection.check_is_external()?;10231024 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10251026 Self::deposit_event(Event::ResourceRemoval {1027 nft_id,1028 resource_id,1029 });1030 Ok(())1031 }1032 }1033}10341035impl<T: Config> Pallet<T> {1036 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1037 let key = rmrk_key.to_key::<T>()?;10381039 let scoped_key = PropertyScope::Rmrk1040 .apply(key)1041 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10421043 Ok(scoped_key)1044 }10451046 // todo think about renaming these1047 pub fn rmrk_property<E: Encode>(1048 rmrk_key: RmrkProperty,1049 value: &E,1050 ) -> Result<Property, DispatchError> {1051 let key = rmrk_key.to_key::<T>()?;10521053 let value = value1054 .encode()1055 .try_into()1056 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10571058 let property = Property { key, value };10591060 Ok(property)1061 }10621063 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1064 vec.decode()1065 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1066 }10671068 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1069 where1070 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1071 {1072 vec.rebind()1073 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1074 }10751076 fn init_collection(1077 sender: T::CrossAccountId,1078 data: CreateCollectionData<T::AccountId>,1079 properties: impl Iterator<Item = Property>,1080 ) -> Result<CollectionId, DispatchError> {1081 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10821083 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1084 return Err(<Error<T>>::NoAvailableCollectionId.into());1085 }10861087 <PalletCommon<T>>::set_scoped_collection_properties(1088 collection_id?,1089 PropertyScope::Rmrk,1090 properties,1091 )?;10921093 collection_id1094 }10951096 pub fn create_nft(1097 sender: &T::CrossAccountId,1098 owner: &T::CrossAccountId,1099 collection: &NonfungibleHandle<T>,1100 properties: impl Iterator<Item = Property>,1101 ) -> Result<TokenId, DispatchError> {1102 let data = CreateNftExData {1103 properties: BoundedVec::default(),1104 owner: owner.clone(),1105 };11061107 let budget = budget::Value::new(NESTING_BUDGET);11081109 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11101111 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11121113 <PalletNft<T>>::set_scoped_token_properties(1114 collection.id,1115 nft_id,1116 PropertyScope::Rmrk,1117 properties,1118 )?;11191120 Ok(nft_id)1121 }11221123 fn destroy_nft(1124 sender: T::CrossAccountId,1125 collection_id: CollectionId,1126 token_id: TokenId,1127 ) -> DispatchResult {1128 let collection =1129 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11301131 let token_data =1132 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11331134 let from = token_data.owner;11351136 let budget = budget::Value::new(NESTING_BUDGET);11371138 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1139 }11401141 fn resource_add(1142 sender: T::AccountId,1143 collection_id: CollectionId,1144 token_id: TokenId,1145 resource_properties: impl Iterator<Item = Property>,1146 ) -> Result<RmrkResourceId, DispatchError> {1147 let collection =1148 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1149 ensure!(collection.owner == sender, Error::<T>::NoPermission);11501151 let sender = T::CrossAccountId::from_sub(sender);1152 let budget = budget::Value::new(NESTING_BUDGET);11531154 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1155 .map_err(Self::map_unique_err_to_proxy)?;11561157 let pending = sender != nft_owner;11581159 let resource_collection_id: CollectionId =1160 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1161 let resource_collection =1162 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11631164 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11651166 let resource_id = Self::create_nft(1167 &sender,1168 &nft_owner,1169 &resource_collection,1170 resource_properties.chain(1171 [1172 Self::rmrk_property(PendingResourceAccept, &pending)?,1173 Self::rmrk_property(PendingResourceRemoval, &false)?,1174 ]1175 .into_iter(),1176 ),1177 )1178 .map_err(|err| match err {1179 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1180 err => Self::map_unique_err_to_proxy(err),1181 })?;11821183 Ok(resource_id.0)1184 }11851186 fn resource_remove(1187 sender: T::AccountId,1188 collection_id: CollectionId,1189 nft_id: TokenId,1190 resource_id: TokenId,1191 ) -> DispatchResult {1192 let collection =1193 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1194 ensure!(collection.owner == sender, Error::<T>::NoPermission);11951196 let resource_collection_id: CollectionId =1197 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1198 let resource_collection =1199 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1200 ensure!(1201 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1202 Error::<T>::ResourceDoesntExist1203 );12041205 let budget = up_data_structs::budget::Value::new(10);1206 let topmost_owner =1207 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12081209 let sender = T::CrossAccountId::from_sub(sender);1210 if topmost_owner == sender {1211 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1212 .map_err(Self::map_unique_err_to_proxy)?;1213 } else {1214 <PalletNft<T>>::set_scoped_token_property(1215 resource_collection_id,1216 resource_id,1217 PropertyScope::Rmrk,1218 Self::rmrk_property(PendingResourceRemoval, &true)?,1219 )?;1220 }12211222 Ok(())1223 }12241225 fn change_collection_owner(1226 collection_id: CollectionId,1227 collection_type: misc::CollectionType,1228 sender: T::AccountId,1229 new_owner: T::AccountId,1230 ) -> DispatchResult {1231 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1232 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12331234 let mut collection = collection.into_inner();12351236 collection.owner = new_owner;1237 collection.save()1238 }12391240 fn check_collection_owner(1241 collection: &NonfungibleHandle<T>,1242 account: &T::CrossAccountId,1243 ) -> DispatchResult {1244 collection1245 .check_is_owner(account)1246 .map_err(Self::map_unique_err_to_proxy)1247 }12481249 pub fn last_collection_idx() -> RmrkCollectionId {1250 <CollectionIndex<T>>::get()1251 }12521253 pub fn unique_collection_id(1254 rmrk_collection_id: RmrkCollectionId,1255 ) -> Result<CollectionId, DispatchError> {1256 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1257 .map_err(|_| <Error<T>>::CollectionUnknown.into())1258 }12591260 pub fn rmrk_collection_id(1261 unique_collection_id: CollectionId,1262 ) -> Result<RmrkCollectionId, DispatchError> {1263 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1264 .map_err(|_| <Error<T>>::CollectionUnknown.into())1265 }12661267 pub fn get_nft_collection(1268 collection_id: CollectionId,1269 ) -> Result<NonfungibleHandle<T>, DispatchError> {1270 let collection = <CollectionHandle<T>>::try_get(collection_id)1271 .map_err(|_| <Error<T>>::CollectionUnknown)?;12721273 match collection.mode {1274 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1275 _ => Err(<Error<T>>::CollectionUnknown.into()),1276 }1277 }12781279 pub fn collection_exists(collection_id: CollectionId) -> bool {1280 <CollectionHandle<T>>::try_get(collection_id).is_ok()1281 }12821283 pub fn get_collection_property(1284 collection_id: CollectionId,1285 key: RmrkProperty,1286 ) -> Result<PropertyValue, DispatchError> {1287 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1288 .get(&Self::rmrk_property_key(key)?)1289 .ok_or(<Error<T>>::CollectionUnknown)?1290 .clone();12911292 Ok(collection_property)1293 }12941295 pub fn get_collection_property_decoded<V: Decode>(1296 collection_id: CollectionId,1297 key: RmrkProperty,1298 ) -> Result<V, DispatchError> {1299 Self::decode_property(Self::get_collection_property(collection_id, key)?)1300 }13011302 pub fn get_collection_type(1303 collection_id: CollectionId,1304 ) -> Result<misc::CollectionType, DispatchError> {1305 Self::get_collection_property_decoded(collection_id, CollectionType)1306 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1307 }13081309 pub fn ensure_collection_type(1310 collection_id: CollectionId,1311 collection_type: misc::CollectionType,1312 ) -> DispatchResult {1313 let actual_type = Self::get_collection_type(collection_id)?;1314 ensure!(1315 actual_type == collection_type,1316 <CommonError<T>>::NoPermission1317 );13181319 Ok(())1320 }13211322 pub fn get_typed_nft_collection(1323 collection_id: CollectionId,1324 collection_type: misc::CollectionType,1325 ) -> Result<NonfungibleHandle<T>, DispatchError> {1326 Self::ensure_collection_type(collection_id, collection_type)?;13271328 Self::get_nft_collection(collection_id)1329 }13301331 pub fn get_typed_nft_collection_mapped(1332 rmrk_collection_id: RmrkCollectionId,1333 collection_type: misc::CollectionType,1334 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1335 let unique_collection_id = match collection_type {1336 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1337 _ => rmrk_collection_id.into(),1338 };13391340 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13411342 Ok((collection, unique_collection_id))1343 }13441345 pub fn get_nft_property(1346 collection_id: CollectionId,1347 nft_id: TokenId,1348 key: RmrkProperty,1349 ) -> Result<PropertyValue, DispatchError> {1350 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1351 .get(&Self::rmrk_property_key(key)?)1352 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1353 .clone();13541355 Ok(nft_property)1356 }13571358 pub fn get_nft_property_decoded<V: Decode>(1359 collection_id: CollectionId,1360 nft_id: TokenId,1361 key: RmrkProperty,1362 ) -> Result<V, DispatchError> {1363 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1364 }13651366 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1367 <TokenData<T>>::contains_key((collection_id, nft_id))1368 }13691370 pub fn get_nft_type(1371 collection_id: CollectionId,1372 token_id: TokenId,1373 ) -> Result<NftType, DispatchError> {1374 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1375 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1376 }13771378 pub fn ensure_nft_type(1379 collection_id: CollectionId,1380 token_id: TokenId,1381 nft_type: NftType,1382 ) -> DispatchResult {1383 let actual_type = Self::get_nft_type(collection_id, token_id)?;1384 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13851386 Ok(())1387 }13881389 pub fn ensure_nft_owner(1390 collection_id: CollectionId,1391 token_id: TokenId,1392 possible_owner: &T::CrossAccountId,1393 nesting_budget: &dyn budget::Budget,1394 ) -> DispatchResult {1395 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1396 possible_owner.clone(),1397 collection_id,1398 token_id,1399 None,1400 nesting_budget,1401 )1402 .map_err(Self::map_unique_err_to_proxy)?;14031404 ensure!(is_owned, <Error<T>>::NoPermission);14051406 Ok(())1407 }14081409 pub fn filter_user_properties<Key, Value, R, Mapper>(1410 collection_id: CollectionId,1411 token_id: Option<TokenId>,1412 filter_keys: Option<Vec<RmrkPropertyKey>>,1413 mapper: Mapper,1414 ) -> Result<Vec<R>, DispatchError>1415 where1416 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1417 Value: Decode + Default,1418 Mapper: Fn(Key, Value) -> R,1419 {1420 filter_keys1421 .map(|keys| {1422 let properties = keys1423 .into_iter()1424 .filter_map(|key| {1425 let key: Key = key.try_into().ok()?;14261427 let value = match token_id {1428 Some(token_id) => Self::get_nft_property_decoded(1429 collection_id,1430 token_id,1431 UserProperty(key.as_ref()),1432 ),1433 None => Self::get_collection_property_decoded(1434 collection_id,1435 UserProperty(key.as_ref()),1436 ),1437 }1438 .ok()?;14391440 Some(mapper(key, value))1441 })1442 .collect();14431444 Ok(properties)1445 })1446 .unwrap_or_else(|| {1447 let properties =1448 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14491450 Ok(properties)1451 })1452 }14531454 pub fn iterate_user_properties<Key, Value, R, Mapper>(1455 collection_id: CollectionId,1456 token_id: Option<TokenId>,1457 mapper: Mapper,1458 ) -> Result<impl Iterator<Item = R>, DispatchError>1459 where1460 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1461 Value: Decode + Default,1462 Mapper: Fn(Key, Value) -> R,1463 {1464 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14651466 let properties = match token_id {1467 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1468 None => <PalletCommon<T>>::collection_properties(collection_id),1469 };14701471 let properties = properties.into_iter().filter_map(move |(key, value)| {1472 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14731474 let key: Key = key.to_vec().try_into().ok()?;1475 let value: Value = value.decode().ok()?;14761477 Some(mapper(key, value))1478 });14791480 Ok(properties)1481 }14821483 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1484 map_unique_err_to_proxy! {1485 match err {1486 CommonError::NoPermission => NoPermission,1487 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1488 CommonError::PublicMintingNotAllowed => NoPermission,1489 CommonError::TokenNotFound => NoAvailableNftId,1490 CommonError::ApprovedValueTooLow => NoPermission,1491 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1492 StructureError::TokenNotFound => NoAvailableNftId,1493 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1494 }1495 }1496 }1497}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::{*, mapping::TokenAddressMapping};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, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161162 /* RMRK compatible events */163 CollectionNotEmpty,164 NoAvailableCollectionId,165 NoAvailableNftId,166 CollectionUnknown,167 NoPermission,168 NonTransferable,169 CollectionFullOrLocked,170 ResourceDoesntExist,171 CannotSendToDescendentOrSelf,172 CannotAcceptNonOwnedNft,173 CannotRejectNonOwnedNft,174 ResourceNotPending,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 /// Create a collection180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingPermissions {204 token_owner: true,205 admin: false,206 restricted: None,207208 permissive: false,209 }),210 ..Default::default()211 }),212 ..Default::default()213 };214215 let unique_collection_id = Self::init_collection(216 T::CrossAccountId::from_sub(sender.clone()),217 data,218 [219 Self::rmrk_property(Metadata, &metadata)?,220 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221 ]222 .into_iter(),223 )?;224 let rmrk_collection_id = <CollectionIndex<T>>::get();225226 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);228229 <CollectionIndex<T>>::mutate(|n| *n += 1);230231 Self::deposit_event(Event::CollectionCreated {232 issuer: sender,233 collection_id: rmrk_collection_id,234 });235236 Ok(())237 }238239 /// destroy collection240 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]241 #[transactional]242 pub fn destroy_collection(243 origin: OriginFor<T>,244 collection_id: RmrkCollectionId,245 ) -> DispatchResult {246 let sender = ensure_signed(origin)?;247 let cross_sender = T::CrossAccountId::from_sub(sender.clone());248249 let collection = Self::get_typed_nft_collection(250 Self::unique_collection_id(collection_id)?,251 misc::CollectionType::Regular,252 )?;253 collection.check_is_external()?;254255 <PalletNft<T>>::destroy_collection(collection, &cross_sender)256 .map_err(Self::map_unique_err_to_proxy)?;257258 Self::deposit_event(Event::CollectionDestroyed {259 issuer: sender,260 collection_id,261 });262263 Ok(())264 }265266 /// Change the issuer of a collection267 ///268 /// Parameters:269 /// - `origin`: sender of the transaction270 /// - `collection_id`: collection id of the nft to change issuer of271 /// - `new_issuer`: Collection's new issuer272 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]273 #[transactional]274 pub fn change_collection_issuer(275 origin: OriginFor<T>,276 collection_id: RmrkCollectionId,277 new_issuer: <T::Lookup as StaticLookup>::Source,278 ) -> DispatchResult {279 let sender = ensure_signed(origin)?;280281 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;282 collection.check_is_external()?;283284 let new_issuer = T::Lookup::lookup(new_issuer)?;285286 Self::change_collection_owner(287 Self::unique_collection_id(collection_id)?,288 misc::CollectionType::Regular,289 sender.clone(),290 new_issuer.clone(),291 )?;292293 Self::deposit_event(Event::IssuerChanged {294 old_issuer: sender,295 new_issuer,296 collection_id,297 });298299 Ok(())300 }301302 /// lock collection303 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304 #[transactional]305 pub fn lock_collection(306 origin: OriginFor<T>,307 collection_id: RmrkCollectionId,308 ) -> DispatchResult {309 let sender = ensure_signed(origin)?;310 let cross_sender = T::CrossAccountId::from_sub(sender.clone());311312 let collection = Self::get_typed_nft_collection(313 Self::unique_collection_id(collection_id)?,314 misc::CollectionType::Regular,315 )?;316 collection.check_is_external()?;317318 Self::check_collection_owner(&collection, &cross_sender)?;319320 let token_count = collection.total_supply();321322 let mut collection = collection.into_inner();323 collection.limits.token_limit = Some(token_count);324 collection.save()?;325326 Self::deposit_event(Event::CollectionLocked {327 issuer: sender,328 collection_id,329 });330331 Ok(())332 }333334 /// Mints an NFT in the specified collection335 /// Sets metadata and the royalty attribute336 ///337 /// Parameters:338 /// - `collection_id`: The class of the asset to be minted.339 /// - `nft_id`: The nft value of the asset to be minted.340 /// - `recipient`: Receiver of the royalty341 /// - `royalty`: Permillage reward from each trade for the Recipient342 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash343 /// - `transferable`: Ability to transfer this NFT344 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345 #[transactional]346 pub fn mint_nft(347 origin: OriginFor<T>,348 owner: T::AccountId,349 collection_id: RmrkCollectionId,350 recipient: Option<T::AccountId>,351 royalty_amount: Option<Permill>,352 metadata: RmrkString,353 transferable: bool,354 ) -> DispatchResult {355 let sender = ensure_signed(origin)?;356 let sender = T::CrossAccountId::from_sub(sender);357 let cross_owner = T::CrossAccountId::from_sub(owner.clone());358359 let collection = Self::get_typed_nft_collection(360 Self::unique_collection_id(collection_id)?,361 misc::CollectionType::Regular,362 )?;363 collection.check_is_external()?;364365 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {366 recipient: recipient.unwrap_or_else(|| owner.clone()),367 amount,368 });369370 let nft_id = Self::create_nft(371 &sender,372 &cross_owner,373 &collection,374 [375 Self::rmrk_property(TokenType, &NftType::Regular)?,376 Self::rmrk_property(Transferable, &transferable)?,377 Self::rmrk_property(PendingNftAccept, &false)?,378 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,379 Self::rmrk_property(Metadata, &metadata)?,380 Self::rmrk_property(Equipped, &false)?,381 Self::rmrk_property(382 ResourceCollection,383 &Self::init_collection(384 sender.clone(),385 CreateCollectionData {386 ..Default::default()387 },388 [Self::rmrk_property(389 CollectionType,390 &misc::CollectionType::Resource,391 )?]392 .into_iter(),393 )?,394 )?, // todo possibly add limits to the collection if rmrk warrants them395 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,396 ]397 .into_iter(),398 )399 .map_err(|err| match err {400 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),401 err => Self::map_unique_err_to_proxy(err),402 })?;403404 Self::deposit_event(Event::NftMinted {405 owner,406 collection_id,407 nft_id: nft_id.0,408 });409410 Ok(())411 }412413 /// burn nft414 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]415 #[transactional]416 pub fn burn_nft(417 origin: OriginFor<T>,418 collection_id: RmrkCollectionId,419 nft_id: RmrkNftId,420 max_burns: u32,421 ) -> DispatchResult {422 let sender = ensure_signed(origin)?;423 let cross_sender = T::CrossAccountId::from_sub(sender.clone());424425 let collection = Self::get_typed_nft_collection(426 Self::unique_collection_id(collection_id)?,427 misc::CollectionType::Regular,428 )?;429 collection.check_is_external()?;430431 Self::destroy_nft(432 cross_sender,433 Self::unique_collection_id(collection_id)?,434 nft_id.into(),435 max_burns,436 <Error<T>>::NoPermission,437 )438 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;439440 Self::deposit_event(Event::NFTBurned {441 owner: sender,442 nft_id,443 });444445 Ok(())446 }447448 /// Transfers a NFT from an Account or NFT A to another Account or NFT B449 ///450 /// Parameters:451 /// - `origin`: sender of the transaction452 /// - `rmrk_collection_id`: collection id of the nft to be transferred453 /// - `rmrk_nft_id`: nft id of the nft to be transferred454 /// - `new_owner`: new owner of the nft which can be either an account or a NFT455 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]456 #[transactional]457 pub fn send(458 origin: OriginFor<T>,459 rmrk_collection_id: RmrkCollectionId,460 rmrk_nft_id: RmrkNftId,461 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,462 ) -> DispatchResult {463 let sender = ensure_signed(origin.clone())?;464 let cross_sender = T::CrossAccountId::from_sub(sender.clone());465466 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;467 let nft_id = rmrk_nft_id.into();468469 let collection =470 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;471 collection.check_is_external()?;472473 let token_data =474 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;475476 let from = token_data.owner;477478 ensure!(479 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,480 <Error<T>>::NonTransferable481 );482483 ensure!(484 !Self::get_nft_property_decoded(485 collection_id,486 nft_id,487 RmrkProperty::PendingNftAccept488 )?,489 <Error<T>>::NoPermission490 );491492 let target_owner;493 let approval_required;494495 match new_owner {496 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {497 target_owner = T::CrossAccountId::from_sub(account_id.clone());498 approval_required = false;499 }500 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(501 target_collection_id,502 target_nft_id,503 ) => {504 let target_collection_id = Self::unique_collection_id(target_collection_id)?;505506 let target_nft_budget = budget::Value::new(NESTING_BUDGET);507508 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(509 target_collection_id,510 target_nft_id.into(),511 Some((collection_id, nft_id)),512 &target_nft_budget,513 )514 .map_err(Self::map_unique_err_to_proxy)?;515516 approval_required = cross_sender != target_nft_owner;517518 if approval_required {519 target_owner = target_nft_owner;520521 <PalletNft<T>>::set_scoped_token_property(522 collection.id,523 nft_id,524 PropertyScope::Rmrk,525 Self::rmrk_property(PendingNftAccept, &approval_required)?,526 )?;527 } else {528 target_owner = T::CrossTokenAddressMapping::token_to_address(529 target_collection_id,530 target_nft_id.into(),531 );532 }533 }534 }535536 let src_nft_budget = budget::Value::new(NESTING_BUDGET);537538 <PalletNft<T>>::transfer_from(539 &collection,540 &cross_sender,541 &from,542 &target_owner,543 nft_id,544 &src_nft_budget,545 )546 .map_err(Self::map_unique_err_to_proxy)?;547548 Self::deposit_event(Event::NFTSent {549 sender,550 recipient: new_owner,551 collection_id: rmrk_collection_id,552 nft_id: rmrk_nft_id,553 approval_required,554 });555556 Ok(())557 }558559 /// Accepts an NFT sent from another account to self or owned NFT560 ///561 /// Parameters:562 /// - `origin`: sender of the transaction563 /// - `rmrk_collection_id`: collection id of the nft to be accepted564 /// - `rmrk_nft_id`: nft id of the nft to be accepted565 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was566 /// sent to567 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]568 #[transactional]569 pub fn accept_nft(570 origin: OriginFor<T>,571 rmrk_collection_id: RmrkCollectionId,572 rmrk_nft_id: RmrkNftId,573 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,574 ) -> DispatchResult {575 let sender = ensure_signed(origin.clone())?;576 let cross_sender = T::CrossAccountId::from_sub(sender.clone());577578 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;579 let nft_id = rmrk_nft_id.into();580581 let collection =582 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;583 collection.check_is_external()?;584585 let new_cross_owner = match new_owner {586 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {587 T::CrossAccountId::from_sub(account_id.clone())588 }589 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(590 target_collection_id,591 target_nft_id,592 ) => {593 let target_collection_id = Self::unique_collection_id(target_collection_id)?;594595 T::CrossTokenAddressMapping::token_to_address(596 target_collection_id,597 TokenId(target_nft_id),598 )599 }600 };601602 let budget = budget::Value::new(NESTING_BUDGET);603604 <PalletNft<T>>::transfer(605 &collection,606 &cross_sender,607 &new_cross_owner,608 nft_id,609 &budget,610 )611 .map_err(|err| {612 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {613 <Error<T>>::CannotAcceptNonOwnedNft.into()614 } else {615 Self::map_unique_err_to_proxy(err)616 }617 })?;618619 <PalletNft<T>>::set_scoped_token_property(620 collection.id,621 nft_id,622 PropertyScope::Rmrk,623 Self::rmrk_property(PendingNftAccept, &false)?,624 )?;625626 Self::deposit_event(Event::NFTAccepted {627 sender,628 recipient: new_owner,629 collection_id: rmrk_collection_id,630 nft_id: rmrk_nft_id,631 });632633 Ok(())634 }635636 /// Rejects an NFT sent from another account to self or owned NFT637 ///638 /// Parameters:639 /// - `origin`: sender of the transaction640 /// - `rmrk_collection_id`: collection id of the nft to be accepted641 /// - `rmrk_nft_id`: nft id of the nft to be accepted642 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]643 #[transactional]644 pub fn reject_nft(645 origin: OriginFor<T>,646 rmrk_collection_id: RmrkCollectionId,647 rmrk_nft_id: RmrkNftId,648 max_burns: u32,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let cross_sender = T::CrossAccountId::from_sub(sender.clone());652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let nft_id = rmrk_nft_id.into();655656 let collection =657 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;658 collection.check_is_external()?;659660 Self::destroy_nft(661 cross_sender,662 collection_id,663 nft_id,664 max_burns,665 <Error<T>>::CannotRejectNonOwnedNft,666 ).map_err(|err| Self::map_unique_err_to_proxy(err.error))?;667668 Self::deposit_event(Event::NFTRejected {669 sender,670 collection_id: rmrk_collection_id,671 nft_id: rmrk_nft_id,672 });673674 Ok(())675 }676677 /// accept the addition of a new resource to an existing NFT678 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]679 #[transactional]680 pub fn accept_resource(681 origin: OriginFor<T>,682 rmrk_collection_id: RmrkCollectionId,683 rmrk_nft_id: RmrkNftId,684 rmrk_resource_id: RmrkResourceId,685 ) -> DispatchResult {686 let sender = ensure_signed(origin)?;687 let cross_sender = T::CrossAccountId::from_sub(sender);688689 let collection_id = Self::unique_collection_id(rmrk_collection_id)690 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;691 let collection =692 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;693 collection.check_is_external()?;694695 let nft_id = rmrk_nft_id.into();696 let resource_id = rmrk_resource_id.into();697698 let budget = budget::Value::new(NESTING_BUDGET);699700 let nft_owner =701 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)702 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;703704 let resource_collection_id: CollectionId =705 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)706 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;707708 let is_pending: bool = Self::get_nft_property_decoded(709 resource_collection_id,710 resource_id,711 PendingResourceAccept,712 )713 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;714715 ensure!(is_pending, <Error<T>>::ResourceNotPending);716717 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);718719 <PalletNft<T>>::set_scoped_token_property(720 resource_collection_id,721 rmrk_resource_id.into(),722 PropertyScope::Rmrk,723 Self::rmrk_property(PendingResourceAccept, &false)?,724 )?;725726 Self::deposit_event(Event::<T>::ResourceAccepted {727 nft_id: rmrk_nft_id,728 resource_id: rmrk_resource_id,729 });730731 Ok(())732 }733734 /// accept the removal of a resource of an existing NFT735 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]736 #[transactional]737 pub fn accept_resource_removal(738 origin: OriginFor<T>,739 rmrk_collection_id: RmrkCollectionId,740 rmrk_nft_id: RmrkNftId,741 rmrk_resource_id: RmrkResourceId,742 ) -> DispatchResult {743 let sender = ensure_signed(origin)?;744 let cross_sender = T::CrossAccountId::from_sub(sender);745746 let collection_id = Self::unique_collection_id(rmrk_collection_id)747 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;748 let collection =749 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;750 collection.check_is_external()?;751752 let nft_id = rmrk_nft_id.into();753 let resource_id = rmrk_resource_id.into();754755 let budget = budget::Value::new(NESTING_BUDGET);756757 let nft_owner =758 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)759 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;760761 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);762763 let resource_collection_id: CollectionId =764 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)765 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;766767 let is_pending: bool = Self::get_nft_property_decoded(768 resource_collection_id,769 resource_id,770 PendingResourceRemoval,771 )772 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;773774 ensure!(is_pending, <Error<T>>::ResourceNotPending);775776 let resource_collection = Self::get_typed_nft_collection(777 resource_collection_id,778 misc::CollectionType::Resource,779 )?;780781 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())782 .map_err(Self::map_unique_err_to_proxy)?;783784 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {785 nft_id: rmrk_nft_id,786 resource_id: rmrk_resource_id,787 });788789 Ok(())790 }791792 /// set a custom value on an NFT793 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794 #[transactional]795 pub fn set_property(796 origin: OriginFor<T>,797 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,798 maybe_nft_id: Option<RmrkNftId>,799 key: RmrkKeyString,800 value: RmrkValueString,801 ) -> DispatchResult {802 let sender = ensure_signed(origin)?;803 let sender = T::CrossAccountId::from_sub(sender);804805 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;806 let collection =807 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;808 collection.check_is_external()?;809810 let budget = budget::Value::new(NESTING_BUDGET);811812 match maybe_nft_id {813 Some(nft_id) => {814 let token_id: TokenId = nft_id.into();815816 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;817 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;818819 <PalletNft<T>>::set_scoped_token_property(820 collection_id,821 token_id,822 PropertyScope::Rmrk,823 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,824 )?;825 }826 None => {827 let collection = Self::get_typed_nft_collection(828 collection_id,829 misc::CollectionType::Regular,830 )?;831832 Self::check_collection_owner(&collection, &sender)?;833834 <PalletCommon<T>>::set_scoped_collection_property(835 collection_id,836 PropertyScope::Rmrk,837 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,838 )?;839 }840 }841842 Self::deposit_event(Event::PropertySet {843 collection_id: rmrk_collection_id,844 maybe_nft_id,845 key,846 value,847 });848849 Ok(())850 }851852 /// set a different order of resource priority853 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]854 #[transactional]855 pub fn set_priority(856 origin: OriginFor<T>,857 rmrk_collection_id: RmrkCollectionId,858 rmrk_nft_id: RmrkNftId,859 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,860 ) -> DispatchResult {861 let sender = ensure_signed(origin)?;862 let sender = T::CrossAccountId::from_sub(sender);863864 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865 let nft_id = rmrk_nft_id.into();866867 let collection =868 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869 collection.check_is_external()?;870871 let budget = budget::Value::new(NESTING_BUDGET);872873 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;874 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;875876 <PalletNft<T>>::set_scoped_token_property(877 collection_id,878 nft_id,879 PropertyScope::Rmrk,880 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,881 )?;882883 Self::deposit_event(Event::<T>::PrioritySet {884 collection_id: rmrk_collection_id,885 nft_id: rmrk_nft_id,886 });887888 Ok(())889 }890891 /// Create basic resource892 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]893 #[transactional]894 pub fn add_basic_resource(895 origin: OriginFor<T>,896 rmrk_collection_id: RmrkCollectionId,897 nft_id: RmrkNftId,898 resource: RmrkBasicResource,899 ) -> DispatchResult {900 let sender = ensure_signed(origin.clone())?;901902 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903 let collection =904 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905 collection.check_is_external()?;906907 let resource_id = Self::resource_add(908 sender,909 collection_id,910 nft_id.into(),911 [912 Self::rmrk_property(TokenType, &NftType::Resource)?,913 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,914 Self::rmrk_property(Src, &resource.src)?,915 Self::rmrk_property(Metadata, &resource.metadata)?,916 Self::rmrk_property(License, &resource.license)?,917 Self::rmrk_property(Thumb, &resource.thumb)?,918 ]919 .into_iter(),920 )?;921922 Self::deposit_event(Event::ResourceAdded {923 nft_id,924 resource_id,925 });926 Ok(())927 }928929 /// Create composable resource930 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]931 #[transactional]932 pub fn add_composable_resource(933 origin: OriginFor<T>,934 rmrk_collection_id: RmrkCollectionId,935 nft_id: RmrkNftId,936 _resource_id: RmrkBoundedResource,937 resource: RmrkComposableResource,938 ) -> DispatchResult {939 let sender = ensure_signed(origin.clone())?;940941 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;942 let collection =943 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;944 collection.check_is_external()?;945946 let resource_id = Self::resource_add(947 sender,948 collection_id,949 nft_id.into(),950 [951 Self::rmrk_property(TokenType, &NftType::Resource)?,952 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,953 Self::rmrk_property(Parts, &resource.parts)?,954 Self::rmrk_property(Base, &resource.base)?,955 Self::rmrk_property(Src, &resource.src)?,956 Self::rmrk_property(Metadata, &resource.metadata)?,957 Self::rmrk_property(License, &resource.license)?,958 Self::rmrk_property(Thumb, &resource.thumb)?,959 ]960 .into_iter(),961 )?;962963 Self::deposit_event(Event::ResourceAdded {964 nft_id,965 resource_id,966 });967 Ok(())968 }969970 /// Create slot resource971 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]972 #[transactional]973 pub fn add_slot_resource(974 origin: OriginFor<T>,975 rmrk_collection_id: RmrkCollectionId,976 nft_id: RmrkNftId,977 resource: RmrkSlotResource,978 ) -> DispatchResult {979 let sender = ensure_signed(origin.clone())?;980981 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;982 let collection =983 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;984 collection.check_is_external()?;985986 let resource_id = Self::resource_add(987 sender,988 collection_id,989 nft_id.into(),990 [991 Self::rmrk_property(TokenType, &NftType::Resource)?,992 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,993 Self::rmrk_property(Base, &resource.base)?,994 Self::rmrk_property(Src, &resource.src)?,995 Self::rmrk_property(Metadata, &resource.metadata)?,996 Self::rmrk_property(Slot, &resource.slot)?,997 Self::rmrk_property(License, &resource.license)?,998 Self::rmrk_property(Thumb, &resource.thumb)?,999 ]1000 .into_iter(),1001 )?;10021003 Self::deposit_event(Event::ResourceAdded {1004 nft_id,1005 resource_id,1006 });1007 Ok(())1008 }10091010 /// remove resource1011 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1012 #[transactional]1013 pub fn remove_resource(1014 origin: OriginFor<T>,1015 rmrk_collection_id: RmrkCollectionId,1016 nft_id: RmrkNftId,1017 resource_id: RmrkResourceId,1018 ) -> DispatchResult {1019 let sender = ensure_signed(origin.clone())?;10201021 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1022 let collection =1023 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1024 collection.check_is_external()?;10251026 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10271028 Self::deposit_event(Event::ResourceRemoval {1029 nft_id,1030 resource_id,1031 });1032 Ok(())1033 }1034 }1035}10361037impl<T: Config> Pallet<T> {1038 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1039 let key = rmrk_key.to_key::<T>()?;10401041 let scoped_key = PropertyScope::Rmrk1042 .apply(key)1043 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10441045 Ok(scoped_key)1046 }10471048 // todo think about renaming these1049 pub fn rmrk_property<E: Encode>(1050 rmrk_key: RmrkProperty,1051 value: &E,1052 ) -> Result<Property, DispatchError> {1053 let key = rmrk_key.to_key::<T>()?;10541055 let value = value1056 .encode()1057 .try_into()1058 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10591060 let property = Property { key, value };10611062 Ok(property)1063 }10641065 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1066 vec.decode()1067 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1068 }10691070 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1071 where1072 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1073 {1074 vec.rebind()1075 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1076 }10771078 fn init_collection(1079 sender: T::CrossAccountId,1080 data: CreateCollectionData<T::AccountId>,1081 properties: impl Iterator<Item = Property>,1082 ) -> Result<CollectionId, DispatchError> {1083 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10841085 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1086 return Err(<Error<T>>::NoAvailableCollectionId.into());1087 }10881089 <PalletCommon<T>>::set_scoped_collection_properties(1090 collection_id?,1091 PropertyScope::Rmrk,1092 properties,1093 )?;10941095 collection_id1096 }10971098 pub fn create_nft(1099 sender: &T::CrossAccountId,1100 owner: &T::CrossAccountId,1101 collection: &NonfungibleHandle<T>,1102 properties: impl Iterator<Item = Property>,1103 ) -> Result<TokenId, DispatchError> {1104 let data = CreateNftExData {1105 properties: BoundedVec::default(),1106 owner: owner.clone(),1107 };11081109 let budget = budget::Value::new(NESTING_BUDGET);11101111 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11121113 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11141115 <PalletNft<T>>::set_scoped_token_properties(1116 collection.id,1117 nft_id,1118 PropertyScope::Rmrk,1119 properties,1120 )?;11211122 Ok(nft_id)1123 }11241125 fn destroy_nft(1126 sender: T::CrossAccountId,1127 collection_id: CollectionId,1128 token_id: TokenId,1129 max_burns: u32,1130 error_if_not_owned: Error<T>,1131 ) -> DispatchResultWithPostInfo {1132 let collection =1133 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11341135 let token_data =1136 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11371138 let from = token_data.owner;11391140 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11411142 ensure!(1143 <PalletStructure<T>>::check_indirectly_owned(1144 sender.clone(),1145 collection_id,1146 token_id,1147 None,1148 &owner_check_budget1149 )?,1150 error_if_not_owned,1151 );11521153 let burns_budget = budget::Value::new(max_burns);1154 let breadth_budget = budget::Value::new(max_burns);11551156 <PalletNft<T>>::burn_recursively(&collection, &from, token_id, &burns_budget, &breadth_budget)1157 }11581159 fn resource_add(1160 sender: T::AccountId,1161 collection_id: CollectionId,1162 token_id: TokenId,1163 resource_properties: impl Iterator<Item = Property>,1164 ) -> Result<RmrkResourceId, DispatchError> {1165 let collection =1166 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1167 ensure!(collection.owner == sender, Error::<T>::NoPermission);11681169 let sender = T::CrossAccountId::from_sub(sender);1170 let budget = budget::Value::new(NESTING_BUDGET);11711172 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1173 .map_err(Self::map_unique_err_to_proxy)?;11741175 let pending = sender != nft_owner;11761177 let resource_collection_id: CollectionId =1178 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1179 let resource_collection =1180 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11811182 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11831184 let resource_id = Self::create_nft(1185 &sender,1186 &nft_owner,1187 &resource_collection,1188 resource_properties.chain(1189 [1190 Self::rmrk_property(PendingResourceAccept, &pending)?,1191 Self::rmrk_property(PendingResourceRemoval, &false)?,1192 ]1193 .into_iter(),1194 ),1195 )1196 .map_err(|err| match err {1197 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1198 err => Self::map_unique_err_to_proxy(err),1199 })?;12001201 Ok(resource_id.0)1202 }12031204 fn resource_remove(1205 sender: T::AccountId,1206 collection_id: CollectionId,1207 nft_id: TokenId,1208 resource_id: TokenId,1209 ) -> DispatchResult {1210 let collection =1211 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1212 ensure!(collection.owner == sender, Error::<T>::NoPermission);12131214 let resource_collection_id: CollectionId =1215 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1216 let resource_collection =1217 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1218 ensure!(1219 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1220 Error::<T>::ResourceDoesntExist1221 );12221223 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1224 let topmost_owner =1225 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12261227 let sender = T::CrossAccountId::from_sub(sender);1228 if topmost_owner == sender {1229 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1230 .map_err(Self::map_unique_err_to_proxy)?;1231 } else {1232 <PalletNft<T>>::set_scoped_token_property(1233 resource_collection_id,1234 resource_id,1235 PropertyScope::Rmrk,1236 Self::rmrk_property(PendingResourceRemoval, &true)?,1237 )?;1238 }12391240 Ok(())1241 }12421243 fn change_collection_owner(1244 collection_id: CollectionId,1245 collection_type: misc::CollectionType,1246 sender: T::AccountId,1247 new_owner: T::AccountId,1248 ) -> DispatchResult {1249 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1250 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12511252 let mut collection = collection.into_inner();12531254 collection.owner = new_owner;1255 collection.save()1256 }12571258 fn check_collection_owner(1259 collection: &NonfungibleHandle<T>,1260 account: &T::CrossAccountId,1261 ) -> DispatchResult {1262 collection1263 .check_is_owner(account)1264 .map_err(Self::map_unique_err_to_proxy)1265 }12661267 pub fn last_collection_idx() -> RmrkCollectionId {1268 <CollectionIndex<T>>::get()1269 }12701271 pub fn unique_collection_id(1272 rmrk_collection_id: RmrkCollectionId,1273 ) -> Result<CollectionId, DispatchError> {1274 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1275 .map_err(|_| <Error<T>>::CollectionUnknown.into())1276 }12771278 pub fn rmrk_collection_id(1279 unique_collection_id: CollectionId,1280 ) -> Result<RmrkCollectionId, DispatchError> {1281 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1282 .map_err(|_| <Error<T>>::CollectionUnknown.into())1283 }12841285 pub fn get_nft_collection(1286 collection_id: CollectionId,1287 ) -> Result<NonfungibleHandle<T>, DispatchError> {1288 let collection = <CollectionHandle<T>>::try_get(collection_id)1289 .map_err(|_| <Error<T>>::CollectionUnknown)?;12901291 match collection.mode {1292 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1293 _ => Err(<Error<T>>::CollectionUnknown.into()),1294 }1295 }12961297 pub fn collection_exists(collection_id: CollectionId) -> bool {1298 <CollectionHandle<T>>::try_get(collection_id).is_ok()1299 }13001301 pub fn get_collection_property(1302 collection_id: CollectionId,1303 key: RmrkProperty,1304 ) -> Result<PropertyValue, DispatchError> {1305 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1306 .get(&Self::rmrk_property_key(key)?)1307 .ok_or(<Error<T>>::CollectionUnknown)?1308 .clone();13091310 Ok(collection_property)1311 }13121313 pub fn get_collection_property_decoded<V: Decode>(1314 collection_id: CollectionId,1315 key: RmrkProperty,1316 ) -> Result<V, DispatchError> {1317 Self::decode_property(Self::get_collection_property(collection_id, key)?)1318 }13191320 pub fn get_collection_type(1321 collection_id: CollectionId,1322 ) -> Result<misc::CollectionType, DispatchError> {1323 Self::get_collection_property_decoded(collection_id, CollectionType)1324 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1325 }13261327 pub fn ensure_collection_type(1328 collection_id: CollectionId,1329 collection_type: misc::CollectionType,1330 ) -> DispatchResult {1331 let actual_type = Self::get_collection_type(collection_id)?;1332 ensure!(1333 actual_type == collection_type,1334 <CommonError<T>>::NoPermission1335 );13361337 Ok(())1338 }13391340 pub fn get_typed_nft_collection(1341 collection_id: CollectionId,1342 collection_type: misc::CollectionType,1343 ) -> Result<NonfungibleHandle<T>, DispatchError> {1344 Self::ensure_collection_type(collection_id, collection_type)?;13451346 Self::get_nft_collection(collection_id)1347 }13481349 pub fn get_typed_nft_collection_mapped(1350 rmrk_collection_id: RmrkCollectionId,1351 collection_type: misc::CollectionType,1352 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1353 let unique_collection_id = match collection_type {1354 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1355 _ => rmrk_collection_id.into(),1356 };13571358 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13591360 Ok((collection, unique_collection_id))1361 }13621363 pub fn get_nft_property(1364 collection_id: CollectionId,1365 nft_id: TokenId,1366 key: RmrkProperty,1367 ) -> Result<PropertyValue, DispatchError> {1368 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1369 .get(&Self::rmrk_property_key(key)?)1370 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1371 .clone();13721373 Ok(nft_property)1374 }13751376 pub fn get_nft_property_decoded<V: Decode>(1377 collection_id: CollectionId,1378 nft_id: TokenId,1379 key: RmrkProperty,1380 ) -> Result<V, DispatchError> {1381 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1382 }13831384 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1385 <TokenData<T>>::contains_key((collection_id, nft_id))1386 }13871388 pub fn get_nft_type(1389 collection_id: CollectionId,1390 token_id: TokenId,1391 ) -> Result<NftType, DispatchError> {1392 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1393 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1394 }13951396 pub fn ensure_nft_type(1397 collection_id: CollectionId,1398 token_id: TokenId,1399 nft_type: NftType,1400 ) -> DispatchResult {1401 let actual_type = Self::get_nft_type(collection_id, token_id)?;1402 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14031404 Ok(())1405 }14061407 pub fn ensure_nft_owner(1408 collection_id: CollectionId,1409 token_id: TokenId,1410 possible_owner: &T::CrossAccountId,1411 nesting_budget: &dyn budget::Budget,1412 ) -> DispatchResult {1413 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1414 possible_owner.clone(),1415 collection_id,1416 token_id,1417 None,1418 nesting_budget,1419 )1420 .map_err(Self::map_unique_err_to_proxy)?;14211422 ensure!(is_owned, <Error<T>>::NoPermission);14231424 Ok(())1425 }14261427 pub fn filter_user_properties<Key, Value, R, Mapper>(1428 collection_id: CollectionId,1429 token_id: Option<TokenId>,1430 filter_keys: Option<Vec<RmrkPropertyKey>>,1431 mapper: Mapper,1432 ) -> Result<Vec<R>, DispatchError>1433 where1434 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1435 Value: Decode + Default,1436 Mapper: Fn(Key, Value) -> R,1437 {1438 filter_keys1439 .map(|keys| {1440 let properties = keys1441 .into_iter()1442 .filter_map(|key| {1443 let key: Key = key.try_into().ok()?;14441445 let value = match token_id {1446 Some(token_id) => Self::get_nft_property_decoded(1447 collection_id,1448 token_id,1449 UserProperty(key.as_ref()),1450 ),1451 None => Self::get_collection_property_decoded(1452 collection_id,1453 UserProperty(key.as_ref()),1454 ),1455 }1456 .ok()?;14571458 Some(mapper(key, value))1459 })1460 .collect();14611462 Ok(properties)1463 })1464 .unwrap_or_else(|| {1465 let properties =1466 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14671468 Ok(properties)1469 })1470 }14711472 pub fn iterate_user_properties<Key, Value, R, Mapper>(1473 collection_id: CollectionId,1474 token_id: Option<TokenId>,1475 mapper: Mapper,1476 ) -> Result<impl Iterator<Item = R>, DispatchError>1477 where1478 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1479 Value: Decode + Default,1480 Mapper: Fn(Key, Value) -> R,1481 {1482 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14831484 let properties = match token_id {1485 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1486 None => <PalletCommon<T>>::collection_properties(collection_id),1487 };14881489 let properties = properties.into_iter().filter_map(move |(key, value)| {1490 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14911492 let key: Key = key.to_vec().try_into().ok()?;1493 let value: Value = value.decode().ok()?;14941495 Some(mapper(key, value))1496 });14971498 Ok(properties)1499 }15001501 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1502 map_unique_err_to_proxy! {1503 match err {1504 CommonError::NoPermission => NoPermission,1505 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1506 CommonError::PublicMintingNotAllowed => NoPermission,1507 CommonError::TokenNotFound => NoAvailableNftId,1508 CommonError::ApprovedValueTooLow => NoPermission,1509 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1510 StructureError::TokenNotFound => NoAvailableNftId,1511 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1512 }1513 }1514 }1515}