difftreelog
feat(rmrk) make resources lazy
in: master
2 files 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 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 )667 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;668669 Self::deposit_event(Event::NFTRejected {670 sender,671 collection_id: rmrk_collection_id,672 nft_id: rmrk_nft_id,673 });674675 Ok(())676 }677678 /// accept the addition of a new resource to an existing NFT679 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]680 #[transactional]681 pub fn accept_resource(682 origin: OriginFor<T>,683 rmrk_collection_id: RmrkCollectionId,684 rmrk_nft_id: RmrkNftId,685 rmrk_resource_id: RmrkResourceId,686 ) -> DispatchResult {687 let sender = ensure_signed(origin)?;688 let cross_sender = T::CrossAccountId::from_sub(sender);689690 let collection_id = Self::unique_collection_id(rmrk_collection_id)691 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;692 let collection =693 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;694 collection.check_is_external()?;695696 let nft_id = rmrk_nft_id.into();697 let resource_id = rmrk_resource_id.into();698699 let budget = budget::Value::new(NESTING_BUDGET);700701 let nft_owner =702 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)703 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;704705 let resource_collection_id: CollectionId =706 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)707 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;708709 let is_pending: bool = Self::get_nft_property_decoded(710 resource_collection_id,711 resource_id,712 PendingResourceAccept,713 )714 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716 ensure!(is_pending, <Error<T>>::ResourceNotPending);717718 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);719720 <PalletNft<T>>::set_scoped_token_property(721 resource_collection_id,722 rmrk_resource_id.into(),723 PropertyScope::Rmrk,724 Self::rmrk_property(PendingResourceAccept, &false)?,725 )?;726727 Self::deposit_event(Event::<T>::ResourceAccepted {728 nft_id: rmrk_nft_id,729 resource_id: rmrk_resource_id,730 });731732 Ok(())733 }734735 /// accept the removal of a resource of an existing NFT736 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]737 #[transactional]738 pub fn accept_resource_removal(739 origin: OriginFor<T>,740 rmrk_collection_id: RmrkCollectionId,741 rmrk_nft_id: RmrkNftId,742 rmrk_resource_id: RmrkResourceId,743 ) -> DispatchResult {744 let sender = ensure_signed(origin)?;745 let cross_sender = T::CrossAccountId::from_sub(sender);746747 let collection_id = Self::unique_collection_id(rmrk_collection_id)748 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;749 let collection =750 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;751 collection.check_is_external()?;752753 let nft_id = rmrk_nft_id.into();754 let resource_id = rmrk_resource_id.into();755756 let budget = budget::Value::new(NESTING_BUDGET);757758 let nft_owner =759 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)760 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;761762 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);763764 let resource_collection_id: CollectionId =765 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)766 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;767768 let is_pending: bool = Self::get_nft_property_decoded(769 resource_collection_id,770 resource_id,771 PendingResourceRemoval,772 )773 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;774775 ensure!(is_pending, <Error<T>>::ResourceNotPending);776777 let resource_collection = Self::get_typed_nft_collection(778 resource_collection_id,779 misc::CollectionType::Resource,780 )?;781782 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())783 .map_err(Self::map_unique_err_to_proxy)?;784785 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {786 nft_id: rmrk_nft_id,787 resource_id: rmrk_resource_id,788 });789790 Ok(())791 }792793 /// set a custom value on an NFT794 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]795 #[transactional]796 pub fn set_property(797 origin: OriginFor<T>,798 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,799 maybe_nft_id: Option<RmrkNftId>,800 key: RmrkKeyString,801 value: RmrkValueString,802 ) -> DispatchResult {803 let sender = ensure_signed(origin)?;804 let sender = T::CrossAccountId::from_sub(sender);805806 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;807 let collection =808 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809 collection.check_is_external()?;810811 let budget = budget::Value::new(NESTING_BUDGET);812813 match maybe_nft_id {814 Some(nft_id) => {815 let token_id: TokenId = nft_id.into();816817 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;818 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;819820 <PalletNft<T>>::set_scoped_token_property(821 collection_id,822 token_id,823 PropertyScope::Rmrk,824 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,825 )?;826 }827 None => {828 let collection = Self::get_typed_nft_collection(829 collection_id,830 misc::CollectionType::Regular,831 )?;832833 Self::check_collection_owner(&collection, &sender)?;834835 <PalletCommon<T>>::set_scoped_collection_property(836 collection_id,837 PropertyScope::Rmrk,838 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,839 )?;840 }841 }842843 Self::deposit_event(Event::PropertySet {844 collection_id: rmrk_collection_id,845 maybe_nft_id,846 key,847 value,848 });849850 Ok(())851 }852853 /// set a different order of resource priority854 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]855 #[transactional]856 pub fn set_priority(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,861 ) -> DispatchResult {862 let sender = ensure_signed(origin)?;863 let sender = T::CrossAccountId::from_sub(sender);864865 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;866 let nft_id = rmrk_nft_id.into();867868 let collection =869 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;870 collection.check_is_external()?;871872 let budget = budget::Value::new(NESTING_BUDGET);873874 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;875 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;876877 <PalletNft<T>>::set_scoped_token_property(878 collection_id,879 nft_id,880 PropertyScope::Rmrk,881 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,882 )?;883884 Self::deposit_event(Event::<T>::PrioritySet {885 collection_id: rmrk_collection_id,886 nft_id: rmrk_nft_id,887 });888889 Ok(())890 }891892 /// Create basic resource893 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]894 #[transactional]895 pub fn add_basic_resource(896 origin: OriginFor<T>,897 rmrk_collection_id: RmrkCollectionId,898 nft_id: RmrkNftId,899 resource: RmrkBasicResource,900 ) -> DispatchResult {901 let sender = ensure_signed(origin.clone())?;902903 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;904 let collection =905 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906 collection.check_is_external()?;907908 let resource_id = Self::resource_add(909 sender,910 collection_id,911 nft_id.into(),912 [913 Self::rmrk_property(TokenType, &NftType::Resource)?,914 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,915 Self::rmrk_property(Src, &resource.src)?,916 Self::rmrk_property(Metadata, &resource.metadata)?,917 Self::rmrk_property(License, &resource.license)?,918 Self::rmrk_property(Thumb, &resource.thumb)?,919 ]920 .into_iter(),921 )?;922923 Self::deposit_event(Event::ResourceAdded {924 nft_id,925 resource_id,926 });927 Ok(())928 }929930 /// Create composable resource931 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]932 #[transactional]933 pub fn add_composable_resource(934 origin: OriginFor<T>,935 rmrk_collection_id: RmrkCollectionId,936 nft_id: RmrkNftId,937 _resource_id: RmrkBoundedResource,938 resource: RmrkComposableResource,939 ) -> DispatchResult {940 let sender = ensure_signed(origin.clone())?;941942 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;943 let collection =944 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;945 collection.check_is_external()?;946947 let resource_id = Self::resource_add(948 sender,949 collection_id,950 nft_id.into(),951 [952 Self::rmrk_property(TokenType, &NftType::Resource)?,953 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,954 Self::rmrk_property(Parts, &resource.parts)?,955 Self::rmrk_property(Base, &resource.base)?,956 Self::rmrk_property(Src, &resource.src)?,957 Self::rmrk_property(Metadata, &resource.metadata)?,958 Self::rmrk_property(License, &resource.license)?,959 Self::rmrk_property(Thumb, &resource.thumb)?,960 ]961 .into_iter(),962 )?;963964 Self::deposit_event(Event::ResourceAdded {965 nft_id,966 resource_id,967 });968 Ok(())969 }970971 /// Create slot resource972 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]973 #[transactional]974 pub fn add_slot_resource(975 origin: OriginFor<T>,976 rmrk_collection_id: RmrkCollectionId,977 nft_id: RmrkNftId,978 resource: RmrkSlotResource,979 ) -> DispatchResult {980 let sender = ensure_signed(origin.clone())?;981982 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;983 let collection =984 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;985 collection.check_is_external()?;986987 let resource_id = Self::resource_add(988 sender,989 collection_id,990 nft_id.into(),991 [992 Self::rmrk_property(TokenType, &NftType::Resource)?,993 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,994 Self::rmrk_property(Base, &resource.base)?,995 Self::rmrk_property(Src, &resource.src)?,996 Self::rmrk_property(Metadata, &resource.metadata)?,997 Self::rmrk_property(Slot, &resource.slot)?,998 Self::rmrk_property(License, &resource.license)?,999 Self::rmrk_property(Thumb, &resource.thumb)?,1000 ]1001 .into_iter(),1002 )?;10031004 Self::deposit_event(Event::ResourceAdded {1005 nft_id,1006 resource_id,1007 });1008 Ok(())1009 }10101011 /// remove resource1012 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1013 #[transactional]1014 pub fn remove_resource(1015 origin: OriginFor<T>,1016 rmrk_collection_id: RmrkCollectionId,1017 nft_id: RmrkNftId,1018 resource_id: RmrkResourceId,1019 ) -> DispatchResult {1020 let sender = ensure_signed(origin.clone())?;10211022 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1023 let collection =1024 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1025 collection.check_is_external()?;10261027 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10281029 Self::deposit_event(Event::ResourceRemoval {1030 nft_id,1031 resource_id,1032 });1033 Ok(())1034 }1035 }1036}10371038impl<T: Config> Pallet<T> {1039 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1040 let key = rmrk_key.to_key::<T>()?;10411042 let scoped_key = PropertyScope::Rmrk1043 .apply(key)1044 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10451046 Ok(scoped_key)1047 }10481049 // todo think about renaming these1050 pub fn rmrk_property<E: Encode>(1051 rmrk_key: RmrkProperty,1052 value: &E,1053 ) -> Result<Property, DispatchError> {1054 let key = rmrk_key.to_key::<T>()?;10551056 let value = value1057 .encode()1058 .try_into()1059 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10601061 let property = Property { key, value };10621063 Ok(property)1064 }10651066 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1067 vec.decode()1068 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1069 }10701071 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1072 where1073 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1074 {1075 vec.rebind()1076 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1077 }10781079 fn init_collection(1080 sender: T::CrossAccountId,1081 data: CreateCollectionData<T::AccountId>,1082 properties: impl Iterator<Item = Property>,1083 ) -> Result<CollectionId, DispatchError> {1084 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10851086 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1087 return Err(<Error<T>>::NoAvailableCollectionId.into());1088 }10891090 <PalletCommon<T>>::set_scoped_collection_properties(1091 collection_id?,1092 PropertyScope::Rmrk,1093 properties,1094 )?;10951096 collection_id1097 }10981099 pub fn create_nft(1100 sender: &T::CrossAccountId,1101 owner: &T::CrossAccountId,1102 collection: &NonfungibleHandle<T>,1103 properties: impl Iterator<Item = Property>,1104 ) -> Result<TokenId, DispatchError> {1105 let data = CreateNftExData {1106 properties: BoundedVec::default(),1107 owner: owner.clone(),1108 };11091110 let budget = budget::Value::new(NESTING_BUDGET);11111112 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11131114 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11151116 <PalletNft<T>>::set_scoped_token_properties(1117 collection.id,1118 nft_id,1119 PropertyScope::Rmrk,1120 properties,1121 )?;11221123 Ok(nft_id)1124 }11251126 fn destroy_nft(1127 sender: T::CrossAccountId,1128 collection_id: CollectionId,1129 token_id: TokenId,1130 max_burns: u32,1131 error_if_not_owned: Error<T>,1132 ) -> DispatchResultWithPostInfo {1133 let collection =1134 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11351136 let token_data =1137 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11381139 let from = token_data.owner;11401141 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11421143 ensure!(1144 <PalletStructure<T>>::check_indirectly_owned(1145 sender.clone(),1146 collection_id,1147 token_id,1148 None,1149 &owner_check_budget1150 )?,1151 error_if_not_owned,1152 );11531154 let burns_budget = budget::Value::new(max_burns);1155 let breadth_budget = budget::Value::new(max_burns);11561157 <PalletNft<T>>::burn_recursively(1158 &collection,1159 &from,1160 token_id,1161 &burns_budget,1162 &breadth_budget,1163 )1164 }11651166 fn resource_add(1167 sender: T::AccountId,1168 collection_id: CollectionId,1169 token_id: TokenId,1170 resource_properties: impl Iterator<Item = Property>,1171 ) -> Result<RmrkResourceId, DispatchError> {1172 let collection =1173 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1174 ensure!(collection.owner == sender, Error::<T>::NoPermission);11751176 let sender = T::CrossAccountId::from_sub(sender);1177 let budget = budget::Value::new(NESTING_BUDGET);11781179 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1180 .map_err(Self::map_unique_err_to_proxy)?;11811182 let pending = sender != nft_owner;11831184 let resource_collection_id: CollectionId =1185 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1186 let resource_collection =1187 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11881189 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11901191 let resource_id = Self::create_nft(1192 &sender,1193 &nft_owner,1194 &resource_collection,1195 resource_properties.chain(1196 [1197 Self::rmrk_property(PendingResourceAccept, &pending)?,1198 Self::rmrk_property(PendingResourceRemoval, &false)?,1199 ]1200 .into_iter(),1201 ),1202 )1203 .map_err(|err| match err {1204 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1205 err => Self::map_unique_err_to_proxy(err),1206 })?;12071208 Ok(resource_id.0)1209 }12101211 fn resource_remove(1212 sender: T::AccountId,1213 collection_id: CollectionId,1214 nft_id: TokenId,1215 resource_id: TokenId,1216 ) -> DispatchResult {1217 let collection =1218 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1219 ensure!(collection.owner == sender, Error::<T>::NoPermission);12201221 let resource_collection_id: CollectionId =1222 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1223 let resource_collection =1224 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1225 ensure!(1226 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1227 Error::<T>::ResourceDoesntExist1228 );12291230 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1231 let topmost_owner =1232 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12331234 let sender = T::CrossAccountId::from_sub(sender);1235 if topmost_owner == sender {1236 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1237 .map_err(Self::map_unique_err_to_proxy)?;1238 } else {1239 <PalletNft<T>>::set_scoped_token_property(1240 resource_collection_id,1241 resource_id,1242 PropertyScope::Rmrk,1243 Self::rmrk_property(PendingResourceRemoval, &true)?,1244 )?;1245 }12461247 Ok(())1248 }12491250 fn change_collection_owner(1251 collection_id: CollectionId,1252 collection_type: misc::CollectionType,1253 sender: T::AccountId,1254 new_owner: T::AccountId,1255 ) -> DispatchResult {1256 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1257 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12581259 let mut collection = collection.into_inner();12601261 collection.owner = new_owner;1262 collection.save()1263 }12641265 fn check_collection_owner(1266 collection: &NonfungibleHandle<T>,1267 account: &T::CrossAccountId,1268 ) -> DispatchResult {1269 collection1270 .check_is_owner(account)1271 .map_err(Self::map_unique_err_to_proxy)1272 }12731274 pub fn last_collection_idx() -> RmrkCollectionId {1275 <CollectionIndex<T>>::get()1276 }12771278 pub fn unique_collection_id(1279 rmrk_collection_id: RmrkCollectionId,1280 ) -> Result<CollectionId, DispatchError> {1281 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1282 .map_err(|_| <Error<T>>::CollectionUnknown.into())1283 }12841285 pub fn rmrk_collection_id(1286 unique_collection_id: CollectionId,1287 ) -> Result<RmrkCollectionId, DispatchError> {1288 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1289 .map_err(|_| <Error<T>>::CollectionUnknown.into())1290 }12911292 pub fn get_nft_collection(1293 collection_id: CollectionId,1294 ) -> Result<NonfungibleHandle<T>, DispatchError> {1295 let collection = <CollectionHandle<T>>::try_get(collection_id)1296 .map_err(|_| <Error<T>>::CollectionUnknown)?;12971298 match collection.mode {1299 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1300 _ => Err(<Error<T>>::CollectionUnknown.into()),1301 }1302 }13031304 pub fn collection_exists(collection_id: CollectionId) -> bool {1305 <CollectionHandle<T>>::try_get(collection_id).is_ok()1306 }13071308 pub fn get_collection_property(1309 collection_id: CollectionId,1310 key: RmrkProperty,1311 ) -> Result<PropertyValue, DispatchError> {1312 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1313 .get(&Self::rmrk_property_key(key)?)1314 .ok_or(<Error<T>>::CollectionUnknown)?1315 .clone();13161317 Ok(collection_property)1318 }13191320 pub fn get_collection_property_decoded<V: Decode>(1321 collection_id: CollectionId,1322 key: RmrkProperty,1323 ) -> Result<V, DispatchError> {1324 Self::decode_property(Self::get_collection_property(collection_id, key)?)1325 }13261327 pub fn get_collection_type(1328 collection_id: CollectionId,1329 ) -> Result<misc::CollectionType, DispatchError> {1330 Self::get_collection_property_decoded(collection_id, CollectionType)1331 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1332 }13331334 pub fn ensure_collection_type(1335 collection_id: CollectionId,1336 collection_type: misc::CollectionType,1337 ) -> DispatchResult {1338 let actual_type = Self::get_collection_type(collection_id)?;1339 ensure!(1340 actual_type == collection_type,1341 <CommonError<T>>::NoPermission1342 );13431344 Ok(())1345 }13461347 pub fn get_typed_nft_collection(1348 collection_id: CollectionId,1349 collection_type: misc::CollectionType,1350 ) -> Result<NonfungibleHandle<T>, DispatchError> {1351 Self::ensure_collection_type(collection_id, collection_type)?;13521353 Self::get_nft_collection(collection_id)1354 }13551356 pub fn get_typed_nft_collection_mapped(1357 rmrk_collection_id: RmrkCollectionId,1358 collection_type: misc::CollectionType,1359 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1360 let unique_collection_id = match collection_type {1361 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1362 _ => rmrk_collection_id.into(),1363 };13641365 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13661367 Ok((collection, unique_collection_id))1368 }13691370 pub fn get_nft_property(1371 collection_id: CollectionId,1372 nft_id: TokenId,1373 key: RmrkProperty,1374 ) -> Result<PropertyValue, DispatchError> {1375 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1376 .get(&Self::rmrk_property_key(key)?)1377 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1378 .clone();13791380 Ok(nft_property)1381 }13821383 pub fn get_nft_property_decoded<V: Decode>(1384 collection_id: CollectionId,1385 nft_id: TokenId,1386 key: RmrkProperty,1387 ) -> Result<V, DispatchError> {1388 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1389 }13901391 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1392 <TokenData<T>>::contains_key((collection_id, nft_id))1393 }13941395 pub fn get_nft_type(1396 collection_id: CollectionId,1397 token_id: TokenId,1398 ) -> Result<NftType, DispatchError> {1399 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1400 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1401 }14021403 pub fn ensure_nft_type(1404 collection_id: CollectionId,1405 token_id: TokenId,1406 nft_type: NftType,1407 ) -> DispatchResult {1408 let actual_type = Self::get_nft_type(collection_id, token_id)?;1409 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14101411 Ok(())1412 }14131414 pub fn ensure_nft_owner(1415 collection_id: CollectionId,1416 token_id: TokenId,1417 possible_owner: &T::CrossAccountId,1418 nesting_budget: &dyn budget::Budget,1419 ) -> DispatchResult {1420 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1421 possible_owner.clone(),1422 collection_id,1423 token_id,1424 None,1425 nesting_budget,1426 )1427 .map_err(Self::map_unique_err_to_proxy)?;14281429 ensure!(is_owned, <Error<T>>::NoPermission);14301431 Ok(())1432 }14331434 pub fn filter_user_properties<Key, Value, R, Mapper>(1435 collection_id: CollectionId,1436 token_id: Option<TokenId>,1437 filter_keys: Option<Vec<RmrkPropertyKey>>,1438 mapper: Mapper,1439 ) -> Result<Vec<R>, DispatchError>1440 where1441 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1442 Value: Decode + Default,1443 Mapper: Fn(Key, Value) -> R,1444 {1445 filter_keys1446 .map(|keys| {1447 let properties = keys1448 .into_iter()1449 .filter_map(|key| {1450 let key: Key = key.try_into().ok()?;14511452 let value = match token_id {1453 Some(token_id) => Self::get_nft_property_decoded(1454 collection_id,1455 token_id,1456 UserProperty(key.as_ref()),1457 ),1458 None => Self::get_collection_property_decoded(1459 collection_id,1460 UserProperty(key.as_ref()),1461 ),1462 }1463 .ok()?;14641465 Some(mapper(key, value))1466 })1467 .collect();14681469 Ok(properties)1470 })1471 .unwrap_or_else(|| {1472 let properties =1473 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14741475 Ok(properties)1476 })1477 }14781479 pub fn iterate_user_properties<Key, Value, R, Mapper>(1480 collection_id: CollectionId,1481 token_id: Option<TokenId>,1482 mapper: Mapper,1483 ) -> Result<impl Iterator<Item = R>, DispatchError>1484 where1485 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1486 Value: Decode + Default,1487 Mapper: Fn(Key, Value) -> R,1488 {1489 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14901491 let properties = match token_id {1492 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1493 None => <PalletCommon<T>>::collection_properties(collection_id),1494 };14951496 let properties = properties.into_iter().filter_map(move |(key, value)| {1497 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14981499 let key: Key = key.to_vec().try_into().ok()?;1500 let value: Value = value.decode().ok()?;15011502 Some(mapper(key, value))1503 });15041505 Ok(properties)1506 }15071508 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1509 map_unique_err_to_proxy! {1510 match err {1511 CommonError::NoPermission => NoPermission,1512 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1513 CommonError::PublicMintingNotAllowed => NoPermission,1514 CommonError::TokenNotFound => NoAvailableNftId,1515 CommonError::ApprovedValueTooLow => NoPermission,1516 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1517 StructureError::TokenNotFound => NoAvailableNftId,1518 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1519 }1520 }1521 }1522}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,161 UnableToDecodeRmrkData,162163 /* RMRK compatible events */164 CollectionNotEmpty,165 NoAvailableCollectionId,166 NoAvailableNftId,167 CollectionUnknown,168 NoPermission,169 NonTransferable,170 CollectionFullOrLocked,171 ResourceDoesntExist,172 CannotSendToDescendentOrSelf,173 CannotAcceptNonOwnedNft,174 CannotRejectNonOwnedNft,175 ResourceNotPending,176 }177178 #[pallet::call]179 impl<T: Config> Pallet<T> {180 /// Create a collection181 #[transactional]182 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]183 pub fn create_collection(184 origin: OriginFor<T>,185 metadata: RmrkString,186 max: Option<u32>,187 symbol: RmrkCollectionSymbol,188 ) -> DispatchResult {189 let sender = ensure_signed(origin)?;190191 let limits = CollectionLimits {192 owner_can_transfer: Some(false),193 token_limit: max,194 ..Default::default()195 };196197 let data = CreateCollectionData {198 limits: Some(limits),199 token_prefix: symbol200 .into_inner()201 .try_into()202 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,203 permissions: Some(CollectionPermissions {204 nesting: Some(NestingPermissions {205 token_owner: true,206 admin: false,207 restricted: None,208209 permissive: false,210 }),211 ..Default::default()212 }),213 ..Default::default()214 };215216 let unique_collection_id = Self::init_collection(217 T::CrossAccountId::from_sub(sender.clone()),218 data,219 [220 Self::rmrk_property(Metadata, &metadata)?,221 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,222 ]223 .into_iter(),224 )?;225 let rmrk_collection_id = <CollectionIndex<T>>::get();226227 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);228 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);229230 <CollectionIndex<T>>::mutate(|n| *n += 1);231232 Self::deposit_event(Event::CollectionCreated {233 issuer: sender,234 collection_id: rmrk_collection_id,235 });236237 Ok(())238 }239240 /// destroy collection241 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]242 #[transactional]243 pub fn destroy_collection(244 origin: OriginFor<T>,245 collection_id: RmrkCollectionId,246 ) -> DispatchResult {247 let sender = ensure_signed(origin)?;248 let cross_sender = T::CrossAccountId::from_sub(sender.clone());249250 let collection = Self::get_typed_nft_collection(251 Self::unique_collection_id(collection_id)?,252 misc::CollectionType::Regular,253 )?;254 collection.check_is_external()?;255256 <PalletNft<T>>::destroy_collection(collection, &cross_sender)257 .map_err(Self::map_unique_err_to_proxy)?;258259 Self::deposit_event(Event::CollectionDestroyed {260 issuer: sender,261 collection_id,262 });263264 Ok(())265 }266267 /// Change the issuer of a collection268 ///269 /// Parameters:270 /// - `origin`: sender of the transaction271 /// - `collection_id`: collection id of the nft to change issuer of272 /// - `new_issuer`: Collection's new issuer273 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]274 #[transactional]275 pub fn change_collection_issuer(276 origin: OriginFor<T>,277 collection_id: RmrkCollectionId,278 new_issuer: <T::Lookup as StaticLookup>::Source,279 ) -> DispatchResult {280 let sender = ensure_signed(origin)?;281282 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;283 collection.check_is_external()?;284285 let new_issuer = T::Lookup::lookup(new_issuer)?;286287 Self::change_collection_owner(288 Self::unique_collection_id(collection_id)?,289 misc::CollectionType::Regular,290 sender.clone(),291 new_issuer.clone(),292 )?;293294 Self::deposit_event(Event::IssuerChanged {295 old_issuer: sender,296 new_issuer,297 collection_id,298 });299300 Ok(())301 }302303 /// lock collection304 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]305 #[transactional]306 pub fn lock_collection(307 origin: OriginFor<T>,308 collection_id: RmrkCollectionId,309 ) -> DispatchResult {310 let sender = ensure_signed(origin)?;311 let cross_sender = T::CrossAccountId::from_sub(sender.clone());312313 let collection = Self::get_typed_nft_collection(314 Self::unique_collection_id(collection_id)?,315 misc::CollectionType::Regular,316 )?;317 collection.check_is_external()?;318319 Self::check_collection_owner(&collection, &cross_sender)?;320321 let token_count = collection.total_supply();322323 let mut collection = collection.into_inner();324 collection.limits.token_limit = Some(token_count);325 collection.save()?;326327 Self::deposit_event(Event::CollectionLocked {328 issuer: sender,329 collection_id,330 });331332 Ok(())333 }334335 /// Mints an NFT in the specified collection336 /// Sets metadata and the royalty attribute337 ///338 /// Parameters:339 /// - `collection_id`: The class of the asset to be minted.340 /// - `nft_id`: The nft value of the asset to be minted.341 /// - `recipient`: Receiver of the royalty342 /// - `royalty`: Permillage reward from each trade for the Recipient343 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash344 /// - `transferable`: Ability to transfer this NFT345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn mint_nft(348 origin: OriginFor<T>,349 owner: T::AccountId,350 collection_id: RmrkCollectionId,351 recipient: Option<T::AccountId>,352 royalty_amount: Option<Permill>,353 metadata: RmrkString,354 transferable: bool,355 ) -> DispatchResult {356 let sender = ensure_signed(origin)?;357 let sender = T::CrossAccountId::from_sub(sender);358 let cross_owner = T::CrossAccountId::from_sub(owner.clone());359360 let collection = Self::get_typed_nft_collection(361 Self::unique_collection_id(collection_id)?,362 misc::CollectionType::Regular,363 )?;364 collection.check_is_external()?;365366 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {367 recipient: recipient.unwrap_or_else(|| owner.clone()),368 amount,369 });370371 let nft_id = Self::create_nft(372 &sender,373 &cross_owner,374 &collection,375 [376 Self::rmrk_property(TokenType, &NftType::Regular)?,377 Self::rmrk_property(Transferable, &transferable)?,378 Self::rmrk_property(PendingNftAccept, &false)?,379 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,380 Self::rmrk_property(Metadata, &metadata)?,381 Self::rmrk_property(Equipped, &false)?,382 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,383 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,384 ]385 .into_iter(),386 )387 .map_err(|err| match err {388 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),389 err => Self::map_unique_err_to_proxy(err),390 })?;391392 Self::deposit_event(Event::NftMinted {393 owner,394 collection_id,395 nft_id: nft_id.0,396 });397398 Ok(())399 }400401 /// burn nft402 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]403 #[transactional]404 pub fn burn_nft(405 origin: OriginFor<T>,406 collection_id: RmrkCollectionId,407 nft_id: RmrkNftId,408 max_burns: u32,409 ) -> DispatchResult {410 let sender = ensure_signed(origin)?;411 let cross_sender = T::CrossAccountId::from_sub(sender.clone());412413 let collection = Self::get_typed_nft_collection(414 Self::unique_collection_id(collection_id)?,415 misc::CollectionType::Regular,416 )?;417 collection.check_is_external()?;418419 Self::destroy_nft(420 cross_sender,421 Self::unique_collection_id(collection_id)?,422 nft_id.into(),423 max_burns,424 <Error<T>>::NoPermission,425 )426 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;427428 Self::deposit_event(Event::NFTBurned {429 owner: sender,430 nft_id,431 });432433 Ok(())434 }435436 /// Transfers a NFT from an Account or NFT A to another Account or NFT B437 ///438 /// Parameters:439 /// - `origin`: sender of the transaction440 /// - `rmrk_collection_id`: collection id of the nft to be transferred441 /// - `rmrk_nft_id`: nft id of the nft to be transferred442 /// - `new_owner`: new owner of the nft which can be either an account or a NFT443 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444 #[transactional]445 pub fn send(446 origin: OriginFor<T>,447 rmrk_collection_id: RmrkCollectionId,448 rmrk_nft_id: RmrkNftId,449 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,450 ) -> DispatchResult {451 let sender = ensure_signed(origin.clone())?;452 let cross_sender = T::CrossAccountId::from_sub(sender.clone());453454 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455 let nft_id = rmrk_nft_id.into();456457 let collection =458 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;459 collection.check_is_external()?;460461 let token_data =462 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;463464 let from = token_data.owner;465466 ensure!(467 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,468 <Error<T>>::NonTransferable469 );470471 ensure!(472 !Self::get_nft_property_decoded(473 collection_id,474 nft_id,475 RmrkProperty::PendingNftAccept476 )?,477 <Error<T>>::NoPermission478 );479480 let target_owner;481 let approval_required;482483 match new_owner {484 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {485 target_owner = T::CrossAccountId::from_sub(account_id.clone());486 approval_required = false;487 }488 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(489 target_collection_id,490 target_nft_id,491 ) => {492 let target_collection_id = Self::unique_collection_id(target_collection_id)?;493494 let target_nft_budget = budget::Value::new(NESTING_BUDGET);495496 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(497 target_collection_id,498 target_nft_id.into(),499 Some((collection_id, nft_id)),500 &target_nft_budget,501 )502 .map_err(Self::map_unique_err_to_proxy)?;503504 approval_required = cross_sender != target_nft_owner;505506 if approval_required {507 target_owner = target_nft_owner;508509 <PalletNft<T>>::set_scoped_token_property(510 collection.id,511 nft_id,512 PropertyScope::Rmrk,513 Self::rmrk_property(PendingNftAccept, &approval_required)?,514 )?;515 } else {516 target_owner = T::CrossTokenAddressMapping::token_to_address(517 target_collection_id,518 target_nft_id.into(),519 );520 }521 }522 }523524 let src_nft_budget = budget::Value::new(NESTING_BUDGET);525526 <PalletNft<T>>::transfer_from(527 &collection,528 &cross_sender,529 &from,530 &target_owner,531 nft_id,532 &src_nft_budget,533 )534 .map_err(Self::map_unique_err_to_proxy)?;535536 Self::deposit_event(Event::NFTSent {537 sender,538 recipient: new_owner,539 collection_id: rmrk_collection_id,540 nft_id: rmrk_nft_id,541 approval_required,542 });543544 Ok(())545 }546547 /// Accepts an NFT sent from another account to self or owned NFT548 ///549 /// Parameters:550 /// - `origin`: sender of the transaction551 /// - `rmrk_collection_id`: collection id of the nft to be accepted552 /// - `rmrk_nft_id`: nft id of the nft to be accepted553 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was554 /// sent to555 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]556 #[transactional]557 pub fn accept_nft(558 origin: OriginFor<T>,559 rmrk_collection_id: RmrkCollectionId,560 rmrk_nft_id: RmrkNftId,561 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,562 ) -> DispatchResult {563 let sender = ensure_signed(origin.clone())?;564 let cross_sender = T::CrossAccountId::from_sub(sender.clone());565566 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;567 let nft_id = rmrk_nft_id.into();568569 let collection =570 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;571 collection.check_is_external()?;572573 let new_cross_owner = match new_owner {574 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {575 T::CrossAccountId::from_sub(account_id.clone())576 }577 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(578 target_collection_id,579 target_nft_id,580 ) => {581 let target_collection_id = Self::unique_collection_id(target_collection_id)?;582583 T::CrossTokenAddressMapping::token_to_address(584 target_collection_id,585 TokenId(target_nft_id),586 )587 }588 };589590 let budget = budget::Value::new(NESTING_BUDGET);591592 <PalletNft<T>>::transfer(593 &collection,594 &cross_sender,595 &new_cross_owner,596 nft_id,597 &budget,598 )599 .map_err(|err| {600 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {601 <Error<T>>::CannotAcceptNonOwnedNft.into()602 } else {603 Self::map_unique_err_to_proxy(err)604 }605 })?;606607 <PalletNft<T>>::set_scoped_token_property(608 collection.id,609 nft_id,610 PropertyScope::Rmrk,611 Self::rmrk_property(PendingNftAccept, &false)?,612 )?;613614 Self::deposit_event(Event::NFTAccepted {615 sender,616 recipient: new_owner,617 collection_id: rmrk_collection_id,618 nft_id: rmrk_nft_id,619 });620621 Ok(())622 }623624 /// Rejects an NFT sent from another account to self or owned NFT625 ///626 /// Parameters:627 /// - `origin`: sender of the transaction628 /// - `rmrk_collection_id`: collection id of the nft to be accepted629 /// - `rmrk_nft_id`: nft id of the nft to be accepted630 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631 #[transactional]632 pub fn reject_nft(633 origin: OriginFor<T>,634 rmrk_collection_id: RmrkCollectionId,635 rmrk_nft_id: RmrkNftId,636 max_burns: u32,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642 let nft_id = rmrk_nft_id.into();643644 let collection =645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646 collection.check_is_external()?;647648 Self::destroy_nft(649 cross_sender,650 collection_id,651 nft_id,652 max_burns,653 <Error<T>>::CannotRejectNonOwnedNft,654 )655 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657 Self::deposit_event(Event::NFTRejected {658 sender,659 collection_id: rmrk_collection_id,660 nft_id: rmrk_nft_id,661 });662663 Ok(())664 }665666 /// accept the addition of a new resource to an existing NFT667 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]668 #[transactional]669 pub fn accept_resource(670 origin: OriginFor<T>,671 rmrk_collection_id: RmrkCollectionId,672 rmrk_nft_id: RmrkNftId,673 rmrk_resource_id: RmrkResourceId,674 ) -> DispatchResult {675 let sender = ensure_signed(origin)?;676 let cross_sender = T::CrossAccountId::from_sub(sender);677678 let collection_id = Self::unique_collection_id(rmrk_collection_id)679 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;680 let collection =681 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682 collection.check_is_external()?;683684 let nft_id = rmrk_nft_id.into();685 let resource_id = rmrk_resource_id.into();686687 let budget = budget::Value::new(NESTING_BUDGET);688689 let nft_owner =690 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693 let resource_collection_id: Option<CollectionId> =694 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;696 697 let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;698699 let is_pending: bool = Self::get_nft_property_decoded(700 resource_collection_id,701 resource_id,702 PendingResourceAccept,703 )704 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;705706 ensure!(is_pending, <Error<T>>::ResourceNotPending);707708 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);709710 <PalletNft<T>>::set_scoped_token_property(711 resource_collection_id,712 rmrk_resource_id.into(),713 PropertyScope::Rmrk,714 Self::rmrk_property(PendingResourceAccept, &false)?,715 )?;716717 Self::deposit_event(Event::<T>::ResourceAccepted {718 nft_id: rmrk_nft_id,719 resource_id: rmrk_resource_id,720 });721722 Ok(())723 }724725 /// accept the removal of a resource of an existing NFT726 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]727 #[transactional]728 pub fn accept_resource_removal(729 origin: OriginFor<T>,730 rmrk_collection_id: RmrkCollectionId,731 rmrk_nft_id: RmrkNftId,732 rmrk_resource_id: RmrkResourceId,733 ) -> DispatchResult {734 let sender = ensure_signed(origin)?;735 let cross_sender = T::CrossAccountId::from_sub(sender);736737 let collection_id = Self::unique_collection_id(rmrk_collection_id)738 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;739 let collection =740 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;741 collection.check_is_external()?;742743 let nft_id = rmrk_nft_id.into();744 let resource_id = rmrk_resource_id.into();745746 let budget = budget::Value::new(NESTING_BUDGET);747748 let nft_owner =749 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)750 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;751752 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754 let resource_collection_id: Option<CollectionId> =755 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)756 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;757758 let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;759760 let is_pending: bool = Self::get_nft_property_decoded(761 resource_collection_id,762 resource_id,763 PendingResourceRemoval,764 )765 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;766767 ensure!(is_pending, <Error<T>>::ResourceNotPending);768769 let resource_collection = Self::get_typed_nft_collection(770 resource_collection_id,771 misc::CollectionType::Resource,772 )?;773774 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())775 .map_err(Self::map_unique_err_to_proxy)?;776777 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {778 nft_id: rmrk_nft_id,779 resource_id: rmrk_resource_id,780 });781782 Ok(())783 }784785 /// set a custom value on an NFT786 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]787 #[transactional]788 pub fn set_property(789 origin: OriginFor<T>,790 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,791 maybe_nft_id: Option<RmrkNftId>,792 key: RmrkKeyString,793 value: RmrkValueString,794 ) -> DispatchResult {795 let sender = ensure_signed(origin)?;796 let sender = T::CrossAccountId::from_sub(sender);797798 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;799 let collection =800 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;801 collection.check_is_external()?;802803 let budget = budget::Value::new(NESTING_BUDGET);804805 match maybe_nft_id {806 Some(nft_id) => {807 let token_id: TokenId = nft_id.into();808809 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;810 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;811812 <PalletNft<T>>::set_scoped_token_property(813 collection_id,814 token_id,815 PropertyScope::Rmrk,816 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,817 )?;818 }819 None => {820 let collection = Self::get_typed_nft_collection(821 collection_id,822 misc::CollectionType::Regular,823 )?;824825 Self::check_collection_owner(&collection, &sender)?;826827 <PalletCommon<T>>::set_scoped_collection_property(828 collection_id,829 PropertyScope::Rmrk,830 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,831 )?;832 }833 }834835 Self::deposit_event(Event::PropertySet {836 collection_id: rmrk_collection_id,837 maybe_nft_id,838 key,839 value,840 });841842 Ok(())843 }844845 /// set a different order of resource priority846 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]847 #[transactional]848 pub fn set_priority(849 origin: OriginFor<T>,850 rmrk_collection_id: RmrkCollectionId,851 rmrk_nft_id: RmrkNftId,852 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,853 ) -> DispatchResult {854 let sender = ensure_signed(origin)?;855 let sender = T::CrossAccountId::from_sub(sender);856857 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;858 let nft_id = rmrk_nft_id.into();859860 let collection =861 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;862 collection.check_is_external()?;863864 let budget = budget::Value::new(NESTING_BUDGET);865866 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;867 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;868869 <PalletNft<T>>::set_scoped_token_property(870 collection_id,871 nft_id,872 PropertyScope::Rmrk,873 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,874 )?;875876 Self::deposit_event(Event::<T>::PrioritySet {877 collection_id: rmrk_collection_id,878 nft_id: rmrk_nft_id,879 });880881 Ok(())882 }883884 /// Create basic resource885 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]886 #[transactional]887 pub fn add_basic_resource(888 origin: OriginFor<T>,889 rmrk_collection_id: RmrkCollectionId,890 nft_id: RmrkNftId,891 resource: RmrkBasicResource,892 ) -> DispatchResult {893 let sender = ensure_signed(origin.clone())?;894895 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;896 let collection =897 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;898 collection.check_is_external()?;899900 let resource_id = Self::resource_add(901 sender,902 collection_id,903 nft_id.into(),904 [905 Self::rmrk_property(TokenType, &NftType::Resource)?,906 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,907 Self::rmrk_property(Src, &resource.src)?,908 Self::rmrk_property(Metadata, &resource.metadata)?,909 Self::rmrk_property(License, &resource.license)?,910 Self::rmrk_property(Thumb, &resource.thumb)?,911 ]912 .into_iter(),913 )?;914915 Self::deposit_event(Event::ResourceAdded {916 nft_id,917 resource_id,918 });919 Ok(())920 }921922 /// Create composable resource923 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]924 #[transactional]925 pub fn add_composable_resource(926 origin: OriginFor<T>,927 rmrk_collection_id: RmrkCollectionId,928 nft_id: RmrkNftId,929 _resource_id: RmrkBoundedResource,930 resource: RmrkComposableResource,931 ) -> DispatchResult {932 let sender = ensure_signed(origin.clone())?;933934 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;935 let collection =936 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937 collection.check_is_external()?;938939 let resource_id = Self::resource_add(940 sender,941 collection_id,942 nft_id.into(),943 [944 Self::rmrk_property(TokenType, &NftType::Resource)?,945 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,946 Self::rmrk_property(Parts, &resource.parts)?,947 Self::rmrk_property(Base, &resource.base)?,948 Self::rmrk_property(Src, &resource.src)?,949 Self::rmrk_property(Metadata, &resource.metadata)?,950 Self::rmrk_property(License, &resource.license)?,951 Self::rmrk_property(Thumb, &resource.thumb)?,952 ]953 .into_iter(),954 )?;955956 Self::deposit_event(Event::ResourceAdded {957 nft_id,958 resource_id,959 });960 Ok(())961 }962963 /// Create slot resource964 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]965 #[transactional]966 pub fn add_slot_resource(967 origin: OriginFor<T>,968 rmrk_collection_id: RmrkCollectionId,969 nft_id: RmrkNftId,970 resource: RmrkSlotResource,971 ) -> DispatchResult {972 let sender = ensure_signed(origin.clone())?;973974 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;975 let collection =976 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;977 collection.check_is_external()?;978979 let resource_id = Self::resource_add(980 sender,981 collection_id,982 nft_id.into(),983 [984 Self::rmrk_property(TokenType, &NftType::Resource)?,985 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,986 Self::rmrk_property(Base, &resource.base)?,987 Self::rmrk_property(Src, &resource.src)?,988 Self::rmrk_property(Metadata, &resource.metadata)?,989 Self::rmrk_property(Slot, &resource.slot)?,990 Self::rmrk_property(License, &resource.license)?,991 Self::rmrk_property(Thumb, &resource.thumb)?,992 ]993 .into_iter(),994 )?;995996 Self::deposit_event(Event::ResourceAdded {997 nft_id,998 resource_id,999 });1000 Ok(())1001 }10021003 /// remove resource1004 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1005 #[transactional]1006 pub fn remove_resource(1007 origin: OriginFor<T>,1008 rmrk_collection_id: RmrkCollectionId,1009 nft_id: RmrkNftId,1010 resource_id: RmrkResourceId,1011 ) -> DispatchResult {1012 let sender = ensure_signed(origin.clone())?;10131014 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1015 let collection =1016 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1017 collection.check_is_external()?;10181019 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10201021 Self::deposit_event(Event::ResourceRemoval {1022 nft_id,1023 resource_id,1024 });1025 Ok(())1026 }1027 }1028}10291030impl<T: Config> Pallet<T> {1031 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1032 let key = rmrk_key.to_key::<T>()?;10331034 let scoped_key = PropertyScope::Rmrk1035 .apply(key)1036 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10371038 Ok(scoped_key)1039 }10401041 // todo think about renaming these1042 pub fn rmrk_property<E: Encode>(1043 rmrk_key: RmrkProperty,1044 value: &E,1045 ) -> Result<Property, DispatchError> {1046 let key = rmrk_key.to_key::<T>()?;10471048 let value = value1049 .encode()1050 .try_into()1051 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10521053 let property = Property { key, value };10541055 Ok(property)1056 }10571058 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1059 vec.decode()1060 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1061 }10621063 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1064 where1065 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1066 {1067 vec.rebind()1068 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1069 }10701071 fn init_collection(1072 sender: T::CrossAccountId,1073 data: CreateCollectionData<T::AccountId>,1074 properties: impl Iterator<Item = Property>,1075 ) -> Result<CollectionId, DispatchError> {1076 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10771078 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1079 return Err(<Error<T>>::NoAvailableCollectionId.into());1080 }10811082 <PalletCommon<T>>::set_scoped_collection_properties(1083 collection_id?,1084 PropertyScope::Rmrk,1085 properties,1086 )?;10871088 collection_id1089 }10901091 pub fn create_nft(1092 sender: &T::CrossAccountId,1093 owner: &T::CrossAccountId,1094 collection: &NonfungibleHandle<T>,1095 properties: impl Iterator<Item = Property>,1096 ) -> Result<TokenId, DispatchError> {1097 let data = CreateNftExData {1098 properties: BoundedVec::default(),1099 owner: owner.clone(),1100 };11011102 let budget = budget::Value::new(NESTING_BUDGET);11031104 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11051106 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11071108 <PalletNft<T>>::set_scoped_token_properties(1109 collection.id,1110 nft_id,1111 PropertyScope::Rmrk,1112 properties,1113 )?;11141115 Ok(nft_id)1116 }11171118 fn destroy_nft(1119 sender: T::CrossAccountId,1120 collection_id: CollectionId,1121 token_id: TokenId,1122 max_burns: u32,1123 error_if_not_owned: Error<T>,1124 ) -> DispatchResultWithPostInfo {1125 let collection =1126 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11271128 let token_data =1129 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11301131 let from = token_data.owner;11321133 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11341135 ensure!(1136 <PalletStructure<T>>::check_indirectly_owned(1137 sender.clone(),1138 collection_id,1139 token_id,1140 None,1141 &owner_check_budget1142 )?,1143 error_if_not_owned,1144 );11451146 let burns_budget = budget::Value::new(max_burns);1147 let breadth_budget = budget::Value::new(max_burns);11481149 <PalletNft<T>>::burn_recursively(1150 &collection,1151 &from,1152 token_id,1153 &burns_budget,1154 &breadth_budget,1155 )1156 }11571158 fn resource_add(1159 sender: T::AccountId,1160 collection_id: CollectionId,1161 token_id: TokenId,1162 resource_properties: impl Iterator<Item = Property>,1163 ) -> Result<RmrkResourceId, DispatchError> {1164 let collection =1165 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1166 ensure!(collection.owner == sender, Error::<T>::NoPermission);11671168 let sender = T::CrossAccountId::from_sub(sender);1169 let budget = budget::Value::new(NESTING_BUDGET);11701171 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1172 .map_err(Self::map_unique_err_to_proxy)?;11731174 let pending = sender != nft_owner;11751176 let resource_collection_id: Option<CollectionId> =1177 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11781179 let resource_collection_id = match resource_collection_id {1180 Some(id) => id,1181 None => {1182 let resource_collection_id = Self::init_collection(1183 sender.clone(),1184 CreateCollectionData {1185 ..Default::default()1186 },1187 [1188 Self::rmrk_property(1189 CollectionType,1190 &misc::CollectionType::Resource,1191 )?1192 ]1193 .into_iter(),1194 )?;11951196 <PalletNft<T>>::set_scoped_token_property(1197 collection_id,1198 token_id,1199 PropertyScope::Rmrk,1200 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?1201 )?;12021203 resource_collection_id1204 }1205 };12061207 let resource_collection =1208 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12091210 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12111212 let resource_id = Self::create_nft(1213 &sender,1214 &nft_owner,1215 &resource_collection,1216 resource_properties.chain(1217 [1218 Self::rmrk_property(PendingResourceAccept, &pending)?,1219 Self::rmrk_property(PendingResourceRemoval, &false)?,1220 ]1221 .into_iter(),1222 ),1223 )1224 .map_err(|err| match err {1225 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1226 err => Self::map_unique_err_to_proxy(err),1227 })?;12281229 Ok(resource_id.0)1230 }12311232 fn resource_remove(1233 sender: T::AccountId,1234 collection_id: CollectionId,1235 nft_id: TokenId,1236 resource_id: TokenId,1237 ) -> DispatchResult {1238 let collection =1239 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1240 ensure!(collection.owner == sender, Error::<T>::NoPermission);12411242 let resource_collection_id: Option<CollectionId> =1243 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12441245 let resource_collection_id = resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;1246 1247 let resource_collection =1248 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1249 ensure!(1250 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1251 Error::<T>::ResourceDoesntExist1252 );12531254 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1255 let topmost_owner =1256 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12571258 let sender = T::CrossAccountId::from_sub(sender);1259 if topmost_owner == sender {1260 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1261 .map_err(Self::map_unique_err_to_proxy)?;1262 } else {1263 <PalletNft<T>>::set_scoped_token_property(1264 resource_collection_id,1265 resource_id,1266 PropertyScope::Rmrk,1267 Self::rmrk_property(PendingResourceRemoval, &true)?,1268 )?;1269 }12701271 Ok(())1272 }12731274 fn change_collection_owner(1275 collection_id: CollectionId,1276 collection_type: misc::CollectionType,1277 sender: T::AccountId,1278 new_owner: T::AccountId,1279 ) -> DispatchResult {1280 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1281 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12821283 let mut collection = collection.into_inner();12841285 collection.owner = new_owner;1286 collection.save()1287 }12881289 fn check_collection_owner(1290 collection: &NonfungibleHandle<T>,1291 account: &T::CrossAccountId,1292 ) -> DispatchResult {1293 collection1294 .check_is_owner(account)1295 .map_err(Self::map_unique_err_to_proxy)1296 }12971298 pub fn last_collection_idx() -> RmrkCollectionId {1299 <CollectionIndex<T>>::get()1300 }13011302 pub fn unique_collection_id(1303 rmrk_collection_id: RmrkCollectionId,1304 ) -> Result<CollectionId, DispatchError> {1305 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1306 .map_err(|_| <Error<T>>::CollectionUnknown.into())1307 }13081309 pub fn rmrk_collection_id(1310 unique_collection_id: CollectionId,1311 ) -> Result<RmrkCollectionId, DispatchError> {1312 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1313 .map_err(|_| <Error<T>>::CollectionUnknown.into())1314 }13151316 pub fn get_nft_collection(1317 collection_id: CollectionId,1318 ) -> Result<NonfungibleHandle<T>, DispatchError> {1319 let collection = <CollectionHandle<T>>::try_get(collection_id)1320 .map_err(|_| <Error<T>>::CollectionUnknown)?;13211322 match collection.mode {1323 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1324 _ => Err(<Error<T>>::CollectionUnknown.into()),1325 }1326 }13271328 pub fn collection_exists(collection_id: CollectionId) -> bool {1329 <CollectionHandle<T>>::try_get(collection_id).is_ok()1330 }13311332 pub fn get_collection_property(1333 collection_id: CollectionId,1334 key: RmrkProperty,1335 ) -> Result<PropertyValue, DispatchError> {1336 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1337 .get(&Self::rmrk_property_key(key)?)1338 .ok_or(<Error<T>>::CollectionUnknown)?1339 .clone();13401341 Ok(collection_property)1342 }13431344 pub fn get_collection_property_decoded<V: Decode>(1345 collection_id: CollectionId,1346 key: RmrkProperty,1347 ) -> Result<V, DispatchError> {1348 Self::decode_property(Self::get_collection_property(collection_id, key)?)1349 }13501351 pub fn get_collection_type(1352 collection_id: CollectionId,1353 ) -> Result<misc::CollectionType, DispatchError> {1354 Self::get_collection_property_decoded(collection_id, CollectionType)1355 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1356 }13571358 pub fn ensure_collection_type(1359 collection_id: CollectionId,1360 collection_type: misc::CollectionType,1361 ) -> DispatchResult {1362 let actual_type = Self::get_collection_type(collection_id)?;1363 ensure!(1364 actual_type == collection_type,1365 <CommonError<T>>::NoPermission1366 );13671368 Ok(())1369 }13701371 pub fn get_typed_nft_collection(1372 collection_id: CollectionId,1373 collection_type: misc::CollectionType,1374 ) -> Result<NonfungibleHandle<T>, DispatchError> {1375 Self::ensure_collection_type(collection_id, collection_type)?;13761377 Self::get_nft_collection(collection_id)1378 }13791380 pub fn get_typed_nft_collection_mapped(1381 rmrk_collection_id: RmrkCollectionId,1382 collection_type: misc::CollectionType,1383 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1384 let unique_collection_id = match collection_type {1385 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1386 _ => rmrk_collection_id.into(),1387 };13881389 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13901391 Ok((collection, unique_collection_id))1392 }13931394 pub fn get_nft_property(1395 collection_id: CollectionId,1396 nft_id: TokenId,1397 key: RmrkProperty,1398 ) -> Result<PropertyValue, DispatchError> {1399 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1400 .get(&Self::rmrk_property_key(key)?)1401 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1402 .clone();14031404 Ok(nft_property)1405 }14061407 pub fn get_nft_property_decoded<V: Decode>(1408 collection_id: CollectionId,1409 nft_id: TokenId,1410 key: RmrkProperty,1411 ) -> Result<V, DispatchError> {1412 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1413 }14141415 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1416 <TokenData<T>>::contains_key((collection_id, nft_id))1417 }14181419 pub fn get_nft_type(1420 collection_id: CollectionId,1421 token_id: TokenId,1422 ) -> Result<NftType, DispatchError> {1423 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1424 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1425 }14261427 pub fn ensure_nft_type(1428 collection_id: CollectionId,1429 token_id: TokenId,1430 nft_type: NftType,1431 ) -> DispatchResult {1432 let actual_type = Self::get_nft_type(collection_id, token_id)?;1433 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14341435 Ok(())1436 }14371438 pub fn ensure_nft_owner(1439 collection_id: CollectionId,1440 token_id: TokenId,1441 possible_owner: &T::CrossAccountId,1442 nesting_budget: &dyn budget::Budget,1443 ) -> DispatchResult {1444 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1445 possible_owner.clone(),1446 collection_id,1447 token_id,1448 None,1449 nesting_budget,1450 )1451 .map_err(Self::map_unique_err_to_proxy)?;14521453 ensure!(is_owned, <Error<T>>::NoPermission);14541455 Ok(())1456 }14571458 pub fn filter_user_properties<Key, Value, R, Mapper>(1459 collection_id: CollectionId,1460 token_id: Option<TokenId>,1461 filter_keys: Option<Vec<RmrkPropertyKey>>,1462 mapper: Mapper,1463 ) -> Result<Vec<R>, DispatchError>1464 where1465 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1466 Value: Decode + Default,1467 Mapper: Fn(Key, Value) -> R,1468 {1469 filter_keys1470 .map(|keys| {1471 let properties = keys1472 .into_iter()1473 .filter_map(|key| {1474 let key: Key = key.try_into().ok()?;14751476 let value = match token_id {1477 Some(token_id) => Self::get_nft_property_decoded(1478 collection_id,1479 token_id,1480 UserProperty(key.as_ref()),1481 ),1482 None => Self::get_collection_property_decoded(1483 collection_id,1484 UserProperty(key.as_ref()),1485 ),1486 }1487 .ok()?;14881489 Some(mapper(key, value))1490 })1491 .collect();14921493 Ok(properties)1494 })1495 .unwrap_or_else(|| {1496 let properties =1497 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14981499 Ok(properties)1500 })1501 }15021503 pub fn iterate_user_properties<Key, Value, R, Mapper>(1504 collection_id: CollectionId,1505 token_id: Option<TokenId>,1506 mapper: Mapper,1507 ) -> Result<impl Iterator<Item = R>, DispatchError>1508 where1509 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1510 Value: Decode + Default,1511 Mapper: Fn(Key, Value) -> R,1512 {1513 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15141515 let properties = match token_id {1516 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1517 None => <PalletCommon<T>>::collection_properties(collection_id),1518 };15191520 let properties = properties.into_iter().filter_map(move |(key, value)| {1521 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15221523 let key: Key = key.to_vec().try_into().ok()?;1524 let value: Value = value.decode().ok()?;15251526 Some(mapper(key, value))1527 });15281529 Ok(properties)1530 }15311532 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1533 map_unique_err_to_proxy! {1534 match err {1535 CommonError::NoPermission => NoPermission,1536 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1537 CommonError::PublicMintingNotAllowed => NoPermission,1538 CommonError::TokenNotFound => NoAvailableNftId,1539 CommonError::ApprovedValueTooLow => NoPermission,1540 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1541 StructureError::TokenNotFound => NoAvailableNftId,1542 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1543 }1544 }1545 }1546}runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -339,7 +339,13 @@
let nft_id = TokenId(nft_id);
if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
- let res_collection_id: CollectionId = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+ let res_collection_id: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+
+ let res_collection_id = match res_collection_id {
+ Some(id) => id,
+ None => return Ok(Vec::new())
+ };
+
let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
let resources = resource_collection