difftreelog
feat(rmrk) add associated bases property
in: master
2 files changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{20 pallet_prelude::*,21 transactional,22 BoundedVec,23 dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::btree_set::BTreeSet};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenMap = BTreeSet<PendingChild>;5960#[frame_support::pallet]61pub mod pallet {62 use super::*;63 use pallet_evm::account;6465 #[pallet::config]66 pub trait Config:67 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config68 {69 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;70 type WeightInfo: WeightInfo;71 }7273 #[pallet::storage]74 #[pallet::getter(fn collection_index)]75 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7677 #[pallet::storage]78 pub type UniqueCollectionId<T: Config> =79 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8081 #[pallet::pallet]82 #[pallet::generate_store(pub(super) trait Store)]83 pub struct Pallet<T>(_);8485 #[pallet::event]86 #[pallet::generate_deposit(pub(super) fn deposit_event)]87 pub enum Event<T: Config> {88 CollectionCreated {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 CollectionDestroyed {93 issuer: T::AccountId,94 collection_id: RmrkCollectionId,95 },96 IssuerChanged {97 old_issuer: T::AccountId,98 new_issuer: T::AccountId,99 collection_id: RmrkCollectionId,100 },101 CollectionLocked {102 issuer: T::AccountId,103 collection_id: RmrkCollectionId,104 },105 NftMinted {106 owner: T::AccountId,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 },110 NFTBurned {111 owner: T::AccountId,112 nft_id: RmrkNftId,113 },114 NFTSent {115 sender: T::AccountId,116 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,117 collection_id: RmrkCollectionId,118 nft_id: RmrkNftId,119 approval_required: bool,120 },121 NFTAccepted {122 sender: T::AccountId,123 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,124 collection_id: RmrkCollectionId,125 nft_id: RmrkNftId,126 },127 NFTRejected {128 sender: T::AccountId,129 collection_id: RmrkCollectionId,130 nft_id: RmrkNftId,131 },132 PropertySet {133 collection_id: RmrkCollectionId,134 maybe_nft_id: Option<RmrkNftId>,135 key: RmrkKeyString,136 value: RmrkValueString,137 },138 ResourceAdded {139 nft_id: RmrkNftId,140 resource_id: RmrkResourceId,141 },142 ResourceRemoval {143 nft_id: RmrkNftId,144 resource_id: RmrkResourceId,145 },146 ResourceAccepted {147 nft_id: RmrkNftId,148 resource_id: RmrkResourceId,149 },150 ResourceRemovalAccepted {151 nft_id: RmrkNftId,152 resource_id: RmrkResourceId,153 },154 PrioritySet {155 collection_id: RmrkCollectionId,156 nft_id: RmrkNftId,157 },158 }159160 #[pallet::error]161 pub enum Error<T> {162 /* Unique-specific events */163 CorruptedCollectionType,164 NftTypeEncodeError,165 RmrkPropertyKeyIsTooLong,166 RmrkPropertyValueIsTooLong,167 RmrkPropertyIsNotFound,168 UnableToDecodeRmrkData,169170 /* RMRK compatible events */171 CollectionNotEmpty,172 NoAvailableCollectionId,173 NoAvailableNftId,174 CollectionUnknown,175 NoPermission,176 NonTransferable,177 CollectionFullOrLocked,178 ResourceDoesntExist,179 CannotSendToDescendentOrSelf,180 CannotAcceptNonOwnedNft,181 CannotRejectNonOwnedNft,182 CannotRejectNonPendingNft,183 ResourceNotPending,184 NoAvailableResourceId,185 }186187 #[pallet::call]188 impl<T: Config> Pallet<T> {189 /// Create a collection190 #[transactional]191 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]192 pub fn create_collection(193 origin: OriginFor<T>,194 metadata: RmrkString,195 max: Option<u32>,196 symbol: RmrkCollectionSymbol,197 ) -> DispatchResult {198 let sender = ensure_signed(origin)?;199200 let limits = CollectionLimits {201 owner_can_transfer: Some(false),202 token_limit: max,203 ..Default::default()204 };205206 let data = CreateCollectionData {207 limits: Some(limits),208 token_prefix: symbol209 .into_inner()210 .try_into()211 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,212 permissions: Some(CollectionPermissions {213 nesting: Some(NestingPermissions {214 token_owner: true,215 collection_admin: false,216 restricted: None,217 #[cfg(feature = "runtime-benchmarks")]218 permissive: false,219 }),220 ..Default::default()221 }),222 ..Default::default()223 };224225 let unique_collection_id = Self::init_collection(226 T::CrossAccountId::from_sub(sender.clone()),227 data,228 [229 Self::rmrk_property(Metadata, &metadata)?,230 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,231 ]232 .into_iter(),233 )?;234 let rmrk_collection_id = <CollectionIndex<T>>::get();235236 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);237238 <PalletCommon<T>>::set_scoped_collection_property(239 unique_collection_id,240 PropertyScope::Rmrk,241 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,242 )?;243244 <CollectionIndex<T>>::mutate(|n| *n += 1);245246 Self::deposit_event(Event::CollectionCreated {247 issuer: sender,248 collection_id: rmrk_collection_id,249 });250251 Ok(())252 }253254 /// destroy collection255 #[transactional]256 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]257 pub fn destroy_collection(258 origin: OriginFor<T>,259 collection_id: RmrkCollectionId,260 ) -> DispatchResult {261 let sender = ensure_signed(origin)?;262 let cross_sender = T::CrossAccountId::from_sub(sender.clone());263264 let collection = Self::get_typed_nft_collection(265 Self::unique_collection_id(collection_id)?,266 misc::CollectionType::Regular,267 )?;268 collection.check_is_external()?;269270 <PalletNft<T>>::destroy_collection(collection, &cross_sender)271 .map_err(Self::map_unique_err_to_proxy)?;272273 Self::deposit_event(Event::CollectionDestroyed {274 issuer: sender,275 collection_id,276 });277278 Ok(())279 }280281 /// Change the issuer of a collection282 ///283 /// Parameters:284 /// - `origin`: sender of the transaction285 /// - `collection_id`: collection id of the nft to change issuer of286 /// - `new_issuer`: Collection's new issuer287 #[transactional]288 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]289 pub fn change_collection_issuer(290 origin: OriginFor<T>,291 collection_id: RmrkCollectionId,292 new_issuer: <T::Lookup as StaticLookup>::Source,293 ) -> DispatchResult {294 let sender = ensure_signed(origin)?;295296 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;297 collection.check_is_external()?;298299 let new_issuer = T::Lookup::lookup(new_issuer)?;300301 Self::change_collection_owner(302 Self::unique_collection_id(collection_id)?,303 misc::CollectionType::Regular,304 sender.clone(),305 new_issuer.clone(),306 )?;307308 Self::deposit_event(Event::IssuerChanged {309 old_issuer: sender,310 new_issuer,311 collection_id,312 });313314 Ok(())315 }316317 /// lock collection318 #[transactional]319 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]320 pub fn lock_collection(321 origin: OriginFor<T>,322 collection_id: RmrkCollectionId,323 ) -> DispatchResult {324 let sender = ensure_signed(origin)?;325 let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327 let collection = Self::get_typed_nft_collection(328 Self::unique_collection_id(collection_id)?,329 misc::CollectionType::Regular,330 )?;331 collection.check_is_external()?;332333 Self::check_collection_owner(&collection, &cross_sender)?;334335 let token_count = collection.total_supply();336337 let mut collection = collection.into_inner();338 collection.limits.token_limit = Some(token_count);339 collection.save()?;340341 Self::deposit_event(Event::CollectionLocked {342 issuer: sender,343 collection_id,344 });345346 Ok(())347 }348349 /// Mints an NFT in the specified collection350 /// Sets metadata and the royalty attribute351 ///352 /// Parameters:353 /// - `collection_id`: The class of the asset to be minted.354 /// - `nft_id`: The nft value of the asset to be minted.355 /// - `recipient`: Receiver of the royalty356 /// - `royalty`: Permillage reward from each trade for the Recipient357 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash358 /// - `transferable`: Ability to transfer this NFT359 #[transactional]360 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]361 pub fn mint_nft(362 origin: OriginFor<T>,363 owner: Option<T::AccountId>,364 collection_id: RmrkCollectionId,365 recipient: Option<T::AccountId>,366 royalty_amount: Option<Permill>,367 metadata: RmrkString,368 transferable: bool,369 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,370 ) -> DispatchResult {371 let sender = ensure_signed(origin)?;372 let cross_sender = T::CrossAccountId::from_sub(sender.clone());373374 let owner = owner.unwrap_or(sender.clone());375 let cross_owner = T::CrossAccountId::from_sub(owner.clone());376377 let collection = Self::get_typed_nft_collection(378 Self::unique_collection_id(collection_id)?,379 misc::CollectionType::Regular,380 )?;381 collection.check_is_external()?;382383 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {384 recipient: recipient.unwrap_or_else(|| owner.clone()),385 amount,386 });387388 let nft_id = Self::create_nft(389 &cross_sender,390 &cross_owner,391 &collection,392 [393 Self::rmrk_property(TokenType, &NftType::Regular)?,394 Self::rmrk_property(Transferable, &transferable)?,395 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,396 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,397 Self::rmrk_property(Metadata, &metadata)?,398 Self::rmrk_property(Equipped, &false)?,399 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,400 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,401 Self::rmrk_property(PendingChildren, &PendingChildrenMap::new())?,402 ]403 .into_iter(),404 )405 .map_err(|err| match err {406 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),407 err => Self::map_unique_err_to_proxy(err),408 })?;409410 if let Some(resources) = resources {411 for resource in resources {412 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;413 }414 }415416 Self::deposit_event(Event::NftMinted {417 owner,418 collection_id,419 nft_id: nft_id.0,420 });421422 Ok(())423 }424425 /// burn nft426 #[transactional]427 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]428 pub fn burn_nft(429 origin: OriginFor<T>,430 collection_id: RmrkCollectionId,431 nft_id: RmrkNftId,432 max_burns: u32,433 ) -> DispatchResult {434 let sender = ensure_signed(origin)?;435 let cross_sender = T::CrossAccountId::from_sub(sender.clone());436437 let collection = Self::get_typed_nft_collection(438 Self::unique_collection_id(collection_id)?,439 misc::CollectionType::Regular,440 )?;441 collection.check_is_external()?;442443 Self::destroy_nft(444 cross_sender,445 Self::unique_collection_id(collection_id)?,446 nft_id.into(),447 max_burns,448 <Error<T>>::NoPermission,449 )450 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;451452 Self::deposit_event(Event::NFTBurned {453 owner: sender,454 nft_id,455 });456457 Ok(())458 }459460 /// Transfers a NFT from an Account or NFT A to another Account or NFT B461 ///462 /// Parameters:463 /// - `origin`: sender of the transaction464 /// - `rmrk_collection_id`: collection id of the nft to be transferred465 /// - `rmrk_nft_id`: nft id of the nft to be transferred466 /// - `new_owner`: new owner of the nft which can be either an account or a NFT467 #[transactional]468 #[pallet::weight(<SelfWeightOf<T>>::send())]469 pub fn send(470 origin: OriginFor<T>,471 rmrk_collection_id: RmrkCollectionId,472 rmrk_nft_id: RmrkNftId,473 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,474 ) -> DispatchResult {475 let sender = ensure_signed(origin.clone())?;476 let cross_sender = T::CrossAccountId::from_sub(sender.clone());477478 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;479 let nft_id = rmrk_nft_id.into();480481 let collection =482 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;483 collection.check_is_external()?;484485 let token_data =486 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;487488 let from = token_data.owner;489490 ensure!(491 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,492 <Error<T>>::NonTransferable493 );494495 ensure!(496 Self::get_nft_property_decoded::<Option<PendingTarget>>(497 collection_id,498 nft_id,499 RmrkProperty::PendingNftAccept500 )?.is_none(),501 <Error<T>>::NoPermission502 );503504 let target_owner;505 let approval_required;506507 match new_owner {508 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {509 target_owner = T::CrossAccountId::from_sub(account_id.clone());510 approval_required = false;511 }512 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(513 target_collection_id,514 target_nft_id,515 ) => {516 let target_collection_id = Self::unique_collection_id(target_collection_id)?;517518 let target_nft_budget = budget::Value::new(NESTING_BUDGET);519520 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(521 target_collection_id,522 target_nft_id.into(),523 Some((collection_id, nft_id)),524 &target_nft_budget,525 )526 .map_err(Self::map_unique_err_to_proxy)?;527528 approval_required = cross_sender != target_nft_owner;529530 if approval_required {531 target_owner = target_nft_owner;532533 <PalletNft<T>>::set_scoped_token_property(534 collection.id,535 nft_id,536 PropertyScope::Rmrk,537 Self::rmrk_property::<Option<PendingTarget>>(538 PendingNftAccept,539 &Some((target_collection_id, target_nft_id.into()))540 )?,541 )?;542543 Self::insert_pending_child(544 (target_collection_id, target_nft_id.into()),545 (rmrk_collection_id, rmrk_nft_id),546 )?;547 } else {548 target_owner = T::CrossTokenAddressMapping::token_to_address(549 target_collection_id,550 target_nft_id.into(),551 );552 }553 }554 }555556 let src_nft_budget = budget::Value::new(NESTING_BUDGET);557558 <PalletNft<T>>::transfer_from(559 &collection,560 &cross_sender,561 &from,562 &target_owner,563 nft_id,564 &src_nft_budget,565 )566 .map_err(Self::map_unique_err_to_proxy)?;567568 Self::deposit_event(Event::NFTSent {569 sender,570 recipient: new_owner,571 collection_id: rmrk_collection_id,572 nft_id: rmrk_nft_id,573 approval_required,574 });575576 Ok(())577 }578579 /// Accepts an NFT sent from another account to self or owned NFT580 ///581 /// Parameters:582 /// - `origin`: sender of the transaction583 /// - `rmrk_collection_id`: collection id of the nft to be accepted584 /// - `rmrk_nft_id`: nft id of the nft to be accepted585 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was586 /// sent to587 #[transactional]588 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]589 pub fn accept_nft(590 origin: OriginFor<T>,591 rmrk_collection_id: RmrkCollectionId,592 rmrk_nft_id: RmrkNftId,593 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,594 ) -> DispatchResult {595 let sender = ensure_signed(origin.clone())?;596 let cross_sender = T::CrossAccountId::from_sub(sender.clone());597598 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;599 let nft_id = rmrk_nft_id.into();600601 let collection =602 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;603 collection.check_is_external()?;604605 let new_cross_owner = match new_owner {606 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {607 T::CrossAccountId::from_sub(account_id.clone())608 }609 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(610 target_collection_id,611 target_nft_id,612 ) => {613 let target_collection_id = Self::unique_collection_id(target_collection_id)?;614615 T::CrossTokenAddressMapping::token_to_address(616 target_collection_id,617 TokenId(target_nft_id),618 )619 }620 };621622 let budget = budget::Value::new(NESTING_BUDGET);623624 <PalletNft<T>>::transfer(625 &collection,626 &cross_sender,627 &new_cross_owner,628 nft_id,629 &budget,630 )631 .map_err(|err| {632 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {633 <Error<T>>::CannotAcceptNonOwnedNft.into()634 } else {635 Self::map_unique_err_to_proxy(err)636 }637 })?;638639 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(640 collection_id,641 nft_id,642 RmrkProperty::PendingNftAccept643 )?;644645 if let Some(pending_target) = pending_target {646 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;647648 <PalletNft<T>>::set_scoped_token_property(649 collection.id,650 nft_id,651 PropertyScope::Rmrk,652 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,653 )?;654 }655656 Self::deposit_event(Event::NFTAccepted {657 sender,658 recipient: new_owner,659 collection_id: rmrk_collection_id,660 nft_id: rmrk_nft_id,661 });662663 Ok(())664 }665666 /// Rejects an NFT sent from another account to self or owned NFT667 ///668 /// Parameters:669 /// - `origin`: sender of the transaction670 /// - `rmrk_collection_id`: collection id of the nft to be accepted671 /// - `rmrk_nft_id`: nft id of the nft to be accepted672 #[transactional]673 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]674 pub fn reject_nft(675 origin: OriginFor<T>,676 rmrk_collection_id: RmrkCollectionId,677 rmrk_nft_id: RmrkNftId,678 ) -> DispatchResult {679 let sender = ensure_signed(origin)?;680 let cross_sender = T::CrossAccountId::from_sub(sender.clone());681682 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;683 let nft_id = rmrk_nft_id.into();684685 let collection =686 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;687 collection.check_is_external()?;688689 ensure!(690 <TokenData<T>>::get((collection_id, nft_id)).is_some(),691 <Error<T>>::NoAvailableNftId692 );693694695 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(696 collection_id,697 nft_id,698 RmrkProperty::PendingNftAccept699 )?;700701 match pending_target {702 Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,703 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),704 }705706 Self::destroy_nft(707 cross_sender,708 collection_id,709 nft_id,710 NESTING_BUDGET,711 <Error<T>>::CannotRejectNonOwnedNft,712 )713 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;714715 Self::deposit_event(Event::NFTRejected {716 sender,717 collection_id: rmrk_collection_id,718 nft_id: rmrk_nft_id,719 });720721 Ok(())722 }723724 /// accept the addition of a new resource to an existing NFT725 #[transactional]726 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]727 pub fn accept_resource(728 origin: OriginFor<T>,729 rmrk_collection_id: RmrkCollectionId,730 rmrk_nft_id: RmrkNftId,731 resource_id: RmrkResourceId,732 ) -> DispatchResult {733 let sender = ensure_signed(origin)?;734 let cross_sender = T::CrossAccountId::from_sub(sender);735736 let collection_id = Self::unique_collection_id(rmrk_collection_id)737 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;738 let collection =739 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;740 collection.check_is_external()?;741742 let nft_id = rmrk_nft_id.into();743744 let budget = budget::Value::new(NESTING_BUDGET);745746 let nft_owner =747 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)748 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;749750 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {751 ensure!(res.pending, <Error<T>>::ResourceNotPending);752 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754 res.pending = false;755756 Ok(())757 })?;758759 Self::deposit_event(Event::<T>::ResourceAccepted {760 nft_id: rmrk_nft_id,761 resource_id,762 });763764 Ok(())765 }766767 /// accept the removal of a resource of an existing NFT768 #[transactional]769 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]770 pub fn accept_resource_removal(771 origin: OriginFor<T>,772 rmrk_collection_id: RmrkCollectionId,773 rmrk_nft_id: RmrkNftId,774 resource_id: RmrkResourceId,775 ) -> DispatchResult {776 let sender = ensure_signed(origin)?;777 let cross_sender = T::CrossAccountId::from_sub(sender);778779 let collection_id = Self::unique_collection_id(rmrk_collection_id)780 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;781 let collection =782 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;783 collection.check_is_external()?;784785 let nft_id = rmrk_nft_id.into();786787 let budget = budget::Value::new(NESTING_BUDGET);788789 let nft_owner =790 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)791 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;792793 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);794795 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;796797 let resource_info = <PalletNft<T>>::token_aux_property((798 collection_id,799 nft_id,800 PropertyScope::Rmrk,801 resource_id_key.clone(),802 ))803 .ok_or(<Error<T>>::ResourceDoesntExist)?;804805 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;806807 ensure!(808 resource_info.pending_removal,809 <Error<T>>::ResourceNotPending810 );811812 <PalletNft<T>>::remove_token_aux_property(813 collection_id,814 nft_id,815 PropertyScope::Rmrk,816 resource_id_key,817 );818819 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {820 nft_id: rmrk_nft_id,821 resource_id,822 });823824 Ok(())825 }826827 /// set a custom value on an NFT828 #[transactional]829 #[pallet::weight(<SelfWeightOf<T>>::set_property())]830 pub fn set_property(831 origin: OriginFor<T>,832 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,833 maybe_nft_id: Option<RmrkNftId>,834 key: RmrkKeyString,835 value: RmrkValueString,836 ) -> DispatchResult {837 let sender = ensure_signed(origin)?;838 let sender = T::CrossAccountId::from_sub(sender);839840 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;841 let collection =842 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;843 collection.check_is_external()?;844845 let budget = budget::Value::new(NESTING_BUDGET);846847 match maybe_nft_id {848 Some(nft_id) => {849 let token_id: TokenId = nft_id.into();850851 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;852 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;853854 <PalletNft<T>>::set_scoped_token_property(855 collection_id,856 token_id,857 PropertyScope::Rmrk,858 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,859 )?;860 }861 None => {862 let collection = Self::get_typed_nft_collection(863 collection_id,864 misc::CollectionType::Regular,865 )?;866867 Self::check_collection_owner(&collection, &sender)?;868869 <PalletCommon<T>>::set_scoped_collection_property(870 collection_id,871 PropertyScope::Rmrk,872 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,873 )?;874 }875 }876877 Self::deposit_event(Event::PropertySet {878 collection_id: rmrk_collection_id,879 maybe_nft_id,880 key,881 value,882 });883884 Ok(())885 }886887 /// set a different order of resource priority888 #[transactional]889 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]890 pub fn set_priority(891 origin: OriginFor<T>,892 rmrk_collection_id: RmrkCollectionId,893 rmrk_nft_id: RmrkNftId,894 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,895 ) -> DispatchResult {896 let sender = ensure_signed(origin)?;897 let sender = T::CrossAccountId::from_sub(sender);898899 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;900 let nft_id = rmrk_nft_id.into();901902 let collection =903 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;904 collection.check_is_external()?;905906 let budget = budget::Value::new(NESTING_BUDGET);907908 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;909 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;910911 <PalletNft<T>>::set_scoped_token_property(912 collection_id,913 nft_id,914 PropertyScope::Rmrk,915 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,916 )?;917918 Self::deposit_event(Event::<T>::PrioritySet {919 collection_id: rmrk_collection_id,920 nft_id: rmrk_nft_id,921 });922923 Ok(())924 }925926 /// Create basic resource927 #[transactional]928 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]929 pub fn add_basic_resource(930 origin: OriginFor<T>,931 rmrk_collection_id: RmrkCollectionId,932 nft_id: RmrkNftId,933 resource: RmrkBasicResource,934 ) -> DispatchResult {935 let sender = ensure_signed(origin.clone())?;936937 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;938 let collection =939 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;940 collection.check_is_external()?;941942 let resource_id = Self::resource_add(943 sender,944 collection_id,945 nft_id.into(),946 RmrkResourceTypes::Basic(resource),947 )?;948949 Self::deposit_event(Event::ResourceAdded {950 nft_id,951 resource_id,952 });953 Ok(())954 }955956 /// Create composable resource957 #[transactional]958 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]959 pub fn add_composable_resource(960 origin: OriginFor<T>,961 rmrk_collection_id: RmrkCollectionId,962 nft_id: RmrkNftId,963 resource: RmrkComposableResource,964 ) -> DispatchResult {965 let sender = ensure_signed(origin.clone())?;966967 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;968 let collection =969 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;970 collection.check_is_external()?;971972 let resource_id = Self::resource_add(973 sender,974 collection_id,975 nft_id.into(),976 RmrkResourceTypes::Composable(resource),977 )?;978979 Self::deposit_event(Event::ResourceAdded {980 nft_id,981 resource_id,982 });983 Ok(())984 }985986 /// Create slot resource987 #[transactional]988 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]989 pub fn add_slot_resource(990 origin: OriginFor<T>,991 rmrk_collection_id: RmrkCollectionId,992 nft_id: RmrkNftId,993 resource: RmrkSlotResource,994 ) -> DispatchResult {995 let sender = ensure_signed(origin.clone())?;996997 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;998 let collection =999 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1000 collection.check_is_external()?;10011002 let resource_id = Self::resource_add(1003 sender,1004 collection_id,1005 nft_id.into(),1006 RmrkResourceTypes::Slot(resource),1007 )?;10081009 Self::deposit_event(Event::ResourceAdded {1010 nft_id,1011 resource_id,1012 });1013 Ok(())1014 }10151016 /// remove resource1017 #[transactional]1018 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1019 pub fn remove_resource(1020 origin: OriginFor<T>,1021 rmrk_collection_id: RmrkCollectionId,1022 nft_id: RmrkNftId,1023 resource_id: RmrkResourceId,1024 ) -> DispatchResult {1025 let sender = ensure_signed(origin.clone())?;10261027 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1028 let collection =1029 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1030 collection.check_is_external()?;10311032 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10331034 Self::deposit_event(Event::ResourceRemoval {1035 nft_id,1036 resource_id,1037 });1038 Ok(())1039 }1040 }1041}10421043impl<T: Config> Pallet<T> {1044 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1045 let key = rmrk_key.to_key::<T>()?;10461047 let scoped_key = PropertyScope::Rmrk1048 .apply(key)1049 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10501051 Ok(scoped_key)1052 }10531054 // todo think about renaming these1055 pub fn rmrk_property<E: Encode>(1056 rmrk_key: RmrkProperty,1057 value: &E,1058 ) -> Result<Property, DispatchError> {1059 let key = rmrk_key.to_key::<T>()?;10601061 let value = Self::encode_property(value)?;10621063 let property = Property { key, value };10641065 Ok(property)1066 }10671068 pub fn encode_property<E: Encode, S: Get<u32>>(1069 value: &E,1070 ) -> Result<BoundedBytes<S>, DispatchError> {1071 let value = value1072 .encode()1073 .try_into()1074 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10751076 Ok(value)1077 }10781079 pub fn decode_property<D: Decode, S: Get<u32>>(1080 vec: &BoundedBytes<S>,1081 ) -> Result<D, DispatchError> {1082 vec.decode()1083 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1084 }10851086 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1087 where1088 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1089 {1090 vec.rebind()1091 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1092 }10931094 fn init_collection(1095 sender: T::CrossAccountId,1096 data: CreateCollectionData<T::AccountId>,1097 properties: impl Iterator<Item = Property>,1098 ) -> Result<CollectionId, DispatchError> {1099 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11001101 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1102 return Err(<Error<T>>::NoAvailableCollectionId.into());1103 }11041105 <PalletCommon<T>>::set_scoped_collection_properties(1106 collection_id?,1107 PropertyScope::Rmrk,1108 properties,1109 )?;11101111 collection_id1112 }11131114 pub fn create_nft(1115 sender: &T::CrossAccountId,1116 owner: &T::CrossAccountId,1117 collection: &NonfungibleHandle<T>,1118 properties: impl Iterator<Item = Property>,1119 ) -> Result<TokenId, DispatchError> {1120 let data = CreateNftExData {1121 properties: BoundedVec::default(),1122 owner: owner.clone(),1123 };11241125 let budget = budget::Value::new(NESTING_BUDGET);11261127 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11281129 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11301131 <PalletNft<T>>::set_scoped_token_properties(1132 collection.id,1133 nft_id,1134 PropertyScope::Rmrk,1135 properties,1136 )?;11371138 Ok(nft_id)1139 }11401141 fn destroy_nft(1142 sender: T::CrossAccountId,1143 collection_id: CollectionId,1144 token_id: TokenId,1145 max_burns: u32,1146 error_if_not_owned: Error<T>,1147 ) -> DispatchResultWithPostInfo {1148 let collection =1149 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11501151 let token_data =1152 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11531154 let from = token_data.owner;11551156 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11571158 ensure!(1159 <PalletStructure<T>>::check_indirectly_owned(1160 sender.clone(),1161 collection_id,1162 token_id,1163 None,1164 &owner_check_budget1165 )?,1166 error_if_not_owned,1167 );11681169 let burns_budget = budget::Value::new(max_burns);1170 let breadth_budget = budget::Value::new(max_burns);11711172 <PalletNft<T>>::burn_recursively(1173 &collection,1174 &from,1175 token_id,1176 &burns_budget,1177 &breadth_budget,1178 )1179 }11801181 fn insert_pending_child(1182 target: (CollectionId, TokenId),1183 child: (RmrkCollectionId, RmrkNftId),1184 ) -> DispatchResult {1185 Self::mutate_pending_child(target, |pending_children| {1186 pending_children.insert(child);1187 })1188 }11891190 fn remove_pending_child(1191 target: (CollectionId, TokenId),1192 child: (RmrkCollectionId, RmrkNftId),1193 ) -> DispatchResult {1194 Self::mutate_pending_child(target, |pending_children| {1195 pending_children.remove(&child);1196 })1197 }11981199 fn mutate_pending_child(1200 (target_collection_id, target_nft_id): (CollectionId, TokenId),1201 f: impl FnOnce(&mut PendingChildrenMap),1202 ) -> DispatchResult {1203 <PalletNft<T>>::try_mutate_token_aux_property(1204 target_collection_id,1205 target_nft_id,1206 PropertyScope::Rmrk,1207 Self::rmrk_property_key(PendingChildren)?,1208 |pending_children| -> DispatchResult {1209 let mut map = match pending_children {1210 Some(map) => Self::decode_property(map)?,1211 None => PendingChildrenMap::new(),1212 };12131214 f(&mut map);12151216 *pending_children = Some(Self::encode_property(&map)?);12171218 Ok(())1219 },1220 )1221 }12221223 fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1224 let property = <PalletNft<T>>::token_aux_property((1225 collection_id,1226 nft_id,1227 PropertyScope::Rmrk,1228 Self::rmrk_property_key(PendingChildren)?1229 ));12301231 let pending_children = match property {1232 Some(map) => Self::decode_property(&map)?,1233 None => PendingChildrenMap::new(),1234 };12351236 Ok(pending_children.into_iter())1237 }12381239 fn acquire_next_resource_id(1240 collection_id: CollectionId,1241 nft_id: TokenId,1242 ) -> Result<RmrkResourceId, DispatchError> {1243 let resource_id: RmrkResourceId =1244 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12451246 let next_id = resource_id1247 .checked_add(1)1248 .ok_or(<Error<T>>::NoAvailableResourceId)?;12491250 <PalletNft<T>>::set_scoped_token_property(1251 collection_id,1252 nft_id,1253 PropertyScope::Rmrk,1254 Self::rmrk_property(NextResourceId, &next_id)?,1255 )?;12561257 Ok(resource_id)1258 }12591260 fn resource_add(1261 sender: T::AccountId,1262 collection_id: CollectionId,1263 nft_id: TokenId,1264 resource: RmrkResourceTypes,1265 ) -> Result<RmrkResourceId, DispatchError> {1266 let collection =1267 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1268 ensure!(collection.owner == sender, Error::<T>::NoPermission);12691270 let sender = T::CrossAccountId::from_sub(sender);1271 let budget = budget::Value::new(NESTING_BUDGET);12721273 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1274 .map_err(Self::map_unique_err_to_proxy)?;12751276 let pending = sender != nft_owner;12771278 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;12791280 let resource_info = RmrkResourceInfo {1281 id,1282 resource,1283 pending,1284 pending_removal: false,1285 };12861287 <PalletNft<T>>::try_mutate_token_aux_property(1288 collection_id,1289 nft_id,1290 PropertyScope::Rmrk,1291 Self::rmrk_property_key(ResourceId(id))?,1292 |value| -> DispatchResult {1293 *value = Some(Self::encode_property(&resource_info)?);12941295 Ok(())1296 },1297 )?;12981299 Ok(id)1300 }13011302 fn resource_remove(1303 sender: T::AccountId,1304 collection_id: CollectionId,1305 nft_id: TokenId,1306 resource_id: RmrkResourceId,1307 ) -> DispatchResult {1308 let collection =1309 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1310 ensure!(collection.owner == sender, Error::<T>::NoPermission);13111312 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1313 let scope = PropertyScope::Rmrk;13141315 ensure!(1316 <PalletNft<T>>::token_aux_property((1317 collection_id,1318 nft_id,1319 scope,1320 resource_id_key.clone()1321 ))1322 .is_some(),1323 <Error<T>>::ResourceDoesntExist1324 );13251326 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1327 let topmost_owner =1328 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13291330 let sender = T::CrossAccountId::from_sub(sender);1331 if topmost_owner == sender {1332 <PalletNft<T>>::remove_token_aux_property(1333 collection_id,1334 nft_id,1335 PropertyScope::Rmrk,1336 Self::rmrk_property_key(ResourceId(resource_id))?,1337 );1338 } else {1339 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1340 res.pending_removal = true;13411342 Ok(())1343 })?;1344 }13451346 Ok(())1347 }13481349 fn try_mutate_resource_info(1350 collection_id: CollectionId,1351 nft_id: TokenId,1352 resource_id: RmrkResourceId,1353 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1354 ) -> DispatchResult {1355 <PalletNft<T>>::try_mutate_token_aux_property(1356 collection_id,1357 nft_id,1358 PropertyScope::Rmrk,1359 Self::rmrk_property_key(ResourceId(resource_id))?,1360 |value| match value {1361 Some(value) => {1362 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;13631364 f(&mut resource_info)?;13651366 *value = Self::encode_property(&resource_info)?;13671368 Ok(())1369 }1370 None => Err(<Error<T>>::ResourceDoesntExist.into()),1371 },1372 )1373 }13741375 fn change_collection_owner(1376 collection_id: CollectionId,1377 collection_type: misc::CollectionType,1378 sender: T::AccountId,1379 new_owner: T::AccountId,1380 ) -> DispatchResult {1381 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1382 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;13831384 let mut collection = collection.into_inner();13851386 collection.owner = new_owner;1387 collection.save()1388 }13891390 pub fn check_collection_owner(1391 collection: &NonfungibleHandle<T>,1392 account: &T::CrossAccountId,1393 ) -> DispatchResult {1394 collection1395 .check_is_owner(account)1396 .map_err(Self::map_unique_err_to_proxy)1397 }13981399 pub fn last_collection_idx() -> RmrkCollectionId {1400 <CollectionIndex<T>>::get()1401 }14021403 pub fn unique_collection_id(1404 rmrk_collection_id: RmrkCollectionId,1405 ) -> Result<CollectionId, DispatchError> {1406 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1407 .map_err(|_| <Error<T>>::CollectionUnknown.into())1408 }14091410 pub fn rmrk_collection_id(1411 unique_collection_id: CollectionId,1412 ) -> Result<RmrkCollectionId, DispatchError> {1413 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1414 }14151416 pub fn get_nft_collection(1417 collection_id: CollectionId,1418 ) -> Result<NonfungibleHandle<T>, DispatchError> {1419 let collection = <CollectionHandle<T>>::try_get(collection_id)1420 .map_err(|_| <Error<T>>::CollectionUnknown)?;14211422 match collection.mode {1423 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1424 _ => Err(<Error<T>>::CollectionUnknown.into()),1425 }1426 }14271428 pub fn collection_exists(collection_id: CollectionId) -> bool {1429 <CollectionHandle<T>>::try_get(collection_id).is_ok()1430 }14311432 pub fn get_collection_property(1433 collection_id: CollectionId,1434 key: RmrkProperty,1435 ) -> Result<PropertyValue, DispatchError> {1436 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1437 .get(&Self::rmrk_property_key(key)?)1438 .ok_or(<Error<T>>::CollectionUnknown)?1439 .clone();14401441 Ok(collection_property)1442 }14431444 pub fn get_collection_property_decoded<V: Decode>(1445 collection_id: CollectionId,1446 key: RmrkProperty,1447 ) -> Result<V, DispatchError> {1448 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1449 }14501451 pub fn get_collection_type(1452 collection_id: CollectionId,1453 ) -> Result<misc::CollectionType, DispatchError> {1454 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1455 if err != <Error<T>>::CollectionUnknown.into() {1456 <Error<T>>::CorruptedCollectionType.into()1457 } else {1458 err1459 }1460 })1461 }14621463 pub fn ensure_collection_type(1464 collection_id: CollectionId,1465 collection_type: misc::CollectionType,1466 ) -> DispatchResult {1467 let actual_type = Self::get_collection_type(collection_id)?;1468 ensure!(1469 actual_type == collection_type,1470 <CommonError<T>>::NoPermission1471 );14721473 Ok(())1474 }14751476 pub fn get_typed_nft_collection(1477 collection_id: CollectionId,1478 collection_type: misc::CollectionType,1479 ) -> Result<NonfungibleHandle<T>, DispatchError> {1480 Self::ensure_collection_type(collection_id, collection_type)?;14811482 Self::get_nft_collection(collection_id)1483 }14841485 pub fn get_typed_nft_collection_mapped(1486 rmrk_collection_id: RmrkCollectionId,1487 collection_type: misc::CollectionType,1488 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1489 let unique_collection_id = match collection_type {1490 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1491 _ => rmrk_collection_id.into(),1492 };14931494 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14951496 Ok((collection, unique_collection_id))1497 }14981499 pub fn get_nft_property(1500 collection_id: CollectionId,1501 nft_id: TokenId,1502 key: RmrkProperty,1503 ) -> Result<PropertyValue, DispatchError> {1504 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1505 .get(&Self::rmrk_property_key(key)?)1506 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1507 .clone();15081509 Ok(nft_property)1510 }15111512 pub fn get_nft_property_decoded<V: Decode>(1513 collection_id: CollectionId,1514 nft_id: TokenId,1515 key: RmrkProperty,1516 ) -> Result<V, DispatchError> {1517 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1518 }15191520 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1521 <TokenData<T>>::contains_key((collection_id, nft_id))1522 }15231524 pub fn get_nft_type(1525 collection_id: CollectionId,1526 token_id: TokenId,1527 ) -> Result<NftType, DispatchError> {1528 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1529 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1530 }15311532 pub fn ensure_nft_type(1533 collection_id: CollectionId,1534 token_id: TokenId,1535 nft_type: NftType,1536 ) -> DispatchResult {1537 let actual_type = Self::get_nft_type(collection_id, token_id)?;1538 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);15391540 Ok(())1541 }15421543 pub fn ensure_nft_owner(1544 collection_id: CollectionId,1545 token_id: TokenId,1546 possible_owner: &T::CrossAccountId,1547 nesting_budget: &dyn budget::Budget,1548 ) -> DispatchResult {1549 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1550 possible_owner.clone(),1551 collection_id,1552 token_id,1553 None,1554 nesting_budget,1555 )1556 .map_err(Self::map_unique_err_to_proxy)?;15571558 ensure!(is_owned, <Error<T>>::NoPermission);15591560 Ok(())1561 }15621563 pub fn filter_user_properties<Key, Value, R, Mapper>(1564 collection_id: CollectionId,1565 token_id: Option<TokenId>,1566 filter_keys: Option<Vec<RmrkPropertyKey>>,1567 mapper: Mapper,1568 ) -> Result<Vec<R>, DispatchError>1569 where1570 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1571 Value: Decode + Default,1572 Mapper: Fn(Key, Value) -> R,1573 {1574 filter_keys1575 .map(|keys| {1576 let properties = keys1577 .into_iter()1578 .filter_map(|key| {1579 let key: Key = key.try_into().ok()?;15801581 let value = match token_id {1582 Some(token_id) => Self::get_nft_property_decoded(1583 collection_id,1584 token_id,1585 UserProperty(key.as_ref()),1586 ),1587 None => Self::get_collection_property_decoded(1588 collection_id,1589 UserProperty(key.as_ref()),1590 ),1591 }1592 .ok()?;15931594 Some(mapper(key, value))1595 })1596 .collect();15971598 Ok(properties)1599 })1600 .unwrap_or_else(|| {1601 let properties =1602 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16031604 Ok(properties)1605 })1606 }16071608 pub fn iterate_user_properties<Key, Value, R, Mapper>(1609 collection_id: CollectionId,1610 token_id: Option<TokenId>,1611 mapper: Mapper,1612 ) -> Result<impl Iterator<Item = R>, DispatchError>1613 where1614 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1615 Value: Decode + Default,1616 Mapper: Fn(Key, Value) -> R,1617 {1618 let properties = match token_id {1619 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1620 None => <PalletCommon<T>>::collection_properties(collection_id),1621 };16221623 let properties = properties.into_iter().filter_map(move |(key, value)| {1624 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16251626 let key: Key = key.to_vec().try_into().ok()?;1627 let value: Value = value.decode().ok()?;16281629 Some(mapper(key, value))1630 });16311632 Ok(properties)1633 }16341635 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1636 map_unique_err_to_proxy! {1637 match err {1638 CommonError::NoPermission => NoPermission,1639 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1640 CommonError::PublicMintingNotAllowed => NoPermission,1641 CommonError::TokenNotFound => NoAvailableNftId,1642 CommonError::ApprovedValueTooLow => NoPermission,1643 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1644 StructureError::TokenNotFound => NoAvailableNftId,1645 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1646 }1647 }1648 }1649}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::{20 pallet_prelude::*,21 transactional,22 BoundedVec,23 dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::{btree_set::BTreeSet, btree_map::BTreeMap}};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenSet = BTreeSet<PendingChild>;5960type BasesMap = BTreeMap<RmrkBaseId, u32>;6162#[frame_support::pallet]63pub mod pallet {64 use super::*;65 use pallet_evm::account;6667 #[pallet::config]68 pub trait Config:69 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config70 {71 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;72 type WeightInfo: WeightInfo;73 }7475 #[pallet::storage]76 #[pallet::getter(fn collection_index)]77 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7879 #[pallet::storage]80 pub type UniqueCollectionId<T: Config> =81 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8283 #[pallet::pallet]84 #[pallet::generate_store(pub(super) trait Store)]85 pub struct Pallet<T>(_);8687 #[pallet::event]88 #[pallet::generate_deposit(pub(super) fn deposit_event)]89 pub enum Event<T: Config> {90 CollectionCreated {91 issuer: T::AccountId,92 collection_id: RmrkCollectionId,93 },94 CollectionDestroyed {95 issuer: T::AccountId,96 collection_id: RmrkCollectionId,97 },98 IssuerChanged {99 old_issuer: T::AccountId,100 new_issuer: T::AccountId,101 collection_id: RmrkCollectionId,102 },103 CollectionLocked {104 issuer: T::AccountId,105 collection_id: RmrkCollectionId,106 },107 NftMinted {108 owner: T::AccountId,109 collection_id: RmrkCollectionId,110 nft_id: RmrkNftId,111 },112 NFTBurned {113 owner: T::AccountId,114 nft_id: RmrkNftId,115 },116 NFTSent {117 sender: T::AccountId,118 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,119 collection_id: RmrkCollectionId,120 nft_id: RmrkNftId,121 approval_required: bool,122 },123 NFTAccepted {124 sender: T::AccountId,125 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,126 collection_id: RmrkCollectionId,127 nft_id: RmrkNftId,128 },129 NFTRejected {130 sender: T::AccountId,131 collection_id: RmrkCollectionId,132 nft_id: RmrkNftId,133 },134 PropertySet {135 collection_id: RmrkCollectionId,136 maybe_nft_id: Option<RmrkNftId>,137 key: RmrkKeyString,138 value: RmrkValueString,139 },140 ResourceAdded {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemoval {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 ResourceAccepted {149 nft_id: RmrkNftId,150 resource_id: RmrkResourceId,151 },152 ResourceRemovalAccepted {153 nft_id: RmrkNftId,154 resource_id: RmrkResourceId,155 },156 PrioritySet {157 collection_id: RmrkCollectionId,158 nft_id: RmrkNftId,159 },160 }161162 #[pallet::error]163 pub enum Error<T> {164 /* Unique-specific events */165 CorruptedCollectionType,166 NftTypeEncodeError,167 RmrkPropertyKeyIsTooLong,168 RmrkPropertyValueIsTooLong,169 RmrkPropertyIsNotFound,170 UnableToDecodeRmrkData,171172 /* RMRK compatible events */173 CollectionNotEmpty,174 NoAvailableCollectionId,175 NoAvailableNftId,176 CollectionUnknown,177 NoPermission,178 NonTransferable,179 CollectionFullOrLocked,180 ResourceDoesntExist,181 CannotSendToDescendentOrSelf,182 CannotAcceptNonOwnedNft,183 CannotRejectNonOwnedNft,184 CannotRejectNonPendingNft,185 ResourceNotPending,186 NoAvailableResourceId,187 }188189 #[pallet::call]190 impl<T: Config> Pallet<T> {191 /// Create a collection192 #[transactional]193 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]194 pub fn create_collection(195 origin: OriginFor<T>,196 metadata: RmrkString,197 max: Option<u32>,198 symbol: RmrkCollectionSymbol,199 ) -> DispatchResult {200 let sender = ensure_signed(origin)?;201202 let limits = CollectionLimits {203 owner_can_transfer: Some(false),204 token_limit: max,205 ..Default::default()206 };207208 let data = CreateCollectionData {209 limits: Some(limits),210 token_prefix: symbol211 .into_inner()212 .try_into()213 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,214 permissions: Some(CollectionPermissions {215 nesting: Some(NestingPermissions {216 token_owner: true,217 collection_admin: false,218 restricted: None,219 #[cfg(feature = "runtime-benchmarks")]220 permissive: false,221 }),222 ..Default::default()223 }),224 ..Default::default()225 };226227 let unique_collection_id = Self::init_collection(228 T::CrossAccountId::from_sub(sender.clone()),229 data,230 [231 Self::rmrk_property(Metadata, &metadata)?,232 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,233 ]234 .into_iter(),235 )?;236 let rmrk_collection_id = <CollectionIndex<T>>::get();237238 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);239240 <PalletCommon<T>>::set_scoped_collection_property(241 unique_collection_id,242 PropertyScope::Rmrk,243 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,244 )?;245246 <CollectionIndex<T>>::mutate(|n| *n += 1);247248 Self::deposit_event(Event::CollectionCreated {249 issuer: sender,250 collection_id: rmrk_collection_id,251 });252253 Ok(())254 }255256 /// destroy collection257 #[transactional]258 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]259 pub fn destroy_collection(260 origin: OriginFor<T>,261 collection_id: RmrkCollectionId,262 ) -> DispatchResult {263 let sender = ensure_signed(origin)?;264 let cross_sender = T::CrossAccountId::from_sub(sender.clone());265266 let collection = Self::get_typed_nft_collection(267 Self::unique_collection_id(collection_id)?,268 misc::CollectionType::Regular,269 )?;270 collection.check_is_external()?;271272 <PalletNft<T>>::destroy_collection(collection, &cross_sender)273 .map_err(Self::map_unique_err_to_proxy)?;274275 Self::deposit_event(Event::CollectionDestroyed {276 issuer: sender,277 collection_id,278 });279280 Ok(())281 }282283 /// Change the issuer of a collection284 ///285 /// Parameters:286 /// - `origin`: sender of the transaction287 /// - `collection_id`: collection id of the nft to change issuer of288 /// - `new_issuer`: Collection's new issuer289 #[transactional]290 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]291 pub fn change_collection_issuer(292 origin: OriginFor<T>,293 collection_id: RmrkCollectionId,294 new_issuer: <T::Lookup as StaticLookup>::Source,295 ) -> DispatchResult {296 let sender = ensure_signed(origin)?;297298 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;299 collection.check_is_external()?;300301 let new_issuer = T::Lookup::lookup(new_issuer)?;302303 Self::change_collection_owner(304 Self::unique_collection_id(collection_id)?,305 misc::CollectionType::Regular,306 sender.clone(),307 new_issuer.clone(),308 )?;309310 Self::deposit_event(Event::IssuerChanged {311 old_issuer: sender,312 new_issuer,313 collection_id,314 });315316 Ok(())317 }318319 /// lock collection320 #[transactional]321 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]322 pub fn lock_collection(323 origin: OriginFor<T>,324 collection_id: RmrkCollectionId,325 ) -> DispatchResult {326 let sender = ensure_signed(origin)?;327 let cross_sender = T::CrossAccountId::from_sub(sender.clone());328329 let collection = Self::get_typed_nft_collection(330 Self::unique_collection_id(collection_id)?,331 misc::CollectionType::Regular,332 )?;333 collection.check_is_external()?;334335 Self::check_collection_owner(&collection, &cross_sender)?;336337 let token_count = collection.total_supply();338339 let mut collection = collection.into_inner();340 collection.limits.token_limit = Some(token_count);341 collection.save()?;342343 Self::deposit_event(Event::CollectionLocked {344 issuer: sender,345 collection_id,346 });347348 Ok(())349 }350351 /// Mints an NFT in the specified collection352 /// Sets metadata and the royalty attribute353 ///354 /// Parameters:355 /// - `collection_id`: The class of the asset to be minted.356 /// - `nft_id`: The nft value of the asset to be minted.357 /// - `recipient`: Receiver of the royalty358 /// - `royalty`: Permillage reward from each trade for the Recipient359 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash360 /// - `transferable`: Ability to transfer this NFT361 #[transactional]362 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]363 pub fn mint_nft(364 origin: OriginFor<T>,365 owner: Option<T::AccountId>,366 collection_id: RmrkCollectionId,367 recipient: Option<T::AccountId>,368 royalty_amount: Option<Permill>,369 metadata: RmrkString,370 transferable: bool,371 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,372 ) -> DispatchResult {373 let sender = ensure_signed(origin)?;374 let cross_sender = T::CrossAccountId::from_sub(sender.clone());375376 let owner = owner.unwrap_or(sender.clone());377 let cross_owner = T::CrossAccountId::from_sub(owner.clone());378379 let collection = Self::get_typed_nft_collection(380 Self::unique_collection_id(collection_id)?,381 misc::CollectionType::Regular,382 )?;383 collection.check_is_external()?;384385 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {386 recipient: recipient.unwrap_or_else(|| owner.clone()),387 amount,388 });389390 let nft_id = Self::create_nft(391 &cross_sender,392 &cross_owner,393 &collection,394 [395 Self::rmrk_property(TokenType, &NftType::Regular)?,396 Self::rmrk_property(Transferable, &transferable)?,397 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,398 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,399 Self::rmrk_property(Metadata, &metadata)?,400 Self::rmrk_property(Equipped, &false)?,401 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,402 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,403 Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,404 Self::rmrk_property(AssociatedBases, &BasesMap::new())?,405 ]406 .into_iter(),407 )408 .map_err(|err| match err {409 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),410 err => Self::map_unique_err_to_proxy(err),411 })?;412413 if let Some(resources) = resources {414 for resource in resources {415 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;416 }417 }418419 Self::deposit_event(Event::NftMinted {420 owner,421 collection_id,422 nft_id: nft_id.0,423 });424425 Ok(())426 }427428 /// burn nft429 #[transactional]430 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]431 pub fn burn_nft(432 origin: OriginFor<T>,433 collection_id: RmrkCollectionId,434 nft_id: RmrkNftId,435 max_burns: u32,436 ) -> DispatchResult {437 let sender = ensure_signed(origin)?;438 let cross_sender = T::CrossAccountId::from_sub(sender.clone());439440 let collection = Self::get_typed_nft_collection(441 Self::unique_collection_id(collection_id)?,442 misc::CollectionType::Regular,443 )?;444 collection.check_is_external()?;445446 Self::destroy_nft(447 cross_sender,448 Self::unique_collection_id(collection_id)?,449 nft_id.into(),450 max_burns,451 <Error<T>>::NoPermission,452 )453 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;454455 Self::deposit_event(Event::NFTBurned {456 owner: sender,457 nft_id,458 });459460 Ok(())461 }462463 /// Transfers a NFT from an Account or NFT A to another Account or NFT B464 ///465 /// Parameters:466 /// - `origin`: sender of the transaction467 /// - `rmrk_collection_id`: collection id of the nft to be transferred468 /// - `rmrk_nft_id`: nft id of the nft to be transferred469 /// - `new_owner`: new owner of the nft which can be either an account or a NFT470 #[transactional]471 #[pallet::weight(<SelfWeightOf<T>>::send())]472 pub fn send(473 origin: OriginFor<T>,474 rmrk_collection_id: RmrkCollectionId,475 rmrk_nft_id: RmrkNftId,476 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,477 ) -> DispatchResult {478 let sender = ensure_signed(origin.clone())?;479 let cross_sender = T::CrossAccountId::from_sub(sender.clone());480481 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;482 let nft_id = rmrk_nft_id.into();483484 let collection =485 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;486 collection.check_is_external()?;487488 let token_data =489 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;490491 let from = token_data.owner;492493 ensure!(494 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,495 <Error<T>>::NonTransferable496 );497498 ensure!(499 Self::get_nft_property_decoded::<Option<PendingTarget>>(500 collection_id,501 nft_id,502 RmrkProperty::PendingNftAccept503 )?.is_none(),504 <Error<T>>::NoPermission505 );506507 let target_owner;508 let approval_required;509510 match new_owner {511 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {512 target_owner = T::CrossAccountId::from_sub(account_id.clone());513 approval_required = false;514 }515 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(516 target_collection_id,517 target_nft_id,518 ) => {519 let target_collection_id = Self::unique_collection_id(target_collection_id)?;520521 let target_nft_budget = budget::Value::new(NESTING_BUDGET);522523 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(524 target_collection_id,525 target_nft_id.into(),526 Some((collection_id, nft_id)),527 &target_nft_budget,528 )529 .map_err(Self::map_unique_err_to_proxy)?;530531 approval_required = cross_sender != target_nft_owner;532533 if approval_required {534 target_owner = target_nft_owner;535536 <PalletNft<T>>::set_scoped_token_property(537 collection.id,538 nft_id,539 PropertyScope::Rmrk,540 Self::rmrk_property::<Option<PendingTarget>>(541 PendingNftAccept,542 &Some((target_collection_id, target_nft_id.into()))543 )?,544 )?;545546 Self::insert_pending_child(547 (target_collection_id, target_nft_id.into()),548 (rmrk_collection_id, rmrk_nft_id),549 )?;550 } else {551 target_owner = T::CrossTokenAddressMapping::token_to_address(552 target_collection_id,553 target_nft_id.into(),554 );555 }556 }557 }558559 let src_nft_budget = budget::Value::new(NESTING_BUDGET);560561 <PalletNft<T>>::transfer_from(562 &collection,563 &cross_sender,564 &from,565 &target_owner,566 nft_id,567 &src_nft_budget,568 )569 .map_err(Self::map_unique_err_to_proxy)?;570571 Self::deposit_event(Event::NFTSent {572 sender,573 recipient: new_owner,574 collection_id: rmrk_collection_id,575 nft_id: rmrk_nft_id,576 approval_required,577 });578579 Ok(())580 }581582 /// Accepts an NFT sent from another account to self or owned NFT583 ///584 /// Parameters:585 /// - `origin`: sender of the transaction586 /// - `rmrk_collection_id`: collection id of the nft to be accepted587 /// - `rmrk_nft_id`: nft id of the nft to be accepted588 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was589 /// sent to590 #[transactional]591 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]592 pub fn accept_nft(593 origin: OriginFor<T>,594 rmrk_collection_id: RmrkCollectionId,595 rmrk_nft_id: RmrkNftId,596 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,597 ) -> DispatchResult {598 let sender = ensure_signed(origin.clone())?;599 let cross_sender = T::CrossAccountId::from_sub(sender.clone());600601 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;602 let nft_id = rmrk_nft_id.into();603604 let collection =605 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;606 collection.check_is_external()?;607608 let new_cross_owner = match new_owner {609 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {610 T::CrossAccountId::from_sub(account_id.clone())611 }612 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(613 target_collection_id,614 target_nft_id,615 ) => {616 let target_collection_id = Self::unique_collection_id(target_collection_id)?;617618 T::CrossTokenAddressMapping::token_to_address(619 target_collection_id,620 TokenId(target_nft_id),621 )622 }623 };624625 let budget = budget::Value::new(NESTING_BUDGET);626627 <PalletNft<T>>::transfer(628 &collection,629 &cross_sender,630 &new_cross_owner,631 nft_id,632 &budget,633 )634 .map_err(|err| {635 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {636 <Error<T>>::CannotAcceptNonOwnedNft.into()637 } else {638 Self::map_unique_err_to_proxy(err)639 }640 })?;641642 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(643 collection_id,644 nft_id,645 RmrkProperty::PendingNftAccept646 )?;647648 if let Some(pending_target) = pending_target {649 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;650651 <PalletNft<T>>::set_scoped_token_property(652 collection.id,653 nft_id,654 PropertyScope::Rmrk,655 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,656 )?;657 }658659 Self::deposit_event(Event::NFTAccepted {660 sender,661 recipient: new_owner,662 collection_id: rmrk_collection_id,663 nft_id: rmrk_nft_id,664 });665666 Ok(())667 }668669 /// Rejects an NFT sent from another account to self or owned NFT670 ///671 /// Parameters:672 /// - `origin`: sender of the transaction673 /// - `rmrk_collection_id`: collection id of the nft to be accepted674 /// - `rmrk_nft_id`: nft id of the nft to be accepted675 #[transactional]676 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]677 pub fn reject_nft(678 origin: OriginFor<T>,679 rmrk_collection_id: RmrkCollectionId,680 rmrk_nft_id: RmrkNftId,681 ) -> DispatchResult {682 let sender = ensure_signed(origin)?;683 let cross_sender = T::CrossAccountId::from_sub(sender.clone());684685 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;686 let nft_id = rmrk_nft_id.into();687688 let collection =689 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;690 collection.check_is_external()?;691692 ensure!(693 <TokenData<T>>::get((collection_id, nft_id)).is_some(),694 <Error<T>>::NoAvailableNftId695 );696697698 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(699 collection_id,700 nft_id,701 RmrkProperty::PendingNftAccept702 )?;703704 match pending_target {705 Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,706 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),707 }708709 Self::destroy_nft(710 cross_sender,711 collection_id,712 nft_id,713 NESTING_BUDGET,714 <Error<T>>::CannotRejectNonOwnedNft,715 )716 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;717718 Self::deposit_event(Event::NFTRejected {719 sender,720 collection_id: rmrk_collection_id,721 nft_id: rmrk_nft_id,722 });723724 Ok(())725 }726727 /// accept the addition of a new resource to an existing NFT728 #[transactional]729 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730 pub fn accept_resource(731 origin: OriginFor<T>,732 rmrk_collection_id: RmrkCollectionId,733 rmrk_nft_id: RmrkNftId,734 resource_id: RmrkResourceId,735 ) -> DispatchResult {736 let sender = ensure_signed(origin)?;737 let cross_sender = T::CrossAccountId::from_sub(sender);738739 let collection_id = Self::unique_collection_id(rmrk_collection_id)740 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;741 let collection =742 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;743 collection.check_is_external()?;744745 let nft_id = rmrk_nft_id.into();746747 let budget = budget::Value::new(NESTING_BUDGET);748749 let nft_owner =750 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {754 ensure!(res.pending, <Error<T>>::ResourceNotPending);755 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);756757 res.pending = false;758759 Ok(())760 })?;761762 Self::deposit_event(Event::<T>::ResourceAccepted {763 nft_id: rmrk_nft_id,764 resource_id,765 });766767 Ok(())768 }769770 /// accept the removal of a resource of an existing NFT771 #[transactional]772 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773 pub fn accept_resource_removal(774 origin: OriginFor<T>,775 rmrk_collection_id: RmrkCollectionId,776 rmrk_nft_id: RmrkNftId,777 resource_id: RmrkResourceId,778 ) -> DispatchResult {779 let sender = ensure_signed(origin)?;780 let cross_sender = T::CrossAccountId::from_sub(sender);781782 let collection_id = Self::unique_collection_id(rmrk_collection_id)783 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;784 let collection =785 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;786 collection.check_is_external()?;787788 let nft_id = rmrk_nft_id.into();789790 let budget = budget::Value::new(NESTING_BUDGET);791792 let nft_owner =793 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)794 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;795796 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);797798 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;799800 let resource_info = <PalletNft<T>>::token_aux_property((801 collection_id,802 nft_id,803 PropertyScope::Rmrk,804 resource_id_key.clone(),805 ))806 .ok_or(<Error<T>>::ResourceDoesntExist)?;807808 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;809810 ensure!(811 resource_info.pending_removal,812 <Error<T>>::ResourceNotPending813 );814815 <PalletNft<T>>::remove_token_aux_property(816 collection_id,817 nft_id,818 PropertyScope::Rmrk,819 resource_id_key,820 );821822 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {823 let base_id = resource.base;824825 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;826 }827828 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {829 nft_id: rmrk_nft_id,830 resource_id,831 });832833 Ok(())834 }835836 /// set a custom value on an NFT837 #[transactional]838 #[pallet::weight(<SelfWeightOf<T>>::set_property())]839 pub fn set_property(840 origin: OriginFor<T>,841 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,842 maybe_nft_id: Option<RmrkNftId>,843 key: RmrkKeyString,844 value: RmrkValueString,845 ) -> DispatchResult {846 let sender = ensure_signed(origin)?;847 let sender = T::CrossAccountId::from_sub(sender);848849 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;850 let collection =851 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;852 collection.check_is_external()?;853854 let budget = budget::Value::new(NESTING_BUDGET);855856 match maybe_nft_id {857 Some(nft_id) => {858 let token_id: TokenId = nft_id.into();859860 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;861 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;862863 <PalletNft<T>>::set_scoped_token_property(864 collection_id,865 token_id,866 PropertyScope::Rmrk,867 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,868 )?;869 }870 None => {871 let collection = Self::get_typed_nft_collection(872 collection_id,873 misc::CollectionType::Regular,874 )?;875876 Self::check_collection_owner(&collection, &sender)?;877878 <PalletCommon<T>>::set_scoped_collection_property(879 collection_id,880 PropertyScope::Rmrk,881 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,882 )?;883 }884 }885886 Self::deposit_event(Event::PropertySet {887 collection_id: rmrk_collection_id,888 maybe_nft_id,889 key,890 value,891 });892893 Ok(())894 }895896 /// set a different order of resource priority897 #[transactional]898 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]899 pub fn set_priority(900 origin: OriginFor<T>,901 rmrk_collection_id: RmrkCollectionId,902 rmrk_nft_id: RmrkNftId,903 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,904 ) -> DispatchResult {905 let sender = ensure_signed(origin)?;906 let sender = T::CrossAccountId::from_sub(sender);907908 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;909 let nft_id = rmrk_nft_id.into();910911 let collection =912 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;913 collection.check_is_external()?;914915 let budget = budget::Value::new(NESTING_BUDGET);916917 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;918 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;919920 <PalletNft<T>>::set_scoped_token_property(921 collection_id,922 nft_id,923 PropertyScope::Rmrk,924 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,925 )?;926927 Self::deposit_event(Event::<T>::PrioritySet {928 collection_id: rmrk_collection_id,929 nft_id: rmrk_nft_id,930 });931932 Ok(())933 }934935 /// Create basic resource936 #[transactional]937 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938 pub fn add_basic_resource(939 origin: OriginFor<T>,940 rmrk_collection_id: RmrkCollectionId,941 nft_id: RmrkNftId,942 resource: RmrkBasicResource,943 ) -> DispatchResult {944 let sender = ensure_signed(origin.clone())?;945946 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;947 let collection =948 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;949 collection.check_is_external()?;950951 let resource_id = Self::resource_add(952 sender,953 collection_id,954 nft_id.into(),955 RmrkResourceTypes::Basic(resource),956 )?;957958 Self::deposit_event(Event::ResourceAdded {959 nft_id,960 resource_id,961 });962 Ok(())963 }964965 /// Create composable resource966 #[transactional]967 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968 pub fn add_composable_resource(969 origin: OriginFor<T>,970 rmrk_collection_id: RmrkCollectionId,971 nft_id: RmrkNftId,972 resource: RmrkComposableResource,973 ) -> DispatchResult {974 let sender = ensure_signed(origin.clone())?;975976 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;977 let collection =978 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;979 collection.check_is_external()?;980981 let base_id = resource.base;982983 let resource_id = Self::resource_add(984 sender,985 collection_id,986 nft_id.into(),987 RmrkResourceTypes::Composable(resource),988 )?;989990 <PalletNft<T>>::try_mutate_token_aux_property(991 collection_id,992 nft_id.into(),993 PropertyScope::Rmrk,994 Self::rmrk_property_key(AssociatedBases)?,995 |value| -> DispatchResult {996 let mut bases: BasesMap = match value {997 Some(value) => Self::decode_property(value)?,998 None => BasesMap::new()999 };1000 1001 *bases.entry(base_id).or_insert(0) += 1;1002 1003 *value = Some(Self::encode_property(&bases)?);1004 Ok(())1005 }1006 )?;10071008 Self::deposit_event(Event::ResourceAdded {1009 nft_id,1010 resource_id,1011 });1012 Ok(())1013 }10141015 /// Create slot resource1016 #[transactional]1017 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018 pub fn add_slot_resource(1019 origin: OriginFor<T>,1020 rmrk_collection_id: RmrkCollectionId,1021 nft_id: RmrkNftId,1022 resource: RmrkSlotResource,1023 ) -> DispatchResult {1024 let sender = ensure_signed(origin.clone())?;10251026 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1027 let collection =1028 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1029 collection.check_is_external()?;10301031 let resource_id = Self::resource_add(1032 sender,1033 collection_id,1034 nft_id.into(),1035 RmrkResourceTypes::Slot(resource),1036 )?;10371038 Self::deposit_event(Event::ResourceAdded {1039 nft_id,1040 resource_id,1041 });1042 Ok(())1043 }10441045 /// remove resource1046 #[transactional]1047 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048 pub fn remove_resource(1049 origin: OriginFor<T>,1050 rmrk_collection_id: RmrkCollectionId,1051 nft_id: RmrkNftId,1052 resource_id: RmrkResourceId,1053 ) -> DispatchResult {1054 let sender = ensure_signed(origin.clone())?;10551056 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1057 let collection =1058 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1059 collection.check_is_external()?;10601061 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10621063 Self::deposit_event(Event::ResourceRemoval {1064 nft_id,1065 resource_id,1066 });1067 Ok(())1068 }1069 }1070}10711072impl<T: Config> Pallet<T> {1073 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1074 let key = rmrk_key.to_key::<T>()?;10751076 let scoped_key = PropertyScope::Rmrk1077 .apply(key)1078 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10791080 Ok(scoped_key)1081 }10821083 // todo think about renaming these1084 pub fn rmrk_property<E: Encode>(1085 rmrk_key: RmrkProperty,1086 value: &E,1087 ) -> Result<Property, DispatchError> {1088 let key = rmrk_key.to_key::<T>()?;10891090 let value = Self::encode_property(value)?;10911092 let property = Property { key, value };10931094 Ok(property)1095 }10961097 pub fn encode_property<E: Encode, S: Get<u32>>(1098 value: &E,1099 ) -> Result<BoundedBytes<S>, DispatchError> {1100 let value = value1101 .encode()1102 .try_into()1103 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;11041105 Ok(value)1106 }11071108 pub fn decode_property<D: Decode, S: Get<u32>>(1109 vec: &BoundedBytes<S>,1110 ) -> Result<D, DispatchError> {1111 vec.decode()1112 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113 }11141115 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116 where1117 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1118 {1119 vec.rebind()1120 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121 }11221123 fn init_collection(1124 sender: T::CrossAccountId,1125 data: CreateCollectionData<T::AccountId>,1126 properties: impl Iterator<Item = Property>,1127 ) -> Result<CollectionId, DispatchError> {1128 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11291130 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1131 return Err(<Error<T>>::NoAvailableCollectionId.into());1132 }11331134 <PalletCommon<T>>::set_scoped_collection_properties(1135 collection_id?,1136 PropertyScope::Rmrk,1137 properties,1138 )?;11391140 collection_id1141 }11421143 pub fn create_nft(1144 sender: &T::CrossAccountId,1145 owner: &T::CrossAccountId,1146 collection: &NonfungibleHandle<T>,1147 properties: impl Iterator<Item = Property>,1148 ) -> Result<TokenId, DispatchError> {1149 let data = CreateNftExData {1150 properties: BoundedVec::default(),1151 owner: owner.clone(),1152 };11531154 let budget = budget::Value::new(NESTING_BUDGET);11551156 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11571158 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11591160 <PalletNft<T>>::set_scoped_token_properties(1161 collection.id,1162 nft_id,1163 PropertyScope::Rmrk,1164 properties,1165 )?;11661167 Ok(nft_id)1168 }11691170 fn destroy_nft(1171 sender: T::CrossAccountId,1172 collection_id: CollectionId,1173 token_id: TokenId,1174 max_burns: u32,1175 error_if_not_owned: Error<T>,1176 ) -> DispatchResultWithPostInfo {1177 let collection =1178 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11791180 let token_data =1181 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11821183 let from = token_data.owner;11841185 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11861187 ensure!(1188 <PalletStructure<T>>::check_indirectly_owned(1189 sender.clone(),1190 collection_id,1191 token_id,1192 None,1193 &owner_check_budget1194 )?,1195 error_if_not_owned,1196 );11971198 let burns_budget = budget::Value::new(max_burns);1199 let breadth_budget = budget::Value::new(max_burns);12001201 <PalletNft<T>>::burn_recursively(1202 &collection,1203 &from,1204 token_id,1205 &burns_budget,1206 &breadth_budget,1207 )1208 }12091210 fn insert_pending_child(1211 target: (CollectionId, TokenId),1212 child: (RmrkCollectionId, RmrkNftId),1213 ) -> DispatchResult {1214 Self::mutate_pending_child(target, |pending_children| {1215 pending_children.insert(child);1216 })1217 }12181219 fn remove_pending_child(1220 target: (CollectionId, TokenId),1221 child: (RmrkCollectionId, RmrkNftId),1222 ) -> DispatchResult {1223 Self::mutate_pending_child(target, |pending_children| {1224 pending_children.remove(&child);1225 })1226 }12271228 fn mutate_pending_child(1229 (target_collection_id, target_nft_id): (CollectionId, TokenId),1230 f: impl FnOnce(&mut PendingChildrenSet),1231 ) -> DispatchResult {1232 <PalletNft<T>>::try_mutate_token_aux_property(1233 target_collection_id,1234 target_nft_id,1235 PropertyScope::Rmrk,1236 Self::rmrk_property_key(PendingChildren)?,1237 |pending_children| -> DispatchResult {1238 let mut map = match pending_children {1239 Some(map) => Self::decode_property(map)?,1240 None => PendingChildrenSet::new(),1241 };12421243 f(&mut map);12441245 *pending_children = Some(Self::encode_property(&map)?);12461247 Ok(())1248 },1249 )1250 }12511252 fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1253 let property = <PalletNft<T>>::token_aux_property((1254 collection_id,1255 nft_id,1256 PropertyScope::Rmrk,1257 Self::rmrk_property_key(PendingChildren)?1258 ));12591260 let pending_children = match property {1261 Some(map) => Self::decode_property(&map)?,1262 None => PendingChildrenSet::new(),1263 };12641265 Ok(pending_children.into_iter())1266 }12671268 fn acquire_next_resource_id(1269 collection_id: CollectionId,1270 nft_id: TokenId,1271 ) -> Result<RmrkResourceId, DispatchError> {1272 let resource_id: RmrkResourceId =1273 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12741275 let next_id = resource_id1276 .checked_add(1)1277 .ok_or(<Error<T>>::NoAvailableResourceId)?;12781279 <PalletNft<T>>::set_scoped_token_property(1280 collection_id,1281 nft_id,1282 PropertyScope::Rmrk,1283 Self::rmrk_property(NextResourceId, &next_id)?,1284 )?;12851286 Ok(resource_id)1287 }12881289 fn resource_add(1290 sender: T::AccountId,1291 collection_id: CollectionId,1292 nft_id: TokenId,1293 resource: RmrkResourceTypes,1294 ) -> Result<RmrkResourceId, DispatchError> {1295 let collection =1296 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1297 ensure!(collection.owner == sender, Error::<T>::NoPermission);12981299 let sender = T::CrossAccountId::from_sub(sender);1300 let budget = budget::Value::new(NESTING_BUDGET);13011302 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1303 .map_err(Self::map_unique_err_to_proxy)?;13041305 let pending = sender != nft_owner;13061307 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13081309 let resource_info = RmrkResourceInfo {1310 id,1311 resource,1312 pending,1313 pending_removal: false,1314 };13151316 <PalletNft<T>>::try_mutate_token_aux_property(1317 collection_id,1318 nft_id,1319 PropertyScope::Rmrk,1320 Self::rmrk_property_key(ResourceId(id))?,1321 |value| -> DispatchResult {1322 *value = Some(Self::encode_property(&resource_info)?);13231324 Ok(())1325 },1326 )?;13271328 Ok(id)1329 }13301331 fn resource_remove(1332 sender: T::AccountId,1333 collection_id: CollectionId,1334 nft_id: TokenId,1335 resource_id: RmrkResourceId,1336 ) -> DispatchResult {1337 let collection =1338 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1339 ensure!(collection.owner == sender, Error::<T>::NoPermission);13401341 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1342 let scope = PropertyScope::Rmrk;13431344 let resource = <PalletNft<T>>::token_aux_property((1345 collection_id,1346 nft_id,1347 scope,1348 resource_id_key.clone()1349 )).ok_or(<Error<T>>::ResourceDoesntExist)?;13501351 let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13521353 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1354 let topmost_owner =1355 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13561357 let sender = T::CrossAccountId::from_sub(sender);1358 if topmost_owner == sender {1359 <PalletNft<T>>::remove_token_aux_property(1360 collection_id,1361 nft_id,1362 PropertyScope::Rmrk,1363 Self::rmrk_property_key(ResourceId(resource_id))?,1364 );13651366 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1367 let base_id = resource.base;13681369 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1370 }1371 } else {1372 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1373 res.pending_removal = true;13741375 Ok(())1376 })?;1377 }13781379 Ok(())1380 }13811382 fn remove_associated_base_id(1383 collection_id: CollectionId,1384 nft_id: TokenId,1385 base_id: RmrkBaseId,1386 ) -> DispatchResult {1387 <PalletNft<T>>::try_mutate_token_aux_property(1388 collection_id,1389 nft_id,1390 PropertyScope::Rmrk,1391 Self::rmrk_property_key(AssociatedBases)?,1392 |value| -> DispatchResult {1393 let mut bases: BasesMap = match value {1394 Some(value) => Self::decode_property(value)?,1395 None => BasesMap::new()1396 };13971398 let remaining = bases.get(&base_id);13991400 if let Some(remaining) = remaining {1401 if let Some(0) | None = remaining.checked_sub(1) {1402 bases.remove(&base_id);1403 }1404 }14051406 *value = Some(Self::encode_property(&bases)?);1407 Ok(())1408 }1409 )1410 }14111412 fn try_mutate_resource_info(1413 collection_id: CollectionId,1414 nft_id: TokenId,1415 resource_id: RmrkResourceId,1416 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1417 ) -> DispatchResult {1418 <PalletNft<T>>::try_mutate_token_aux_property(1419 collection_id,1420 nft_id,1421 PropertyScope::Rmrk,1422 Self::rmrk_property_key(ResourceId(resource_id))?,1423 |value| match value {1424 Some(value) => {1425 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14261427 f(&mut resource_info)?;14281429 *value = Self::encode_property(&resource_info)?;14301431 Ok(())1432 }1433 None => Err(<Error<T>>::ResourceDoesntExist.into()),1434 },1435 )1436 }14371438 fn change_collection_owner(1439 collection_id: CollectionId,1440 collection_type: misc::CollectionType,1441 sender: T::AccountId,1442 new_owner: T::AccountId,1443 ) -> DispatchResult {1444 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1445 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14461447 let mut collection = collection.into_inner();14481449 collection.owner = new_owner;1450 collection.save()1451 }14521453 pub fn check_collection_owner(1454 collection: &NonfungibleHandle<T>,1455 account: &T::CrossAccountId,1456 ) -> DispatchResult {1457 collection1458 .check_is_owner(account)1459 .map_err(Self::map_unique_err_to_proxy)1460 }14611462 pub fn last_collection_idx() -> RmrkCollectionId {1463 <CollectionIndex<T>>::get()1464 }14651466 pub fn unique_collection_id(1467 rmrk_collection_id: RmrkCollectionId,1468 ) -> Result<CollectionId, DispatchError> {1469 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1470 .map_err(|_| <Error<T>>::CollectionUnknown.into())1471 }14721473 pub fn rmrk_collection_id(1474 unique_collection_id: CollectionId,1475 ) -> Result<RmrkCollectionId, DispatchError> {1476 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1477 }14781479 pub fn get_nft_collection(1480 collection_id: CollectionId,1481 ) -> Result<NonfungibleHandle<T>, DispatchError> {1482 let collection = <CollectionHandle<T>>::try_get(collection_id)1483 .map_err(|_| <Error<T>>::CollectionUnknown)?;14841485 match collection.mode {1486 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1487 _ => Err(<Error<T>>::CollectionUnknown.into()),1488 }1489 }14901491 pub fn collection_exists(collection_id: CollectionId) -> bool {1492 <CollectionHandle<T>>::try_get(collection_id).is_ok()1493 }14941495 pub fn get_collection_property(1496 collection_id: CollectionId,1497 key: RmrkProperty,1498 ) -> Result<PropertyValue, DispatchError> {1499 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1500 .get(&Self::rmrk_property_key(key)?)1501 .ok_or(<Error<T>>::CollectionUnknown)?1502 .clone();15031504 Ok(collection_property)1505 }15061507 pub fn get_collection_property_decoded<V: Decode>(1508 collection_id: CollectionId,1509 key: RmrkProperty,1510 ) -> Result<V, DispatchError> {1511 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1512 }15131514 pub fn get_collection_type(1515 collection_id: CollectionId,1516 ) -> Result<misc::CollectionType, DispatchError> {1517 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1518 if err != <Error<T>>::CollectionUnknown.into() {1519 <Error<T>>::CorruptedCollectionType.into()1520 } else {1521 err1522 }1523 })1524 }15251526 pub fn ensure_collection_type(1527 collection_id: CollectionId,1528 collection_type: misc::CollectionType,1529 ) -> DispatchResult {1530 let actual_type = Self::get_collection_type(collection_id)?;1531 ensure!(1532 actual_type == collection_type,1533 <CommonError<T>>::NoPermission1534 );15351536 Ok(())1537 }15381539 pub fn get_typed_nft_collection(1540 collection_id: CollectionId,1541 collection_type: misc::CollectionType,1542 ) -> Result<NonfungibleHandle<T>, DispatchError> {1543 Self::ensure_collection_type(collection_id, collection_type)?;15441545 Self::get_nft_collection(collection_id)1546 }15471548 pub fn get_typed_nft_collection_mapped(1549 rmrk_collection_id: RmrkCollectionId,1550 collection_type: misc::CollectionType,1551 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1552 let unique_collection_id = match collection_type {1553 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1554 _ => rmrk_collection_id.into(),1555 };15561557 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15581559 Ok((collection, unique_collection_id))1560 }15611562 pub fn get_nft_property(1563 collection_id: CollectionId,1564 nft_id: TokenId,1565 key: RmrkProperty,1566 ) -> Result<PropertyValue, DispatchError> {1567 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1568 .get(&Self::rmrk_property_key(key)?)1569 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1570 .clone();15711572 Ok(nft_property)1573 }15741575 pub fn get_nft_property_decoded<V: Decode>(1576 collection_id: CollectionId,1577 nft_id: TokenId,1578 key: RmrkProperty,1579 ) -> Result<V, DispatchError> {1580 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1581 }15821583 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1584 <TokenData<T>>::contains_key((collection_id, nft_id))1585 }15861587 pub fn get_nft_type(1588 collection_id: CollectionId,1589 token_id: TokenId,1590 ) -> Result<NftType, DispatchError> {1591 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1592 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1593 }15941595 pub fn ensure_nft_type(1596 collection_id: CollectionId,1597 token_id: TokenId,1598 nft_type: NftType,1599 ) -> DispatchResult {1600 let actual_type = Self::get_nft_type(collection_id, token_id)?;1601 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16021603 Ok(())1604 }16051606 pub fn ensure_nft_owner(1607 collection_id: CollectionId,1608 token_id: TokenId,1609 possible_owner: &T::CrossAccountId,1610 nesting_budget: &dyn budget::Budget,1611 ) -> DispatchResult {1612 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1613 possible_owner.clone(),1614 collection_id,1615 token_id,1616 None,1617 nesting_budget,1618 )1619 .map_err(Self::map_unique_err_to_proxy)?;16201621 ensure!(is_owned, <Error<T>>::NoPermission);16221623 Ok(())1624 }16251626 pub fn filter_user_properties<Key, Value, R, Mapper>(1627 collection_id: CollectionId,1628 token_id: Option<TokenId>,1629 filter_keys: Option<Vec<RmrkPropertyKey>>,1630 mapper: Mapper,1631 ) -> Result<Vec<R>, DispatchError>1632 where1633 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1634 Value: Decode + Default,1635 Mapper: Fn(Key, Value) -> R,1636 {1637 filter_keys1638 .map(|keys| {1639 let properties = keys1640 .into_iter()1641 .filter_map(|key| {1642 let key: Key = key.try_into().ok()?;16431644 let value = match token_id {1645 Some(token_id) => Self::get_nft_property_decoded(1646 collection_id,1647 token_id,1648 UserProperty(key.as_ref()),1649 ),1650 None => Self::get_collection_property_decoded(1651 collection_id,1652 UserProperty(key.as_ref()),1653 ),1654 }1655 .ok()?;16561657 Some(mapper(key, value))1658 })1659 .collect();16601661 Ok(properties)1662 })1663 .unwrap_or_else(|| {1664 let properties =1665 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16661667 Ok(properties)1668 })1669 }16701671 pub fn iterate_user_properties<Key, Value, R, Mapper>(1672 collection_id: CollectionId,1673 token_id: Option<TokenId>,1674 mapper: Mapper,1675 ) -> Result<impl Iterator<Item = R>, DispatchError>1676 where1677 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1678 Value: Decode + Default,1679 Mapper: Fn(Key, Value) -> R,1680 {1681 let properties = match token_id {1682 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1683 None => <PalletCommon<T>>::collection_properties(collection_id),1684 };16851686 let properties = properties.into_iter().filter_map(move |(key, value)| {1687 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16881689 let key: Key = key.to_vec().try_into().ok()?;1690 let value: Value = value.decode().ok()?;16911692 Some(mapper(key, value))1693 });16941695 Ok(properties)1696 }16971698 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1699 map_unique_err_to_proxy! {1700 match err {1701 CommonError::NoPermission => NoPermission,1702 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1703 CommonError::PublicMintingNotAllowed => NoPermission,1704 CommonError::TokenNotFound => NoAvailableNftId,1705 CommonError::ApprovedValueTooLow => NoPermission,1706 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1707 StructureError::TokenNotFound => NoAvailableNftId,1708 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1709 }1710 }1711 }1712}pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -34,6 +34,7 @@
ResourceId(RmrkResourceId),
PendingNftAccept,
PendingChildren,
+ AssociatedBases,
Parts,
Base,
Src,
@@ -77,6 +78,7 @@
Self::ResourceId(id) => key!(RESOURCE_ID_PREFIX, id.to_le_bytes()),
Self::PendingNftAccept => key!("pending-nft-accept"),
Self::PendingChildren => key!("pending-children"),
+ Self::AssociatedBases => key!("assoc-bases"),
Self::Parts => key!("parts"),
Self::Base => key!("base"),
Self::Src => key!("src"),