difftreelog
feat(rmrk) refactor resources
in: master
5 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::*;4748pub const 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::pallet]72 #[pallet::generate_store(pub(super) trait Store)]73 pub struct Pallet<T>(_);7475 #[pallet::event]76 #[pallet::generate_deposit(pub(super) fn deposit_event)]77 pub enum Event<T: Config> {78 CollectionCreated {79 issuer: T::AccountId,80 collection_id: RmrkCollectionId,81 },82 CollectionDestroyed {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 IssuerChanged {87 old_issuer: T::AccountId,88 new_issuer: T::AccountId,89 collection_id: RmrkCollectionId,90 },91 CollectionLocked {92 issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 NftMinted {96 owner: T::AccountId,97 collection_id: RmrkCollectionId,98 nft_id: RmrkNftId,99 },100 NFTBurned {101 owner: T::AccountId,102 nft_id: RmrkNftId,103 },104 NFTSent {105 sender: T::AccountId,106 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 approval_required: bool,110 },111 NFTAccepted {112 sender: T::AccountId,113 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114 collection_id: RmrkCollectionId,115 nft_id: RmrkNftId,116 },117 NFTRejected {118 sender: T::AccountId,119 collection_id: RmrkCollectionId,120 nft_id: RmrkNftId,121 },122 PropertySet {123 collection_id: RmrkCollectionId,124 maybe_nft_id: Option<RmrkNftId>,125 key: RmrkKeyString,126 value: RmrkValueString,127 },128 ResourceAdded {129 nft_id: RmrkNftId,130 resource_id: RmrkResourceId,131 },132 ResourceRemoval {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceAccepted {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceRemovalAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 PrioritySet {145 collection_id: RmrkCollectionId,146 nft_id: RmrkNftId,147 },148 }149150 #[pallet::error]151 pub enum Error<T> {152 /* Unique-specific events */153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 UnableToDecodeRmrkData,158159 /* RMRK compatible events */160 CollectionNotEmpty,161 NoAvailableCollectionId,162 NoAvailableNftId,163 CollectionUnknown,164 NoPermission,165 NonTransferable,166 CollectionFullOrLocked,167 ResourceDoesntExist,168 CannotSendToDescendentOrSelf,169 CannotAcceptNonOwnedNft,170 CannotRejectNonOwnedNft,171 ResourceNotPending,172 }173174 #[pallet::call]175 impl<T: Config> Pallet<T> {176 /// Create a collection177 #[transactional]178 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]179 pub fn create_collection(180 origin: OriginFor<T>,181 metadata: RmrkString,182 max: Option<u32>,183 symbol: RmrkCollectionSymbol,184 ) -> DispatchResult {185 let sender = ensure_signed(origin)?;186187 let limits = CollectionLimits {188 owner_can_transfer: Some(false),189 token_limit: max,190 ..Default::default()191 };192193 let data = CreateCollectionData {194 limits: Some(limits),195 token_prefix: symbol196 .into_inner()197 .try_into()198 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,199 permissions: Some(CollectionPermissions {200 nesting: Some(NestingPermissions {201 token_owner: true,202 collection_admin: false,203 restricted: None,204205 permissive: false,206 }),207 ..Default::default()208 }),209 ..Default::default()210 };211212 let unique_collection_id = Self::init_collection(213 T::CrossAccountId::from_sub(sender.clone()),214 data,215 [216 Self::rmrk_property(Metadata, &metadata)?,217 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,218 ]219 .into_iter(),220 )?;221 let rmrk_collection_id = <CollectionIndex<T>>::get();222223 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);224225 <PalletCommon<T>>::set_scoped_collection_property(226 unique_collection_id,227 PropertyScope::Rmrk,228 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,229 )?;230231 <CollectionIndex<T>>::mutate(|n| *n += 1);232233 Self::deposit_event(Event::CollectionCreated {234 issuer: sender,235 collection_id: rmrk_collection_id,236 });237238 Ok(())239 }240241 /// destroy collection242 #[transactional]243 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]244 pub fn destroy_collection(245 origin: OriginFor<T>,246 collection_id: RmrkCollectionId,247 ) -> DispatchResult {248 let sender = ensure_signed(origin)?;249 let cross_sender = T::CrossAccountId::from_sub(sender.clone());250251 let collection = Self::get_typed_nft_collection(252 Self::unique_collection_id(collection_id)?,253 misc::CollectionType::Regular,254 )?;255 collection.check_is_external()?;256257 <PalletNft<T>>::destroy_collection(collection, &cross_sender)258 .map_err(Self::map_unique_err_to_proxy)?;259260 Self::deposit_event(Event::CollectionDestroyed {261 issuer: sender,262 collection_id,263 });264265 Ok(())266 }267268 /// Change the issuer of a collection269 ///270 /// Parameters:271 /// - `origin`: sender of the transaction272 /// - `collection_id`: collection id of the nft to change issuer of273 /// - `new_issuer`: Collection's new issuer274 #[transactional]275 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]276 pub fn change_collection_issuer(277 origin: OriginFor<T>,278 collection_id: RmrkCollectionId,279 new_issuer: <T::Lookup as StaticLookup>::Source,280 ) -> DispatchResult {281 let sender = ensure_signed(origin)?;282283 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;284 collection.check_is_external()?;285286 let new_issuer = T::Lookup::lookup(new_issuer)?;287288 Self::change_collection_owner(289 Self::unique_collection_id(collection_id)?,290 misc::CollectionType::Regular,291 sender.clone(),292 new_issuer.clone(),293 )?;294295 Self::deposit_event(Event::IssuerChanged {296 old_issuer: sender,297 new_issuer,298 collection_id,299 });300301 Ok(())302 }303304 /// lock collection305 #[transactional]306 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]307 pub fn lock_collection(308 origin: OriginFor<T>,309 collection_id: RmrkCollectionId,310 ) -> DispatchResult {311 let sender = ensure_signed(origin)?;312 let cross_sender = T::CrossAccountId::from_sub(sender.clone());313314 let collection = Self::get_typed_nft_collection(315 Self::unique_collection_id(collection_id)?,316 misc::CollectionType::Regular,317 )?;318 collection.check_is_external()?;319320 Self::check_collection_owner(&collection, &cross_sender)?;321322 let token_count = collection.total_supply();323324 let mut collection = collection.into_inner();325 collection.limits.token_limit = Some(token_count);326 collection.save()?;327328 Self::deposit_event(Event::CollectionLocked {329 issuer: sender,330 collection_id,331 });332333 Ok(())334 }335336 /// Mints an NFT in the specified collection337 /// Sets metadata and the royalty attribute338 ///339 /// Parameters:340 /// - `collection_id`: The class of the asset to be minted.341 /// - `nft_id`: The nft value of the asset to be minted.342 /// - `recipient`: Receiver of the royalty343 /// - `royalty`: Permillage reward from each trade for the Recipient344 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash345 /// - `transferable`: Ability to transfer this NFT346 #[transactional]347 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]348 pub fn mint_nft(349 origin: OriginFor<T>,350 owner: T::AccountId,351 collection_id: RmrkCollectionId,352 recipient: Option<T::AccountId>,353 royalty_amount: Option<Permill>,354 metadata: RmrkString,355 transferable: bool,356 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,357 ) -> DispatchResult {358 let sender = ensure_signed(origin)?;359 let cross_sender = T::CrossAccountId::from_sub(sender.clone());360 let cross_owner = T::CrossAccountId::from_sub(owner.clone());361362 let collection = Self::get_typed_nft_collection(363 Self::unique_collection_id(collection_id)?,364 misc::CollectionType::Regular,365 )?;366 collection.check_is_external()?;367368 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {369 recipient: recipient.unwrap_or_else(|| owner.clone()),370 amount,371 });372373 let nft_id = Self::create_nft(374 &cross_sender,375 &cross_owner,376 &collection,377 [378 Self::rmrk_property(TokenType, &NftType::Regular)?,379 Self::rmrk_property(Transferable, &transferable)?,380 Self::rmrk_property(PendingNftAccept, &false)?,381 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,382 Self::rmrk_property(Metadata, &metadata)?,383 Self::rmrk_property(Equipped, &false)?,384 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,385 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,386 ]387 .into_iter(),388 )389 .map_err(|err| match err {390 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),391 err => Self::map_unique_err_to_proxy(err),392 })?;393394 if let Some(resources) = resources {395 for resource in resources {396 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;397 }398 }399400 Self::deposit_event(Event::NftMinted {401 owner,402 collection_id,403 nft_id: nft_id.0,404 });405406 Ok(())407 }408409 /// burn nft410 #[transactional]411 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]412 pub fn burn_nft(413 origin: OriginFor<T>,414 collection_id: RmrkCollectionId,415 nft_id: RmrkNftId,416 max_burns: u32,417 ) -> DispatchResult {418 let sender = ensure_signed(origin)?;419 let cross_sender = T::CrossAccountId::from_sub(sender.clone());420421 let collection = Self::get_typed_nft_collection(422 Self::unique_collection_id(collection_id)?,423 misc::CollectionType::Regular,424 )?;425 collection.check_is_external()?;426427 Self::destroy_nft(428 cross_sender,429 Self::unique_collection_id(collection_id)?,430 nft_id.into(),431 max_burns,432 <Error<T>>::NoPermission,433 )434 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;435436 Self::deposit_event(Event::NFTBurned {437 owner: sender,438 nft_id,439 });440441 Ok(())442 }443444 /// Transfers a NFT from an Account or NFT A to another Account or NFT B445 ///446 /// Parameters:447 /// - `origin`: sender of the transaction448 /// - `rmrk_collection_id`: collection id of the nft to be transferred449 /// - `rmrk_nft_id`: nft id of the nft to be transferred450 /// - `new_owner`: new owner of the nft which can be either an account or a NFT451 #[transactional]452 #[pallet::weight(<SelfWeightOf<T>>::send())]453 pub fn send(454 origin: OriginFor<T>,455 rmrk_collection_id: RmrkCollectionId,456 rmrk_nft_id: RmrkNftId,457 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,458 ) -> DispatchResult {459 let sender = ensure_signed(origin.clone())?;460 let cross_sender = T::CrossAccountId::from_sub(sender.clone());461462 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;463 let nft_id = rmrk_nft_id.into();464465 let collection =466 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;467 collection.check_is_external()?;468469 let token_data =470 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;471472 let from = token_data.owner;473474 ensure!(475 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,476 <Error<T>>::NonTransferable477 );478479 ensure!(480 !Self::get_nft_property_decoded(481 collection_id,482 nft_id,483 RmrkProperty::PendingNftAccept484 )?,485 <Error<T>>::NoPermission486 );487488 let target_owner;489 let approval_required;490491 match new_owner {492 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {493 target_owner = T::CrossAccountId::from_sub(account_id.clone());494 approval_required = false;495 }496 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(497 target_collection_id,498 target_nft_id,499 ) => {500 let target_collection_id = Self::unique_collection_id(target_collection_id)?;501502 let target_nft_budget = budget::Value::new(NESTING_BUDGET);503504 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(505 target_collection_id,506 target_nft_id.into(),507 Some((collection_id, nft_id)),508 &target_nft_budget,509 )510 .map_err(Self::map_unique_err_to_proxy)?;511512 approval_required = cross_sender != target_nft_owner;513514 if approval_required {515 target_owner = target_nft_owner;516517 <PalletNft<T>>::set_scoped_token_property(518 collection.id,519 nft_id,520 PropertyScope::Rmrk,521 Self::rmrk_property(PendingNftAccept, &approval_required)?,522 )?;523 } else {524 target_owner = T::CrossTokenAddressMapping::token_to_address(525 target_collection_id,526 target_nft_id.into(),527 );528 }529 }530 }531532 let src_nft_budget = budget::Value::new(NESTING_BUDGET);533534 <PalletNft<T>>::transfer_from(535 &collection,536 &cross_sender,537 &from,538 &target_owner,539 nft_id,540 &src_nft_budget,541 )542 .map_err(Self::map_unique_err_to_proxy)?;543544 Self::deposit_event(Event::NFTSent {545 sender,546 recipient: new_owner,547 collection_id: rmrk_collection_id,548 nft_id: rmrk_nft_id,549 approval_required,550 });551552 Ok(())553 }554555 /// Accepts an NFT sent from another account to self or owned NFT556 ///557 /// Parameters:558 /// - `origin`: sender of the transaction559 /// - `rmrk_collection_id`: collection id of the nft to be accepted560 /// - `rmrk_nft_id`: nft id of the nft to be accepted561 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was562 /// sent to563 #[transactional]564 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]565 pub fn accept_nft(566 origin: OriginFor<T>,567 rmrk_collection_id: RmrkCollectionId,568 rmrk_nft_id: RmrkNftId,569 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,570 ) -> DispatchResult {571 let sender = ensure_signed(origin.clone())?;572 let cross_sender = T::CrossAccountId::from_sub(sender.clone());573574 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;575 let nft_id = rmrk_nft_id.into();576577 let collection =578 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;579 collection.check_is_external()?;580581 let new_cross_owner = match new_owner {582 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {583 T::CrossAccountId::from_sub(account_id.clone())584 }585 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(586 target_collection_id,587 target_nft_id,588 ) => {589 let target_collection_id = Self::unique_collection_id(target_collection_id)?;590591 T::CrossTokenAddressMapping::token_to_address(592 target_collection_id,593 TokenId(target_nft_id),594 )595 }596 };597598 let budget = budget::Value::new(NESTING_BUDGET);599600 <PalletNft<T>>::transfer(601 &collection,602 &cross_sender,603 &new_cross_owner,604 nft_id,605 &budget,606 )607 .map_err(|err| {608 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {609 <Error<T>>::CannotAcceptNonOwnedNft.into()610 } else {611 Self::map_unique_err_to_proxy(err)612 }613 })?;614615 <PalletNft<T>>::set_scoped_token_property(616 collection.id,617 nft_id,618 PropertyScope::Rmrk,619 Self::rmrk_property(PendingNftAccept, &false)?,620 )?;621622 Self::deposit_event(Event::NFTAccepted {623 sender,624 recipient: new_owner,625 collection_id: rmrk_collection_id,626 nft_id: rmrk_nft_id,627 });628629 Ok(())630 }631632 /// Rejects an NFT sent from another account to self or owned NFT633 ///634 /// Parameters:635 /// - `origin`: sender of the transaction636 /// - `rmrk_collection_id`: collection id of the nft to be accepted637 /// - `rmrk_nft_id`: nft id of the nft to be accepted638 #[transactional]639 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]640 pub fn reject_nft(641 origin: OriginFor<T>,642 rmrk_collection_id: RmrkCollectionId,643 rmrk_nft_id: RmrkNftId,644 ) -> DispatchResult {645 let sender = ensure_signed(origin)?;646 let cross_sender = T::CrossAccountId::from_sub(sender.clone());647648 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;649 let nft_id = rmrk_nft_id.into();650651 let collection =652 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;653 collection.check_is_external()?;654655 ensure!(656 Self::get_nft_property_decoded(657 collection_id,658 nft_id,659 RmrkProperty::PendingNftAccept660 )?,661 <Error<T>>::NoPermission662 );663664 Self::destroy_nft(665 cross_sender,666 collection_id,667 nft_id,668 NESTING_BUDGET,669 <Error<T>>::CannotRejectNonOwnedNft,670 )671 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;672673 Self::deposit_event(Event::NFTRejected {674 sender,675 collection_id: rmrk_collection_id,676 nft_id: rmrk_nft_id,677 });678679 Ok(())680 }681682 /// accept the addition of a new resource to an existing NFT683 #[transactional]684 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]685 pub fn accept_resource(686 origin: OriginFor<T>,687 rmrk_collection_id: RmrkCollectionId,688 rmrk_nft_id: RmrkNftId,689 rmrk_resource_id: RmrkResourceId,690 ) -> DispatchResult {691 let sender = ensure_signed(origin)?;692 let cross_sender = T::CrossAccountId::from_sub(sender);693694 let collection_id = Self::unique_collection_id(rmrk_collection_id)695 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;696 let collection =697 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;698 collection.check_is_external()?;699700 let nft_id = rmrk_nft_id.into();701 let resource_id = rmrk_resource_id.into();702703 let budget = budget::Value::new(NESTING_BUDGET);704705 let nft_owner =706 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)707 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;708709 let resource_collection_id: Option<CollectionId> =710 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)711 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;712713 let resource_collection_id =714 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;715716 let is_pending: bool = Self::get_nft_property_decoded(717 resource_collection_id,718 resource_id,719 PendingResourceAccept,720 )721 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;722723 ensure!(is_pending, <Error<T>>::ResourceNotPending);724725 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);726727 <PalletNft<T>>::set_scoped_token_property(728 resource_collection_id,729 rmrk_resource_id.into(),730 PropertyScope::Rmrk,731 Self::rmrk_property(PendingResourceAccept, &false)?,732 )?;733734 Self::deposit_event(Event::<T>::ResourceAccepted {735 nft_id: rmrk_nft_id,736 resource_id: rmrk_resource_id,737 });738739 Ok(())740 }741742 /// accept the removal of a resource of an existing NFT743 #[transactional]744 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]745 pub fn accept_resource_removal(746 origin: OriginFor<T>,747 rmrk_collection_id: RmrkCollectionId,748 rmrk_nft_id: RmrkNftId,749 rmrk_resource_id: RmrkResourceId,750 ) -> DispatchResult {751 let sender = ensure_signed(origin)?;752 let cross_sender = T::CrossAccountId::from_sub(sender);753754 let collection_id = Self::unique_collection_id(rmrk_collection_id)755 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;756 let collection =757 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;758 collection.check_is_external()?;759760 let nft_id = rmrk_nft_id.into();761 let resource_id = rmrk_resource_id.into();762763 let budget = budget::Value::new(NESTING_BUDGET);764765 let nft_owner =766 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)767 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;768769 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);770771 let resource_collection_id: Option<CollectionId> =772 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)773 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;774775 let resource_collection_id =776 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;777778 let is_pending: bool = Self::get_nft_property_decoded(779 resource_collection_id,780 resource_id,781 PendingResourceRemoval,782 )783 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;784785 ensure!(is_pending, <Error<T>>::ResourceNotPending);786787 let resource_collection = Self::get_typed_nft_collection(788 resource_collection_id,789 misc::CollectionType::Resource,790 )?;791792 let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))793 .ok_or(<Error<T>>::ResourceDoesntExist)?;794795 let resource_owner = resource_data.owner;796797 <PalletNft<T>>::burn(798 &resource_collection,799 &resource_owner,800 rmrk_resource_id.into(),801 )802 .map_err(Self::map_unique_err_to_proxy)?;803804 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {805 nft_id: rmrk_nft_id,806 resource_id: rmrk_resource_id,807 });808809 Ok(())810 }811812 /// set a custom value on an NFT813 #[transactional]814 #[pallet::weight(<SelfWeightOf<T>>::set_property())]815 pub fn set_property(816 origin: OriginFor<T>,817 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,818 maybe_nft_id: Option<RmrkNftId>,819 key: RmrkKeyString,820 value: RmrkValueString,821 ) -> DispatchResult {822 let sender = ensure_signed(origin)?;823 let sender = T::CrossAccountId::from_sub(sender);824825 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;826 let collection =827 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;828 collection.check_is_external()?;829830 let budget = budget::Value::new(NESTING_BUDGET);831832 match maybe_nft_id {833 Some(nft_id) => {834 let token_id: TokenId = nft_id.into();835836 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;837 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;838839 <PalletNft<T>>::set_scoped_token_property(840 collection_id,841 token_id,842 PropertyScope::Rmrk,843 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,844 )?;845 }846 None => {847 let collection = Self::get_typed_nft_collection(848 collection_id,849 misc::CollectionType::Regular,850 )?;851852 Self::check_collection_owner(&collection, &sender)?;853854 <PalletCommon<T>>::set_scoped_collection_property(855 collection_id,856 PropertyScope::Rmrk,857 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,858 )?;859 }860 }861862 Self::deposit_event(Event::PropertySet {863 collection_id: rmrk_collection_id,864 maybe_nft_id,865 key,866 value,867 });868869 Ok(())870 }871872 /// set a different order of resource priority873 #[transactional]874 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]875 pub fn set_priority(876 origin: OriginFor<T>,877 rmrk_collection_id: RmrkCollectionId,878 rmrk_nft_id: RmrkNftId,879 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,880 ) -> DispatchResult {881 let sender = ensure_signed(origin)?;882 let sender = T::CrossAccountId::from_sub(sender);883884 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;885 let nft_id = rmrk_nft_id.into();886887 let collection =888 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;889 collection.check_is_external()?;890891 let budget = budget::Value::new(NESTING_BUDGET);892893 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;894 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;895896 <PalletNft<T>>::set_scoped_token_property(897 collection_id,898 nft_id,899 PropertyScope::Rmrk,900 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,901 )?;902903 Self::deposit_event(Event::<T>::PrioritySet {904 collection_id: rmrk_collection_id,905 nft_id: rmrk_nft_id,906 });907908 Ok(())909 }910911 /// Create basic resource912 #[transactional]913 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]914 pub fn add_basic_resource(915 origin: OriginFor<T>,916 rmrk_collection_id: RmrkCollectionId,917 nft_id: RmrkNftId,918 resource: RmrkBasicResource,919 ) -> DispatchResult {920 let sender = ensure_signed(origin.clone())?;921922 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;923 let collection =924 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;925 collection.check_is_external()?;926927 let resource_id = Self::resource_add(928 sender,929 collection_id,930 nft_id.into(),931 RmrkResourceTypes::Basic(resource),932 )?;933934 Self::deposit_event(Event::ResourceAdded {935 nft_id,936 resource_id,937 });938 Ok(())939 }940941 /// Create composable resource942 #[transactional]943 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]944 pub fn add_composable_resource(945 origin: OriginFor<T>,946 rmrk_collection_id: RmrkCollectionId,947 nft_id: RmrkNftId,948 resource: RmrkComposableResource,949 ) -> DispatchResult {950 let sender = ensure_signed(origin.clone())?;951952 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;953 let collection =954 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;955 collection.check_is_external()?;956957 let resource_id = Self::resource_add(958 sender,959 collection_id,960 nft_id.into(),961 RmrkResourceTypes::Composable(resource),962 )?;963964 Self::deposit_event(Event::ResourceAdded {965 nft_id,966 resource_id,967 });968 Ok(())969 }970971 /// Create slot resource972 #[transactional]973 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]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 RmrkResourceTypes::Slot(resource),992 )?;993994 Self::deposit_event(Event::ResourceAdded {995 nft_id,996 resource_id,997 });998 Ok(())999 }10001001 /// remove resource1002 #[transactional]1003 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1004 pub fn remove_resource(1005 origin: OriginFor<T>,1006 rmrk_collection_id: RmrkCollectionId,1007 nft_id: RmrkNftId,1008 resource_id: RmrkResourceId,1009 ) -> DispatchResult {1010 let sender = ensure_signed(origin.clone())?;10111012 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1013 let collection =1014 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1015 collection.check_is_external()?;10161017 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10181019 Self::deposit_event(Event::ResourceRemoval {1020 nft_id,1021 resource_id,1022 });1023 Ok(())1024 }1025 }1026}10271028impl<T: Config> Pallet<T> {1029 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1030 let key = rmrk_key.to_key::<T>()?;10311032 let scoped_key = PropertyScope::Rmrk1033 .apply(key)1034 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10351036 Ok(scoped_key)1037 }10381039 // todo think about renaming these1040 pub fn rmrk_property<E: Encode>(1041 rmrk_key: RmrkProperty,1042 value: &E,1043 ) -> Result<Property, DispatchError> {1044 let key = rmrk_key.to_key::<T>()?;10451046 let value = value1047 .encode()1048 .try_into()1049 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10501051 let property = Property { key, value };10521053 Ok(property)1054 }10551056 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1057 vec.decode()1058 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1059 }10601061 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1062 where1063 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1064 {1065 vec.rebind()1066 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1067 }10681069 fn init_collection(1070 sender: T::CrossAccountId,1071 data: CreateCollectionData<T::AccountId>,1072 properties: impl Iterator<Item = Property>,1073 ) -> Result<CollectionId, DispatchError> {1074 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10751076 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1077 return Err(<Error<T>>::NoAvailableCollectionId.into());1078 }10791080 <PalletCommon<T>>::set_scoped_collection_properties(1081 collection_id?,1082 PropertyScope::Rmrk,1083 properties,1084 )?;10851086 collection_id1087 }10881089 pub fn create_nft(1090 sender: &T::CrossAccountId,1091 owner: &T::CrossAccountId,1092 collection: &NonfungibleHandle<T>,1093 properties: impl Iterator<Item = Property>,1094 ) -> Result<TokenId, DispatchError> {1095 let data = CreateNftExData {1096 properties: BoundedVec::default(),1097 owner: owner.clone(),1098 };10991100 let budget = budget::Value::new(NESTING_BUDGET);11011102 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11031104 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11051106 <PalletNft<T>>::set_scoped_token_properties(1107 collection.id,1108 nft_id,1109 PropertyScope::Rmrk,1110 properties,1111 )?;11121113 Ok(nft_id)1114 }11151116 fn destroy_nft(1117 sender: T::CrossAccountId,1118 collection_id: CollectionId,1119 token_id: TokenId,1120 max_burns: u32,1121 error_if_not_owned: Error<T>,1122 ) -> DispatchResultWithPostInfo {1123 let collection =1124 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11251126 let token_data =1127 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11281129 let from = token_data.owner;11301131 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11321133 ensure!(1134 <PalletStructure<T>>::check_indirectly_owned(1135 sender.clone(),1136 collection_id,1137 token_id,1138 None,1139 &owner_check_budget1140 )?,1141 error_if_not_owned,1142 );11431144 let burns_budget = budget::Value::new(max_burns);1145 let breadth_budget = budget::Value::new(max_burns);11461147 <PalletNft<T>>::burn_recursively(1148 &collection,1149 &from,1150 token_id,1151 &burns_budget,1152 &breadth_budget,1153 )1154 }11551156 fn resource_add(1157 sender: T::AccountId,1158 collection_id: CollectionId,1159 nft_id: TokenId,1160 resource: RmrkResourceTypes,1161 ) -> Result<RmrkResourceId, DispatchError> {1162 match resource {1163 RmrkResourceTypes::Basic(resource) => Self::resource_add_helper(1164 sender,1165 collection_id,1166 nft_id,1167 [1168 Self::rmrk_property(TokenType, &NftType::Resource)?,1169 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,1170 Self::rmrk_property(Src, &resource.src)?,1171 Self::rmrk_property(Metadata, &resource.metadata)?,1172 Self::rmrk_property(License, &resource.license)?,1173 Self::rmrk_property(Thumb, &resource.thumb)?,1174 ]1175 .into_iter(),1176 ),1177 RmrkResourceTypes::Composable(resource) => Self::resource_add_helper(1178 sender,1179 collection_id,1180 nft_id.into(),1181 [1182 Self::rmrk_property(TokenType, &NftType::Resource)?,1183 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,1184 Self::rmrk_property(Parts, &resource.parts)?,1185 Self::rmrk_property(Base, &resource.base)?,1186 Self::rmrk_property(Src, &resource.src)?,1187 Self::rmrk_property(Metadata, &resource.metadata)?,1188 Self::rmrk_property(License, &resource.license)?,1189 Self::rmrk_property(Thumb, &resource.thumb)?,1190 ]1191 .into_iter(),1192 ),1193 RmrkResourceTypes::Slot(resource) => Self::resource_add_helper(1194 sender,1195 collection_id,1196 nft_id.into(),1197 [1198 Self::rmrk_property(TokenType, &NftType::Resource)?,1199 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,1200 Self::rmrk_property(Base, &resource.base)?,1201 Self::rmrk_property(Src, &resource.src)?,1202 Self::rmrk_property(Metadata, &resource.metadata)?,1203 Self::rmrk_property(Slot, &resource.slot)?,1204 Self::rmrk_property(License, &resource.license)?,1205 Self::rmrk_property(Thumb, &resource.thumb)?,1206 ]1207 .into_iter(),1208 ),1209 }1210 }12111212 fn resource_add_helper(1213 sender: T::AccountId,1214 collection_id: CollectionId,1215 token_id: TokenId,1216 resource_properties: impl Iterator<Item = Property>,1217 ) -> Result<RmrkResourceId, DispatchError> {1218 let collection =1219 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1220 ensure!(collection.owner == sender, Error::<T>::NoPermission);12211222 let sender = T::CrossAccountId::from_sub(sender);1223 let budget = budget::Value::new(NESTING_BUDGET);12241225 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1226 .map_err(Self::map_unique_err_to_proxy)?;12271228 let pending = sender != nft_owner;12291230 let resource_collection_id: Option<CollectionId> =1231 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;12321233 let resource_collection_id = match resource_collection_id {1234 Some(id) => id,1235 None => {1236 let resource_collection_id = Self::init_collection(1237 sender.clone(),1238 CreateCollectionData {1239 ..Default::default()1240 },1241 [Self::rmrk_property(1242 CollectionType,1243 &misc::CollectionType::Resource,1244 )?]1245 .into_iter(),1246 )?;12471248 <PalletNft<T>>::set_scoped_token_property(1249 collection_id,1250 token_id,1251 PropertyScope::Rmrk,1252 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1253 )?;12541255 resource_collection_id1256 }1257 };12581259 let resource_collection =1260 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12611262 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12631264 let resource_id = Self::create_nft(1265 &sender,1266 &nft_owner,1267 &resource_collection,1268 resource_properties.chain(1269 [1270 Self::rmrk_property(PendingResourceAccept, &pending)?,1271 Self::rmrk_property(PendingResourceRemoval, &false)?,1272 ]1273 .into_iter(),1274 ),1275 )1276 .map_err(|err| match err {1277 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1278 err => Self::map_unique_err_to_proxy(err),1279 })?;12801281 Ok(resource_id.0)1282 }12831284 fn resource_remove(1285 sender: T::AccountId,1286 collection_id: CollectionId,1287 nft_id: TokenId,1288 resource_id: TokenId,1289 ) -> DispatchResult {1290 let collection =1291 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1292 ensure!(collection.owner == sender, Error::<T>::NoPermission);12931294 let resource_collection_id: Option<CollectionId> =1295 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12961297 let resource_collection_id =1298 resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12991300 let resource_collection =1301 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1302 ensure!(1303 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1304 Error::<T>::ResourceDoesntExist1305 );13061307 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1308 let topmost_owner =1309 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13101311 let sender = T::CrossAccountId::from_sub(sender);1312 if topmost_owner == sender {1313 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1314 .map_err(Self::map_unique_err_to_proxy)?;1315 } else {1316 <PalletNft<T>>::set_scoped_token_property(1317 resource_collection_id,1318 resource_id,1319 PropertyScope::Rmrk,1320 Self::rmrk_property(PendingResourceRemoval, &true)?,1321 )?;1322 }13231324 Ok(())1325 }13261327 fn change_collection_owner(1328 collection_id: CollectionId,1329 collection_type: misc::CollectionType,1330 sender: T::AccountId,1331 new_owner: T::AccountId,1332 ) -> DispatchResult {1333 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1334 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;13351336 let mut collection = collection.into_inner();13371338 collection.owner = new_owner;1339 collection.save()1340 }13411342 fn check_collection_owner(1343 collection: &NonfungibleHandle<T>,1344 account: &T::CrossAccountId,1345 ) -> DispatchResult {1346 collection1347 .check_is_owner(account)1348 .map_err(Self::map_unique_err_to_proxy)1349 }13501351 pub fn last_collection_idx() -> RmrkCollectionId {1352 <CollectionIndex<T>>::get()1353 }13541355 pub fn unique_collection_id(1356 rmrk_collection_id: RmrkCollectionId,1357 ) -> Result<CollectionId, DispatchError> {1358 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1359 .map_err(|_| <Error<T>>::CollectionUnknown.into())1360 }13611362 pub fn rmrk_collection_id(1363 unique_collection_id: CollectionId,1364 ) -> Result<RmrkCollectionId, DispatchError> {1365 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1366 }13671368 pub fn get_nft_collection(1369 collection_id: CollectionId,1370 ) -> Result<NonfungibleHandle<T>, DispatchError> {1371 let collection = <CollectionHandle<T>>::try_get(collection_id)1372 .map_err(|_| <Error<T>>::CollectionUnknown)?;13731374 match collection.mode {1375 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1376 _ => Err(<Error<T>>::CollectionUnknown.into()),1377 }1378 }13791380 pub fn collection_exists(collection_id: CollectionId) -> bool {1381 <CollectionHandle<T>>::try_get(collection_id).is_ok()1382 }13831384 pub fn get_collection_property(1385 collection_id: CollectionId,1386 key: RmrkProperty,1387 ) -> Result<PropertyValue, DispatchError> {1388 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1389 .get(&Self::rmrk_property_key(key)?)1390 .ok_or(<Error<T>>::CollectionUnknown)?1391 .clone();13921393 Ok(collection_property)1394 }13951396 pub fn get_collection_property_decoded<V: Decode>(1397 collection_id: CollectionId,1398 key: RmrkProperty,1399 ) -> Result<V, DispatchError> {1400 Self::decode_property(Self::get_collection_property(collection_id, key)?)1401 }14021403 pub fn get_collection_type(1404 collection_id: CollectionId,1405 ) -> Result<misc::CollectionType, DispatchError> {1406 Self::get_collection_property_decoded(collection_id, CollectionType)1407 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1408 }14091410 pub fn ensure_collection_type(1411 collection_id: CollectionId,1412 collection_type: misc::CollectionType,1413 ) -> DispatchResult {1414 let actual_type = Self::get_collection_type(collection_id)?;1415 ensure!(1416 actual_type == collection_type,1417 <CommonError<T>>::NoPermission1418 );14191420 Ok(())1421 }14221423 pub fn get_typed_nft_collection(1424 collection_id: CollectionId,1425 collection_type: misc::CollectionType,1426 ) -> Result<NonfungibleHandle<T>, DispatchError> {1427 Self::ensure_collection_type(collection_id, collection_type)?;14281429 Self::get_nft_collection(collection_id)1430 }14311432 pub fn get_typed_nft_collection_mapped(1433 rmrk_collection_id: RmrkCollectionId,1434 collection_type: misc::CollectionType,1435 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1436 let unique_collection_id = match collection_type {1437 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1438 _ => rmrk_collection_id.into(),1439 };14401441 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14421443 Ok((collection, unique_collection_id))1444 }14451446 pub fn get_nft_property(1447 collection_id: CollectionId,1448 nft_id: TokenId,1449 key: RmrkProperty,1450 ) -> Result<PropertyValue, DispatchError> {1451 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1452 .get(&Self::rmrk_property_key(key)?)1453 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1454 .clone();14551456 Ok(nft_property)1457 }14581459 pub fn get_nft_property_decoded<V: Decode>(1460 collection_id: CollectionId,1461 nft_id: TokenId,1462 key: RmrkProperty,1463 ) -> Result<V, DispatchError> {1464 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1465 }14661467 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1468 <TokenData<T>>::contains_key((collection_id, nft_id))1469 }14701471 pub fn get_nft_type(1472 collection_id: CollectionId,1473 token_id: TokenId,1474 ) -> Result<NftType, DispatchError> {1475 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1476 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1477 }14781479 pub fn ensure_nft_type(1480 collection_id: CollectionId,1481 token_id: TokenId,1482 nft_type: NftType,1483 ) -> DispatchResult {1484 let actual_type = Self::get_nft_type(collection_id, token_id)?;1485 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14861487 Ok(())1488 }14891490 pub fn ensure_nft_owner(1491 collection_id: CollectionId,1492 token_id: TokenId,1493 possible_owner: &T::CrossAccountId,1494 nesting_budget: &dyn budget::Budget,1495 ) -> DispatchResult {1496 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1497 possible_owner.clone(),1498 collection_id,1499 token_id,1500 None,1501 nesting_budget,1502 )1503 .map_err(Self::map_unique_err_to_proxy)?;15041505 ensure!(is_owned, <Error<T>>::NoPermission);15061507 Ok(())1508 }15091510 pub fn filter_user_properties<Key, Value, R, Mapper>(1511 collection_id: CollectionId,1512 token_id: Option<TokenId>,1513 filter_keys: Option<Vec<RmrkPropertyKey>>,1514 mapper: Mapper,1515 ) -> Result<Vec<R>, DispatchError>1516 where1517 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1518 Value: Decode + Default,1519 Mapper: Fn(Key, Value) -> R,1520 {1521 filter_keys1522 .map(|keys| {1523 let properties = keys1524 .into_iter()1525 .filter_map(|key| {1526 let key: Key = key.try_into().ok()?;15271528 let value = match token_id {1529 Some(token_id) => Self::get_nft_property_decoded(1530 collection_id,1531 token_id,1532 UserProperty(key.as_ref()),1533 ),1534 None => Self::get_collection_property_decoded(1535 collection_id,1536 UserProperty(key.as_ref()),1537 ),1538 }1539 .ok()?;15401541 Some(mapper(key, value))1542 })1543 .collect();15441545 Ok(properties)1546 })1547 .unwrap_or_else(|| {1548 let properties =1549 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15501551 Ok(properties)1552 })1553 }15541555 pub fn iterate_user_properties<Key, Value, R, Mapper>(1556 collection_id: CollectionId,1557 token_id: Option<TokenId>,1558 mapper: Mapper,1559 ) -> Result<impl Iterator<Item = R>, DispatchError>1560 where1561 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1562 Value: Decode + Default,1563 Mapper: Fn(Key, Value) -> R,1564 {1565 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15661567 let properties = match token_id {1568 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1569 None => <PalletCommon<T>>::collection_properties(collection_id),1570 };15711572 let properties = properties.into_iter().filter_map(move |(key, value)| {1573 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15741575 let key: Key = key.to_vec().try_into().ok()?;1576 let value: Value = value.decode().ok()?;15771578 Some(mapper(key, value))1579 });15801581 Ok(properties)1582 }15831584 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1585 map_unique_err_to_proxy! {1586 match err {1587 CommonError::NoPermission => NoPermission,1588 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1589 CommonError::PublicMintingNotAllowed => NoPermission,1590 CommonError::TokenNotFound => NoAvailableNftId,1591 CommonError::ApprovedValueTooLow => NoPermission,1592 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1593 StructureError::TokenNotFound => NoAvailableNftId,1594 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1595 }1596 }1597 }1598}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::*;4748pub const 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::pallet]72 #[pallet::generate_store(pub(super) trait Store)]73 pub struct Pallet<T>(_);7475 #[pallet::event]76 #[pallet::generate_deposit(pub(super) fn deposit_event)]77 pub enum Event<T: Config> {78 CollectionCreated {79 issuer: T::AccountId,80 collection_id: RmrkCollectionId,81 },82 CollectionDestroyed {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 IssuerChanged {87 old_issuer: T::AccountId,88 new_issuer: T::AccountId,89 collection_id: RmrkCollectionId,90 },91 CollectionLocked {92 issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 NftMinted {96 owner: T::AccountId,97 collection_id: RmrkCollectionId,98 nft_id: RmrkNftId,99 },100 NFTBurned {101 owner: T::AccountId,102 nft_id: RmrkNftId,103 },104 NFTSent {105 sender: T::AccountId,106 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 approval_required: bool,110 },111 NFTAccepted {112 sender: T::AccountId,113 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114 collection_id: RmrkCollectionId,115 nft_id: RmrkNftId,116 },117 NFTRejected {118 sender: T::AccountId,119 collection_id: RmrkCollectionId,120 nft_id: RmrkNftId,121 },122 PropertySet {123 collection_id: RmrkCollectionId,124 maybe_nft_id: Option<RmrkNftId>,125 key: RmrkKeyString,126 value: RmrkValueString,127 },128 ResourceAdded {129 nft_id: RmrkNftId,130 resource_id: RmrkResourceId,131 },132 ResourceRemoval {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceAccepted {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceRemovalAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 PrioritySet {145 collection_id: RmrkCollectionId,146 nft_id: RmrkNftId,147 },148 }149150 #[pallet::error]151 pub enum Error<T> {152 /* Unique-specific events */153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 RmrkPropertyIsNotFound,158 UnableToDecodeRmrkData,159160 /* RMRK compatible events */161 CollectionNotEmpty,162 NoAvailableCollectionId,163 NoAvailableNftId,164 CollectionUnknown,165 NoPermission,166 NonTransferable,167 CollectionFullOrLocked,168 ResourceDoesntExist,169 CannotSendToDescendentOrSelf,170 CannotAcceptNonOwnedNft,171 CannotRejectNonOwnedNft,172 ResourceNotPending,173 NoAvailableResourceId,174 }175176 #[pallet::call]177 impl<T: Config> Pallet<T> {178 /// Create a collection179 #[transactional]180 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]181 pub fn create_collection(182 origin: OriginFor<T>,183 metadata: RmrkString,184 max: Option<u32>,185 symbol: RmrkCollectionSymbol,186 ) -> DispatchResult {187 let sender = ensure_signed(origin)?;188189 let limits = CollectionLimits {190 owner_can_transfer: Some(false),191 token_limit: max,192 ..Default::default()193 };194195 let data = CreateCollectionData {196 limits: Some(limits),197 token_prefix: symbol198 .into_inner()199 .try_into()200 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,201 permissions: Some(CollectionPermissions {202 nesting: Some(NestingPermissions {203 token_owner: true,204 collection_admin: false,205 restricted: None,206207 permissive: false,208 }),209 ..Default::default()210 }),211 ..Default::default()212 };213214 let unique_collection_id = Self::init_collection(215 T::CrossAccountId::from_sub(sender.clone()),216 data,217 [218 Self::rmrk_property(Metadata, &metadata)?,219 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,220 ]221 .into_iter(),222 )?;223 let rmrk_collection_id = <CollectionIndex<T>>::get();224225 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);226227 <PalletCommon<T>>::set_scoped_collection_property(228 unique_collection_id,229 PropertyScope::Rmrk,230 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,231 )?;232233 <CollectionIndex<T>>::mutate(|n| *n += 1);234235 Self::deposit_event(Event::CollectionCreated {236 issuer: sender,237 collection_id: rmrk_collection_id,238 });239240 Ok(())241 }242243 /// destroy collection244 #[transactional]245 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]246 pub fn destroy_collection(247 origin: OriginFor<T>,248 collection_id: RmrkCollectionId,249 ) -> DispatchResult {250 let sender = ensure_signed(origin)?;251 let cross_sender = T::CrossAccountId::from_sub(sender.clone());252253 let collection = Self::get_typed_nft_collection(254 Self::unique_collection_id(collection_id)?,255 misc::CollectionType::Regular,256 )?;257 collection.check_is_external()?;258259 <PalletNft<T>>::destroy_collection(collection, &cross_sender)260 .map_err(Self::map_unique_err_to_proxy)?;261262 Self::deposit_event(Event::CollectionDestroyed {263 issuer: sender,264 collection_id,265 });266267 Ok(())268 }269270 /// Change the issuer of a collection271 ///272 /// Parameters:273 /// - `origin`: sender of the transaction274 /// - `collection_id`: collection id of the nft to change issuer of275 /// - `new_issuer`: Collection's new issuer276 #[transactional]277 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]278 pub fn change_collection_issuer(279 origin: OriginFor<T>,280 collection_id: RmrkCollectionId,281 new_issuer: <T::Lookup as StaticLookup>::Source,282 ) -> DispatchResult {283 let sender = ensure_signed(origin)?;284285 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;286 collection.check_is_external()?;287288 let new_issuer = T::Lookup::lookup(new_issuer)?;289290 Self::change_collection_owner(291 Self::unique_collection_id(collection_id)?,292 misc::CollectionType::Regular,293 sender.clone(),294 new_issuer.clone(),295 )?;296297 Self::deposit_event(Event::IssuerChanged {298 old_issuer: sender,299 new_issuer,300 collection_id,301 });302303 Ok(())304 }305306 /// lock collection307 #[transactional]308 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]309 pub fn lock_collection(310 origin: OriginFor<T>,311 collection_id: RmrkCollectionId,312 ) -> DispatchResult {313 let sender = ensure_signed(origin)?;314 let cross_sender = T::CrossAccountId::from_sub(sender.clone());315316 let collection = Self::get_typed_nft_collection(317 Self::unique_collection_id(collection_id)?,318 misc::CollectionType::Regular,319 )?;320 collection.check_is_external()?;321322 Self::check_collection_owner(&collection, &cross_sender)?;323324 let token_count = collection.total_supply();325326 let mut collection = collection.into_inner();327 collection.limits.token_limit = Some(token_count);328 collection.save()?;329330 Self::deposit_event(Event::CollectionLocked {331 issuer: sender,332 collection_id,333 });334335 Ok(())336 }337338 /// Mints an NFT in the specified collection339 /// Sets metadata and the royalty attribute340 ///341 /// Parameters:342 /// - `collection_id`: The class of the asset to be minted.343 /// - `nft_id`: The nft value of the asset to be minted.344 /// - `recipient`: Receiver of the royalty345 /// - `royalty`: Permillage reward from each trade for the Recipient346 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash347 /// - `transferable`: Ability to transfer this NFT348 #[transactional]349 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]350 pub fn mint_nft(351 origin: OriginFor<T>,352 owner: T::AccountId,353 collection_id: RmrkCollectionId,354 recipient: Option<T::AccountId>,355 royalty_amount: Option<Permill>,356 metadata: RmrkString,357 transferable: bool,358 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,359 ) -> DispatchResult {360 let sender = ensure_signed(origin)?;361 let cross_sender = T::CrossAccountId::from_sub(sender.clone());362 let cross_owner = T::CrossAccountId::from_sub(owner.clone());363364 let collection = Self::get_typed_nft_collection(365 Self::unique_collection_id(collection_id)?,366 misc::CollectionType::Regular,367 )?;368 collection.check_is_external()?;369370 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {371 recipient: recipient.unwrap_or_else(|| owner.clone()),372 amount,373 });374375 let nft_id = Self::create_nft(376 &cross_sender,377 &cross_owner,378 &collection,379 [380 Self::rmrk_property(TokenType, &NftType::Regular)?,381 Self::rmrk_property(Transferable, &transferable)?,382 Self::rmrk_property(PendingNftAccept, &false)?,383 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,384 Self::rmrk_property(Metadata, &metadata)?,385 Self::rmrk_property(Equipped, &false)?,386 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,387 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,388 ]389 .into_iter(),390 )391 .map_err(|err| match err {392 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),393 err => Self::map_unique_err_to_proxy(err),394 })?;395396 if let Some(resources) = resources {397 for resource in resources {398 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;399 }400 }401402 Self::deposit_event(Event::NftMinted {403 owner,404 collection_id,405 nft_id: nft_id.0,406 });407408 Ok(())409 }410411 /// burn nft412 #[transactional]413 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]414 pub fn burn_nft(415 origin: OriginFor<T>,416 collection_id: RmrkCollectionId,417 nft_id: RmrkNftId,418 max_burns: u32,419 ) -> DispatchResult {420 let sender = ensure_signed(origin)?;421 let cross_sender = T::CrossAccountId::from_sub(sender.clone());422423 let collection = Self::get_typed_nft_collection(424 Self::unique_collection_id(collection_id)?,425 misc::CollectionType::Regular,426 )?;427 collection.check_is_external()?;428429 Self::destroy_nft(430 cross_sender,431 Self::unique_collection_id(collection_id)?,432 nft_id.into(),433 max_burns,434 <Error<T>>::NoPermission,435 )436 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;437438 Self::deposit_event(Event::NFTBurned {439 owner: sender,440 nft_id,441 });442443 Ok(())444 }445446 /// Transfers a NFT from an Account or NFT A to another Account or NFT B447 ///448 /// Parameters:449 /// - `origin`: sender of the transaction450 /// - `rmrk_collection_id`: collection id of the nft to be transferred451 /// - `rmrk_nft_id`: nft id of the nft to be transferred452 /// - `new_owner`: new owner of the nft which can be either an account or a NFT453 #[transactional]454 #[pallet::weight(<SelfWeightOf<T>>::send())]455 pub fn send(456 origin: OriginFor<T>,457 rmrk_collection_id: RmrkCollectionId,458 rmrk_nft_id: RmrkNftId,459 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,460 ) -> DispatchResult {461 let sender = ensure_signed(origin.clone())?;462 let cross_sender = T::CrossAccountId::from_sub(sender.clone());463464 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;465 let nft_id = rmrk_nft_id.into();466467 let collection =468 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;469 collection.check_is_external()?;470471 let token_data =472 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;473474 let from = token_data.owner;475476 ensure!(477 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,478 <Error<T>>::NonTransferable479 );480481 ensure!(482 !Self::get_nft_property_decoded(483 collection_id,484 nft_id,485 RmrkProperty::PendingNftAccept486 )?,487 <Error<T>>::NoPermission488 );489490 let target_owner;491 let approval_required;492493 match new_owner {494 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {495 target_owner = T::CrossAccountId::from_sub(account_id.clone());496 approval_required = false;497 }498 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(499 target_collection_id,500 target_nft_id,501 ) => {502 let target_collection_id = Self::unique_collection_id(target_collection_id)?;503504 let target_nft_budget = budget::Value::new(NESTING_BUDGET);505506 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(507 target_collection_id,508 target_nft_id.into(),509 Some((collection_id, nft_id)),510 &target_nft_budget,511 )512 .map_err(Self::map_unique_err_to_proxy)?;513514 approval_required = cross_sender != target_nft_owner;515516 if approval_required {517 target_owner = target_nft_owner;518519 <PalletNft<T>>::set_scoped_token_property(520 collection.id,521 nft_id,522 PropertyScope::Rmrk,523 Self::rmrk_property(PendingNftAccept, &approval_required)?,524 )?;525 } else {526 target_owner = T::CrossTokenAddressMapping::token_to_address(527 target_collection_id,528 target_nft_id.into(),529 );530 }531 }532 }533534 let src_nft_budget = budget::Value::new(NESTING_BUDGET);535536 <PalletNft<T>>::transfer_from(537 &collection,538 &cross_sender,539 &from,540 &target_owner,541 nft_id,542 &src_nft_budget,543 )544 .map_err(Self::map_unique_err_to_proxy)?;545546 Self::deposit_event(Event::NFTSent {547 sender,548 recipient: new_owner,549 collection_id: rmrk_collection_id,550 nft_id: rmrk_nft_id,551 approval_required,552 });553554 Ok(())555 }556557 /// Accepts an NFT sent from another account to self or owned NFT558 ///559 /// Parameters:560 /// - `origin`: sender of the transaction561 /// - `rmrk_collection_id`: collection id of the nft to be accepted562 /// - `rmrk_nft_id`: nft id of the nft to be accepted563 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was564 /// sent to565 #[transactional]566 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]567 pub fn accept_nft(568 origin: OriginFor<T>,569 rmrk_collection_id: RmrkCollectionId,570 rmrk_nft_id: RmrkNftId,571 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,572 ) -> DispatchResult {573 let sender = ensure_signed(origin.clone())?;574 let cross_sender = T::CrossAccountId::from_sub(sender.clone());575576 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;577 let nft_id = rmrk_nft_id.into();578579 let collection =580 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;581 collection.check_is_external()?;582583 let new_cross_owner = match new_owner {584 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {585 T::CrossAccountId::from_sub(account_id.clone())586 }587 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(588 target_collection_id,589 target_nft_id,590 ) => {591 let target_collection_id = Self::unique_collection_id(target_collection_id)?;592593 T::CrossTokenAddressMapping::token_to_address(594 target_collection_id,595 TokenId(target_nft_id),596 )597 }598 };599600 let budget = budget::Value::new(NESTING_BUDGET);601602 <PalletNft<T>>::transfer(603 &collection,604 &cross_sender,605 &new_cross_owner,606 nft_id,607 &budget,608 )609 .map_err(|err| {610 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {611 <Error<T>>::CannotAcceptNonOwnedNft.into()612 } else {613 Self::map_unique_err_to_proxy(err)614 }615 })?;616617 <PalletNft<T>>::set_scoped_token_property(618 collection.id,619 nft_id,620 PropertyScope::Rmrk,621 Self::rmrk_property(PendingNftAccept, &false)?,622 )?;623624 Self::deposit_event(Event::NFTAccepted {625 sender,626 recipient: new_owner,627 collection_id: rmrk_collection_id,628 nft_id: rmrk_nft_id,629 });630631 Ok(())632 }633634 /// Rejects an NFT sent from another account to self or owned NFT635 ///636 /// Parameters:637 /// - `origin`: sender of the transaction638 /// - `rmrk_collection_id`: collection id of the nft to be accepted639 /// - `rmrk_nft_id`: nft id of the nft to be accepted640 #[transactional]641 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]642 pub fn reject_nft(643 origin: OriginFor<T>,644 rmrk_collection_id: RmrkCollectionId,645 rmrk_nft_id: RmrkNftId,646 ) -> DispatchResult {647 let sender = ensure_signed(origin)?;648 let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651 let nft_id = rmrk_nft_id.into();652653 let collection =654 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655 collection.check_is_external()?;656657 ensure!(658 <TokenData<T>>::get((collection_id, nft_id)).is_some(),659 <Error<T>>::NoAvailableNftId660 );661662 ensure!(663 Self::get_nft_property_decoded(664 collection_id,665 nft_id,666 RmrkProperty::PendingNftAccept667 )?,668 <Error<T>>::NoPermission669 );670671 Self::destroy_nft(672 cross_sender,673 collection_id,674 nft_id,675 NESTING_BUDGET,676 <Error<T>>::CannotRejectNonOwnedNft,677 )678 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;679680 Self::deposit_event(Event::NFTRejected {681 sender,682 collection_id: rmrk_collection_id,683 nft_id: rmrk_nft_id,684 });685686 Ok(())687 }688689 /// accept the addition of a new resource to an existing NFT690 #[transactional]691 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]692 pub fn accept_resource(693 origin: OriginFor<T>,694 rmrk_collection_id: RmrkCollectionId,695 rmrk_nft_id: RmrkNftId,696 resource_id: RmrkResourceId,697 ) -> DispatchResult {698 let sender = ensure_signed(origin)?;699 let cross_sender = T::CrossAccountId::from_sub(sender);700701 let collection_id = Self::unique_collection_id(rmrk_collection_id)702 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;703 let collection =704 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;705 collection.check_is_external()?;706707 let nft_id = rmrk_nft_id.into();708709 let budget = budget::Value::new(NESTING_BUDGET);710711 let nft_owner =712 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)713 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;714715 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {716 ensure!(res.pending, <Error<T>>::ResourceNotPending);717 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);718719 res.pending = false;720721 Ok(())722 })?;723724 Self::deposit_event(Event::<T>::ResourceAccepted {725 nft_id: rmrk_nft_id,726 resource_id,727 });728729 Ok(())730 }731732 /// accept the removal of a resource of an existing NFT733 #[transactional]734 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]735 pub fn accept_resource_removal(736 origin: OriginFor<T>,737 rmrk_collection_id: RmrkCollectionId,738 rmrk_nft_id: RmrkNftId,739 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();751752 let budget = budget::Value::new(NESTING_BUDGET);753754 let nft_owner =755 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)756 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;757758 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);759760 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;761762 let resource_info = <PalletNft<T>>::token_sys_property((763 collection_id,764 nft_id,765 PropertyScope::Rmrk,766 resource_id_key.clone(),767 ))768 .ok_or(<Error<T>>::ResourceDoesntExist)?;769770 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;771772 ensure!(773 resource_info.pending_removal,774 <Error<T>>::ResourceNotPending775 );776777 <PalletNft<T>>::remove_token_sys_property(778 collection_id,779 nft_id,780 PropertyScope::Rmrk,781 resource_id_key,782 );783784 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {785 nft_id: rmrk_nft_id,786 resource_id,787 });788789 Ok(())790 }791792 /// set a custom value on an NFT793 #[transactional]794 #[pallet::weight(<SelfWeightOf<T>>::set_property())]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 #[transactional]854 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]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 #[transactional]893 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]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 RmrkResourceTypes::Basic(resource),912 )?;913914 Self::deposit_event(Event::ResourceAdded {915 nft_id,916 resource_id,917 });918 Ok(())919 }920921 /// Create composable resource922 #[transactional]923 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]924 pub fn add_composable_resource(925 origin: OriginFor<T>,926 rmrk_collection_id: RmrkCollectionId,927 nft_id: RmrkNftId,928 resource: RmrkComposableResource,929 ) -> DispatchResult {930 let sender = ensure_signed(origin.clone())?;931932 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;933 let collection =934 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935 collection.check_is_external()?;936937 let resource_id = Self::resource_add(938 sender,939 collection_id,940 nft_id.into(),941 RmrkResourceTypes::Composable(resource),942 )?;943944 Self::deposit_event(Event::ResourceAdded {945 nft_id,946 resource_id,947 });948 Ok(())949 }950951 /// Create slot resource952 #[transactional]953 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]954 pub fn add_slot_resource(955 origin: OriginFor<T>,956 rmrk_collection_id: RmrkCollectionId,957 nft_id: RmrkNftId,958 resource: RmrkSlotResource,959 ) -> DispatchResult {960 let sender = ensure_signed(origin.clone())?;961962 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;963 let collection =964 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965 collection.check_is_external()?;966967 let resource_id = Self::resource_add(968 sender,969 collection_id,970 nft_id.into(),971 RmrkResourceTypes::Slot(resource),972 )?;973974 Self::deposit_event(Event::ResourceAdded {975 nft_id,976 resource_id,977 });978 Ok(())979 }980981 /// remove resource982 #[transactional]983 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]984 pub fn remove_resource(985 origin: OriginFor<T>,986 rmrk_collection_id: RmrkCollectionId,987 nft_id: RmrkNftId,988 resource_id: RmrkResourceId,989 ) -> DispatchResult {990 let sender = ensure_signed(origin.clone())?;991992 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;993 let collection =994 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;995 collection.check_is_external()?;996997 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;998999 Self::deposit_event(Event::ResourceRemoval {1000 nft_id,1001 resource_id,1002 });1003 Ok(())1004 }1005 }1006}10071008impl<T: Config> Pallet<T> {1009 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1010 let key = rmrk_key.to_key::<T>()?;10111012 let scoped_key = PropertyScope::Rmrk1013 .apply(key)1014 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10151016 Ok(scoped_key)1017 }10181019 // todo think about renaming these1020 pub fn rmrk_property<E: Encode>(1021 rmrk_key: RmrkProperty,1022 value: &E,1023 ) -> Result<Property, DispatchError> {1024 let key = rmrk_key.to_key::<T>()?;10251026 let value = Self::encode_property(value)?;10271028 let property = Property { key, value };10291030 Ok(property)1031 }10321033 pub fn encode_property<E: Encode, S: Get<u32>>(1034 value: &E,1035 ) -> Result<BoundedBytes<S>, DispatchError> {1036 let value = value1037 .encode()1038 .try_into()1039 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10401041 Ok(value)1042 }10431044 pub fn decode_property<D: Decode, S: Get<u32>>(1045 vec: &BoundedBytes<S>,1046 ) -> Result<D, DispatchError> {1047 vec.decode()1048 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1049 }10501051 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1052 where1053 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1054 {1055 vec.rebind()1056 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1057 }10581059 fn init_collection(1060 sender: T::CrossAccountId,1061 data: CreateCollectionData<T::AccountId>,1062 properties: impl Iterator<Item = Property>,1063 ) -> Result<CollectionId, DispatchError> {1064 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10651066 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1067 return Err(<Error<T>>::NoAvailableCollectionId.into());1068 }10691070 <PalletCommon<T>>::set_scoped_collection_properties(1071 collection_id?,1072 PropertyScope::Rmrk,1073 properties,1074 )?;10751076 collection_id1077 }10781079 pub fn create_nft(1080 sender: &T::CrossAccountId,1081 owner: &T::CrossAccountId,1082 collection: &NonfungibleHandle<T>,1083 properties: impl Iterator<Item = Property>,1084 ) -> Result<TokenId, DispatchError> {1085 let data = CreateNftExData {1086 properties: BoundedVec::default(),1087 owner: owner.clone(),1088 };10891090 let budget = budget::Value::new(NESTING_BUDGET);10911092 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10931094 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10951096 <PalletNft<T>>::set_scoped_token_properties(1097 collection.id,1098 nft_id,1099 PropertyScope::Rmrk,1100 properties,1101 )?;11021103 Ok(nft_id)1104 }11051106 fn destroy_nft(1107 sender: T::CrossAccountId,1108 collection_id: CollectionId,1109 token_id: TokenId,1110 max_burns: u32,1111 error_if_not_owned: Error<T>,1112 ) -> DispatchResultWithPostInfo {1113 let collection =1114 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11151116 let token_data =1117 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11181119 let from = token_data.owner;11201121 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11221123 ensure!(1124 <PalletStructure<T>>::check_indirectly_owned(1125 sender.clone(),1126 collection_id,1127 token_id,1128 None,1129 &owner_check_budget1130 )?,1131 error_if_not_owned,1132 );11331134 let burns_budget = budget::Value::new(max_burns);1135 let breadth_budget = budget::Value::new(max_burns);11361137 <PalletNft<T>>::burn_recursively(1138 &collection,1139 &from,1140 token_id,1141 &burns_budget,1142 &breadth_budget,1143 )1144 }11451146 fn acquire_next_resource_id(1147 collection_id: CollectionId,1148 nft_id: TokenId,1149 ) -> Result<RmrkResourceId, DispatchError> {1150 let resource_id: RmrkResourceId =1151 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11521153 let next_id = resource_id1154 .checked_add(1)1155 .ok_or(<Error<T>>::NoAvailableResourceId)?;11561157 <PalletNft<T>>::set_scoped_token_property(1158 collection_id,1159 nft_id,1160 PropertyScope::Rmrk,1161 Self::rmrk_property(NextResourceId, &next_id)?,1162 )?;11631164 Ok(resource_id)1165 }11661167 fn resource_add(1168 sender: T::AccountId,1169 collection_id: CollectionId,1170 nft_id: TokenId,1171 resource: RmrkResourceTypes,1172 ) -> Result<RmrkResourceId, DispatchError> {1173 let collection =1174 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1175 ensure!(collection.owner == sender, Error::<T>::NoPermission);11761177 let sender = T::CrossAccountId::from_sub(sender);1178 let budget = budget::Value::new(NESTING_BUDGET);11791180 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1181 .map_err(Self::map_unique_err_to_proxy)?;11821183 let pending = sender != nft_owner;11841185 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11861187 let resource_info = RmrkResourceInfo {1188 id,1189 resource,1190 pending,1191 pending_removal: false,1192 };11931194 <PalletNft<T>>::try_mutate_token_sys_property(1195 collection_id,1196 nft_id,1197 PropertyScope::Rmrk,1198 Self::rmrk_property_key(ResourceId(id))?,1199 |value| -> DispatchResult {1200 *value = Some(Self::encode_property(&resource_info)?);12011202 Ok(())1203 },1204 )?;12051206 Ok(id)1207 }12081209 fn resource_remove(1210 sender: T::AccountId,1211 collection_id: CollectionId,1212 nft_id: TokenId,1213 resource_id: RmrkResourceId,1214 ) -> DispatchResult {1215 let collection =1216 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1217 ensure!(collection.owner == sender, Error::<T>::NoPermission);12181219 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1220 let scope = PropertyScope::Rmrk;12211222 ensure!(1223 <PalletNft<T>>::token_sys_property((1224 collection_id,1225 nft_id,1226 scope,1227 resource_id_key.clone()1228 ))1229 .is_some(),1230 <Error<T>>::ResourceDoesntExist1231 );12321233 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1234 let topmost_owner =1235 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12361237 let sender = T::CrossAccountId::from_sub(sender);1238 if topmost_owner == sender {1239 <PalletNft<T>>::remove_token_sys_property(1240 collection_id,1241 nft_id,1242 PropertyScope::Rmrk,1243 Self::rmrk_property_key(ResourceId(resource_id))?,1244 );1245 } else {1246 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1247 res.pending_removal = true;12481249 Ok(())1250 })?;1251 }12521253 Ok(())1254 }12551256 fn try_mutate_resource_info(1257 collection_id: CollectionId,1258 nft_id: TokenId,1259 resource_id: RmrkResourceId,1260 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1261 ) -> DispatchResult {1262 <PalletNft<T>>::try_mutate_token_sys_property(1263 collection_id,1264 nft_id,1265 PropertyScope::Rmrk,1266 Self::rmrk_property_key(ResourceId(resource_id))?,1267 |value| match value {1268 Some(value) => {1269 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12701271 f(&mut resource_info)?;12721273 *value = Self::encode_property(&resource_info)?;12741275 Ok(())1276 }1277 None => Err(<Error<T>>::ResourceDoesntExist.into()),1278 },1279 )1280 }12811282 fn change_collection_owner(1283 collection_id: CollectionId,1284 collection_type: misc::CollectionType,1285 sender: T::AccountId,1286 new_owner: T::AccountId,1287 ) -> DispatchResult {1288 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1289 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12901291 let mut collection = collection.into_inner();12921293 collection.owner = new_owner;1294 collection.save()1295 }12961297 fn check_collection_owner(1298 collection: &NonfungibleHandle<T>,1299 account: &T::CrossAccountId,1300 ) -> DispatchResult {1301 collection1302 .check_is_owner(account)1303 .map_err(Self::map_unique_err_to_proxy)1304 }13051306 pub fn last_collection_idx() -> RmrkCollectionId {1307 <CollectionIndex<T>>::get()1308 }13091310 pub fn unique_collection_id(1311 rmrk_collection_id: RmrkCollectionId,1312 ) -> Result<CollectionId, DispatchError> {1313 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1314 .map_err(|_| <Error<T>>::CollectionUnknown.into())1315 }13161317 pub fn rmrk_collection_id(1318 unique_collection_id: CollectionId,1319 ) -> Result<RmrkCollectionId, DispatchError> {1320 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1321 }13221323 pub fn get_nft_collection(1324 collection_id: CollectionId,1325 ) -> Result<NonfungibleHandle<T>, DispatchError> {1326 let collection = <CollectionHandle<T>>::try_get(collection_id)1327 .map_err(|_| <Error<T>>::CollectionUnknown)?;13281329 match collection.mode {1330 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1331 _ => Err(<Error<T>>::CollectionUnknown.into()),1332 }1333 }13341335 pub fn collection_exists(collection_id: CollectionId) -> bool {1336 <CollectionHandle<T>>::try_get(collection_id).is_ok()1337 }13381339 pub fn get_collection_property(1340 collection_id: CollectionId,1341 key: RmrkProperty,1342 ) -> Result<PropertyValue, DispatchError> {1343 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1344 .get(&Self::rmrk_property_key(key)?)1345 .ok_or(<Error<T>>::CollectionUnknown)?1346 .clone();13471348 Ok(collection_property)1349 }13501351 pub fn get_collection_property_decoded<V: Decode>(1352 collection_id: CollectionId,1353 key: RmrkProperty,1354 ) -> Result<V, DispatchError> {1355 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1356 }13571358 pub fn get_collection_type(1359 collection_id: CollectionId,1360 ) -> Result<misc::CollectionType, DispatchError> {1361 Self::get_collection_property_decoded(collection_id, CollectionType)1362 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1363 }13641365 pub fn ensure_collection_type(1366 collection_id: CollectionId,1367 collection_type: misc::CollectionType,1368 ) -> DispatchResult {1369 let actual_type = Self::get_collection_type(collection_id)?;1370 ensure!(1371 actual_type == collection_type,1372 <CommonError<T>>::NoPermission1373 );13741375 Ok(())1376 }13771378 pub fn get_typed_nft_collection(1379 collection_id: CollectionId,1380 collection_type: misc::CollectionType,1381 ) -> Result<NonfungibleHandle<T>, DispatchError> {1382 Self::ensure_collection_type(collection_id, collection_type)?;13831384 Self::get_nft_collection(collection_id)1385 }13861387 pub fn get_typed_nft_collection_mapped(1388 rmrk_collection_id: RmrkCollectionId,1389 collection_type: misc::CollectionType,1390 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1391 let unique_collection_id = match collection_type {1392 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1393 _ => rmrk_collection_id.into(),1394 };13951396 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13971398 Ok((collection, unique_collection_id))1399 }14001401 pub fn get_nft_property(1402 collection_id: CollectionId,1403 nft_id: TokenId,1404 key: RmrkProperty,1405 ) -> Result<PropertyValue, DispatchError> {1406 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1407 .get(&Self::rmrk_property_key(key)?)1408 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1409 .clone();14101411 Ok(nft_property)1412 }14131414 pub fn get_nft_property_decoded<V: Decode>(1415 collection_id: CollectionId,1416 nft_id: TokenId,1417 key: RmrkProperty,1418 ) -> Result<V, DispatchError> {1419 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1420 }14211422 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1423 <TokenData<T>>::contains_key((collection_id, nft_id))1424 }14251426 pub fn get_nft_type(1427 collection_id: CollectionId,1428 token_id: TokenId,1429 ) -> Result<NftType, DispatchError> {1430 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1431 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1432 }14331434 pub fn ensure_nft_type(1435 collection_id: CollectionId,1436 token_id: TokenId,1437 nft_type: NftType,1438 ) -> DispatchResult {1439 let actual_type = Self::get_nft_type(collection_id, token_id)?;1440 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14411442 Ok(())1443 }14441445 pub fn ensure_nft_owner(1446 collection_id: CollectionId,1447 token_id: TokenId,1448 possible_owner: &T::CrossAccountId,1449 nesting_budget: &dyn budget::Budget,1450 ) -> DispatchResult {1451 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1452 possible_owner.clone(),1453 collection_id,1454 token_id,1455 None,1456 nesting_budget,1457 )1458 .map_err(Self::map_unique_err_to_proxy)?;14591460 ensure!(is_owned, <Error<T>>::NoPermission);14611462 Ok(())1463 }14641465 pub fn filter_user_properties<Key, Value, R, Mapper>(1466 collection_id: CollectionId,1467 token_id: Option<TokenId>,1468 filter_keys: Option<Vec<RmrkPropertyKey>>,1469 mapper: Mapper,1470 ) -> Result<Vec<R>, DispatchError>1471 where1472 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1473 Value: Decode + Default,1474 Mapper: Fn(Key, Value) -> R,1475 {1476 filter_keys1477 .map(|keys| {1478 let properties = keys1479 .into_iter()1480 .filter_map(|key| {1481 let key: Key = key.try_into().ok()?;14821483 let value = match token_id {1484 Some(token_id) => Self::get_nft_property_decoded(1485 collection_id,1486 token_id,1487 UserProperty(key.as_ref()),1488 ),1489 None => Self::get_collection_property_decoded(1490 collection_id,1491 UserProperty(key.as_ref()),1492 ),1493 }1494 .ok()?;14951496 Some(mapper(key, value))1497 })1498 .collect();14991500 Ok(properties)1501 })1502 .unwrap_or_else(|| {1503 let properties =1504 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15051506 Ok(properties)1507 })1508 }15091510 pub fn iterate_user_properties<Key, Value, R, Mapper>(1511 collection_id: CollectionId,1512 token_id: Option<TokenId>,1513 mapper: Mapper,1514 ) -> Result<impl Iterator<Item = R>, DispatchError>1515 where1516 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1517 Value: Decode + Default,1518 Mapper: Fn(Key, Value) -> R,1519 {1520 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15211522 let properties = match token_id {1523 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1524 None => <PalletCommon<T>>::collection_properties(collection_id),1525 };15261527 let properties = properties.into_iter().filter_map(move |(key, value)| {1528 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15291530 let key: Key = key.to_vec().try_into().ok()?;1531 let value: Value = value.decode().ok()?;15321533 Some(mapper(key, value))1534 });15351536 Ok(properties)1537 }15381539 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1540 map_unique_err_to_proxy! {1541 match err {1542 CommonError::NoPermission => NoPermission,1543 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1544 CommonError::PublicMintingNotAllowed => NoPermission,1545 CommonError::TokenNotFound => NoAvailableNftId,1546 CommonError::ApprovedValueTooLow => NoPermission,1547 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1548 StructureError::TokenNotFound => NoAvailableNftId,1549 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1550 }1551 }1552 }1553}pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -61,22 +61,13 @@
#[derive(Encode, Decode, PartialEq, Eq)]
pub enum CollectionType {
Regular,
- Resource,
Base,
}
#[derive(Encode, Decode, PartialEq, Eq)]
pub enum NftType {
Regular,
- Resource,
FixedPart,
SlotPart,
Theme,
-}
-
-#[derive(Encode, Decode, PartialEq, Eq)]
-pub enum ResourceType {
- Basic,
- Composable,
- Slot,
}
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -17,6 +17,8 @@
use super::*;
use core::convert::AsRef;
+const RESOURCE_ID_PREFIX: &str = "rsid-";
+
pub enum RmrkProperty<'r> {
Metadata,
CollectionType,
@@ -25,18 +27,13 @@
Transferable,
RoyaltyInfo,
Equipped,
- ResourceCollection,
ResourcePriorities,
- ResourceType,
+ NextResourceId,
+ ResourceId(RmrkResourceId),
PendingNftAccept,
- PendingResourceAccept,
- PendingResourceRemoval,
Parts,
Base,
Src,
- Slot,
- License,
- Thumb,
EquippedNft,
BaseType,
ExternalPartId,
@@ -72,18 +69,13 @@
Self::Transferable => key!("transferable"),
Self::RoyaltyInfo => key!("royalty-info"),
Self::Equipped => key!("equipped"),
- Self::ResourceCollection => key!("resource-collection"),
Self::ResourcePriorities => key!("resource-priorities"),
- Self::ResourceType => key!("resource-type"),
+ Self::NextResourceId => key!("next-resource-id"),
+ Self::ResourceId(id) => key!(RESOURCE_ID_PREFIX, id.to_le_bytes()),
Self::PendingNftAccept => key!("pending-nft-accept"),
- Self::PendingResourceAccept => key!("pending-resource-accept"),
- Self::PendingResourceRemoval => key!("pending-resource-removal"),
Self::Parts => key!("parts"),
Self::Base => key!("base"),
Self::Src => key!("src"),
- Self::Slot => key!("slot"),
- Self::License => key!("license"),
- Self::Thumb => key!("thumb"),
Self::EquippedNft => key!("equipped-nft"),
Self::BaseType => key!("base-type"),
Self::ExternalPartId => key!("ext-part-id"),
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -75,9 +75,8 @@
mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkThemeProperty, RmrkCollectionId,
- RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceTypes,
- RmrkBasicResource, RmrkComposableResource, RmrkSlotResource, RmrkResourceId, RmrkBaseId,
- RmrkFixedPart, RmrkSlotPart, RmrkString,
+ RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceId,
+ RmrkBaseId, RmrkFixedPart, RmrkSlotPart, RmrkString,
};
// use pallet_contracts::weights::WeightInfo;
@@ -1501,8 +1500,8 @@
}
fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
- use pallet_common::CommonCollectionOperations;
+ use pallet_proxy_rmrk_core::misc::{CollectionType, NftType};
+ use up_data_structs::PropertyScope;
let collection_id = match RmrkCore::unique_collection_id(collection_id) {
Ok(id) => id,
@@ -1513,48 +1512,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: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+ let resources = <pallet_nonfungible::Pallet<Runtime>>::iterate_token_sys_properties(
+ collection_id, nft_id, PropertyScope::Rmrk
+ ).filter_map(|(_, value)| {
+ let resource_info: RmrkResourceInfo = RmrkCore::decode_property(&value).ok()?;
- 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
- .collection_tokens()
- .iter()
- .filter_map(|res_id| Some(RmrkResourceInfo {
- id: res_id.0,
- pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
- pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
- resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
- ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
- parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- },
- }))
- .collect();
+ Some(resource_info)
+ }).collect();
Ok(resources)
}
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -75,9 +75,8 @@
mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkThemeProperty, RmrkCollectionId,
- RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceTypes,
- RmrkBasicResource, RmrkComposableResource, RmrkSlotResource, RmrkResourceId, RmrkBaseId,
- RmrkFixedPart, RmrkSlotPart, RmrkString,
+ RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceId,
+ RmrkBaseId, RmrkFixedPart, RmrkSlotPart, RmrkString,
};
// use pallet_contracts::weights::WeightInfo;
@@ -1501,8 +1500,8 @@
}
fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
- use pallet_common::CommonCollectionOperations;
+ use pallet_proxy_rmrk_core::misc::{CollectionType, NftType};
+ use up_data_structs::PropertyScope;
let collection_id = match RmrkCore::unique_collection_id(collection_id) {
Ok(id) => id,
@@ -1513,48 +1512,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: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+ let resources = <pallet_nonfungible::Pallet<Runtime>>::iterate_token_sys_properties(
+ collection_id, nft_id, PropertyScope::Rmrk
+ ).filter_map(|(_, value)| {
+ let resource_info: RmrkResourceInfo = RmrkCore::decode_property(&value).ok()?;
- 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
- .collection_tokens()
- .iter()
- .filter_map(|res_id| Some(RmrkResourceInfo {
- id: res_id.0,
- pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
- pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
- resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
- ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
- parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- },
- }))
- .collect();
+ Some(resource_info)
+ }).collect();
Ok(resources)
}