difftreelog
Merge pull request #439 from UniqueNetwork/doc/rmrk
in: master
9 files changed
pallets/proxy-rmrk-core/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
use sp_std::vec;
use frame_benchmarking::{benchmarks, account};
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::{23 vec::Vec,24 collections::{btree_set::BTreeSet, btree_map::BTreeMap},25};26use up_data_structs::{*, mapping::TokenAddressMapping};27use pallet_common::{28 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,29};30use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};31use pallet_structure::{Pallet as PalletStructure, Error as StructureError};32use pallet_evm::account::CrossAccountId;33use core::convert::AsRef;3435pub use pallet::*;3637#[cfg(feature = "runtime-benchmarks")]38pub mod benchmarking;39pub mod misc;40pub mod property;41pub mod rpc;42pub mod weights;4344pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4546use weights::WeightInfo;47use misc::*;48pub use property::*;4950use RmrkProperty::*;5152pub const NESTING_BUDGET: u32 = 5;5354type PendingTarget = (CollectionId, TokenId);55type PendingChild = (RmrkCollectionId, RmrkNftId);56type PendingChildrenSet = BTreeSet<PendingChild>;5758type BasesMap = BTreeMap<RmrkBaseId, u32>;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, &PendingChildrenSet::new())?,402 Self::rmrk_property(AssociatedBases, &BasesMap::new())?,403 ]404 .into_iter(),405 )406 .map_err(|err| match err {407 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),408 err => Self::map_unique_err_to_proxy(err),409 })?;410411 if let Some(resources) = resources {412 for resource in resources {413 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;414 }415 }416417 Self::deposit_event(Event::NftMinted {418 owner,419 collection_id,420 nft_id: nft_id.0,421 });422423 Ok(())424 }425426 /// burn nft427 #[transactional]428 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]429 pub fn burn_nft(430 origin: OriginFor<T>,431 collection_id: RmrkCollectionId,432 nft_id: RmrkNftId,433 max_burns: u32,434 ) -> DispatchResult {435 let sender = ensure_signed(origin)?;436 let cross_sender = T::CrossAccountId::from_sub(sender.clone());437438 let collection = Self::get_typed_nft_collection(439 Self::unique_collection_id(collection_id)?,440 misc::CollectionType::Regular,441 )?;442 collection.check_is_external()?;443444 Self::destroy_nft(445 cross_sender,446 Self::unique_collection_id(collection_id)?,447 nft_id.into(),448 max_burns,449 <Error<T>>::NoPermission,450 )451 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;452453 Self::deposit_event(Event::NFTBurned {454 owner: sender,455 nft_id,456 });457458 Ok(())459 }460461 /// Transfers a NFT from an Account or NFT A to another Account or NFT B462 ///463 /// Parameters:464 /// - `origin`: sender of the transaction465 /// - `rmrk_collection_id`: collection id of the nft to be transferred466 /// - `rmrk_nft_id`: nft id of the nft to be transferred467 /// - `new_owner`: new owner of the nft which can be either an account or a NFT468 #[transactional]469 #[pallet::weight(<SelfWeightOf<T>>::send())]470 pub fn send(471 origin: OriginFor<T>,472 rmrk_collection_id: RmrkCollectionId,473 rmrk_nft_id: RmrkNftId,474 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,475 ) -> DispatchResult {476 let sender = ensure_signed(origin.clone())?;477 let cross_sender = T::CrossAccountId::from_sub(sender.clone());478479 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;480 let nft_id = rmrk_nft_id.into();481482 let collection =483 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;484 collection.check_is_external()?;485486 let token_data =487 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;488489 let from = token_data.owner;490491 ensure!(492 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,493 <Error<T>>::NonTransferable494 );495496 ensure!(497 Self::get_nft_property_decoded::<Option<PendingTarget>>(498 collection_id,499 nft_id,500 RmrkProperty::PendingNftAccept501 )?502 .is_none(),503 <Error<T>>::NoPermission504 );505506 let target_owner;507 let approval_required;508509 match new_owner {510 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {511 target_owner = T::CrossAccountId::from_sub(account_id.clone());512 approval_required = false;513 }514 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(515 target_collection_id,516 target_nft_id,517 ) => {518 let target_collection_id = Self::unique_collection_id(target_collection_id)?;519520 let target_nft_budget = budget::Value::new(NESTING_BUDGET);521522 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(523 target_collection_id,524 target_nft_id.into(),525 Some((collection_id, nft_id)),526 &target_nft_budget,527 )528 .map_err(Self::map_unique_err_to_proxy)?;529530 approval_required = cross_sender != target_nft_owner;531532 if approval_required {533 target_owner = target_nft_owner;534535 <PalletNft<T>>::set_scoped_token_property(536 collection.id,537 nft_id,538 PropertyScope::Rmrk,539 Self::rmrk_property::<Option<PendingTarget>>(540 PendingNftAccept,541 &Some((target_collection_id, target_nft_id.into())),542 )?,543 )?;544545 Self::insert_pending_child(546 (target_collection_id, target_nft_id.into()),547 (rmrk_collection_id, rmrk_nft_id),548 )?;549 } else {550 target_owner = T::CrossTokenAddressMapping::token_to_address(551 target_collection_id,552 target_nft_id.into(),553 );554 }555 }556 }557558 let src_nft_budget = budget::Value::new(NESTING_BUDGET);559560 <PalletNft<T>>::transfer_from(561 &collection,562 &cross_sender,563 &from,564 &target_owner,565 nft_id,566 &src_nft_budget,567 )568 .map_err(Self::map_unique_err_to_proxy)?;569570 Self::deposit_event(Event::NFTSent {571 sender,572 recipient: new_owner,573 collection_id: rmrk_collection_id,574 nft_id: rmrk_nft_id,575 approval_required,576 });577578 Ok(())579 }580581 /// Accepts an NFT sent from another account to self or owned NFT582 ///583 /// Parameters:584 /// - `origin`: sender of the transaction585 /// - `rmrk_collection_id`: collection id of the nft to be accepted586 /// - `rmrk_nft_id`: nft id of the nft to be accepted587 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was588 /// sent to589 #[transactional]590 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]591 pub fn accept_nft(592 origin: OriginFor<T>,593 rmrk_collection_id: RmrkCollectionId,594 rmrk_nft_id: RmrkNftId,595 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,596 ) -> DispatchResult {597 let sender = ensure_signed(origin.clone())?;598 let cross_sender = T::CrossAccountId::from_sub(sender.clone());599600 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;601 let nft_id = rmrk_nft_id.into();602603 let collection =604 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;605 collection.check_is_external()?;606607 let new_cross_owner = match new_owner {608 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {609 T::CrossAccountId::from_sub(account_id.clone())610 }611 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(612 target_collection_id,613 target_nft_id,614 ) => {615 let target_collection_id = Self::unique_collection_id(target_collection_id)?;616617 T::CrossTokenAddressMapping::token_to_address(618 target_collection_id,619 TokenId(target_nft_id),620 )621 }622 };623624 let budget = budget::Value::new(NESTING_BUDGET);625626 <PalletNft<T>>::transfer(627 &collection,628 &cross_sender,629 &new_cross_owner,630 nft_id,631 &budget,632 )633 .map_err(|err| {634 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {635 <Error<T>>::CannotAcceptNonOwnedNft.into()636 } else {637 Self::map_unique_err_to_proxy(err)638 }639 })?;640641 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(642 collection_id,643 nft_id,644 RmrkProperty::PendingNftAccept,645 )?;646647 if let Some(pending_target) = pending_target {648 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;649650 <PalletNft<T>>::set_scoped_token_property(651 collection.id,652 nft_id,653 PropertyScope::Rmrk,654 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,655 )?;656 }657658 Self::deposit_event(Event::NFTAccepted {659 sender,660 recipient: new_owner,661 collection_id: rmrk_collection_id,662 nft_id: rmrk_nft_id,663 });664665 Ok(())666 }667668 /// Rejects an NFT sent from another account to self or owned NFT669 ///670 /// Parameters:671 /// - `origin`: sender of the transaction672 /// - `rmrk_collection_id`: collection id of the nft to be accepted673 /// - `rmrk_nft_id`: nft id of the nft to be accepted674 #[transactional]675 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]676 pub fn reject_nft(677 origin: OriginFor<T>,678 rmrk_collection_id: RmrkCollectionId,679 rmrk_nft_id: RmrkNftId,680 ) -> DispatchResult {681 let sender = ensure_signed(origin)?;682 let cross_sender = T::CrossAccountId::from_sub(sender.clone());683684 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;685 let nft_id = rmrk_nft_id.into();686687 let collection =688 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;689 collection.check_is_external()?;690691 ensure!(692 <TokenData<T>>::get((collection_id, nft_id)).is_some(),693 <Error<T>>::NoAvailableNftId694 );695696 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(697 collection_id,698 nft_id,699 RmrkProperty::PendingNftAccept,700 )?;701702 match pending_target {703 Some(pending_target) => {704 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?705 }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 };10001001 *bases.entry(base_id).or_insert(0) += 1;10021003 *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(1253 collection_id: CollectionId,1254 nft_id: TokenId,1255 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1256 let property = <PalletNft<T>>::token_aux_property((1257 collection_id,1258 nft_id,1259 PropertyScope::Rmrk,1260 Self::rmrk_property_key(PendingChildren)?,1261 ));12621263 let pending_children = match property {1264 Some(map) => Self::decode_property(&map)?,1265 None => PendingChildrenSet::new(),1266 };12671268 Ok(pending_children.into_iter())1269 }12701271 fn acquire_next_resource_id(1272 collection_id: CollectionId,1273 nft_id: TokenId,1274 ) -> Result<RmrkResourceId, DispatchError> {1275 let resource_id: RmrkResourceId =1276 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12771278 let next_id = resource_id1279 .checked_add(1)1280 .ok_or(<Error<T>>::NoAvailableResourceId)?;12811282 <PalletNft<T>>::set_scoped_token_property(1283 collection_id,1284 nft_id,1285 PropertyScope::Rmrk,1286 Self::rmrk_property(NextResourceId, &next_id)?,1287 )?;12881289 Ok(resource_id)1290 }12911292 fn resource_add(1293 sender: T::AccountId,1294 collection_id: CollectionId,1295 nft_id: TokenId,1296 resource: RmrkResourceTypes,1297 ) -> Result<RmrkResourceId, DispatchError> {1298 let collection =1299 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1300 ensure!(collection.owner == sender, Error::<T>::NoPermission);13011302 let sender = T::CrossAccountId::from_sub(sender);1303 let budget = budget::Value::new(NESTING_BUDGET);13041305 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1306 .map_err(Self::map_unique_err_to_proxy)?;13071308 let pending = sender != nft_owner;13091310 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13111312 let resource_info = RmrkResourceInfo {1313 id,1314 resource,1315 pending,1316 pending_removal: false,1317 };13181319 <PalletNft<T>>::try_mutate_token_aux_property(1320 collection_id,1321 nft_id,1322 PropertyScope::Rmrk,1323 Self::rmrk_property_key(ResourceId(id))?,1324 |value| -> DispatchResult {1325 *value = Some(Self::encode_property(&resource_info)?);13261327 Ok(())1328 },1329 )?;13301331 Ok(id)1332 }13331334 fn resource_remove(1335 sender: T::AccountId,1336 collection_id: CollectionId,1337 nft_id: TokenId,1338 resource_id: RmrkResourceId,1339 ) -> DispatchResult {1340 let collection =1341 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1342 ensure!(collection.owner == sender, Error::<T>::NoPermission);13431344 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1345 let scope = PropertyScope::Rmrk;13461347 let resource = <PalletNft<T>>::token_aux_property((1348 collection_id,1349 nft_id,1350 scope,1351 resource_id_key.clone(),1352 ))1353 .ok_or(<Error<T>>::ResourceDoesntExist)?;13541355 let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13561357 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1358 let topmost_owner =1359 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13601361 let sender = T::CrossAccountId::from_sub(sender);1362 if topmost_owner == sender {1363 <PalletNft<T>>::remove_token_aux_property(1364 collection_id,1365 nft_id,1366 PropertyScope::Rmrk,1367 Self::rmrk_property_key(ResourceId(resource_id))?,1368 );13691370 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1371 let base_id = resource.base;13721373 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1374 }1375 } else {1376 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1377 res.pending_removal = true;13781379 Ok(())1380 })?;1381 }13821383 Ok(())1384 }13851386 fn remove_associated_base_id(1387 collection_id: CollectionId,1388 nft_id: TokenId,1389 base_id: RmrkBaseId,1390 ) -> DispatchResult {1391 <PalletNft<T>>::try_mutate_token_aux_property(1392 collection_id,1393 nft_id,1394 PropertyScope::Rmrk,1395 Self::rmrk_property_key(AssociatedBases)?,1396 |value| -> DispatchResult {1397 let mut bases: BasesMap = match value {1398 Some(value) => Self::decode_property(value)?,1399 None => BasesMap::new(),1400 };14011402 let remaining = bases.get(&base_id);14031404 if let Some(remaining) = remaining {1405 if let Some(0) | None = remaining.checked_sub(1) {1406 bases.remove(&base_id);1407 }1408 }14091410 *value = Some(Self::encode_property(&bases)?);1411 Ok(())1412 },1413 )1414 }14151416 fn try_mutate_resource_info(1417 collection_id: CollectionId,1418 nft_id: TokenId,1419 resource_id: RmrkResourceId,1420 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1421 ) -> DispatchResult {1422 <PalletNft<T>>::try_mutate_token_aux_property(1423 collection_id,1424 nft_id,1425 PropertyScope::Rmrk,1426 Self::rmrk_property_key(ResourceId(resource_id))?,1427 |value| match value {1428 Some(value) => {1429 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14301431 f(&mut resource_info)?;14321433 *value = Self::encode_property(&resource_info)?;14341435 Ok(())1436 }1437 None => Err(<Error<T>>::ResourceDoesntExist.into()),1438 },1439 )1440 }14411442 fn change_collection_owner(1443 collection_id: CollectionId,1444 collection_type: misc::CollectionType,1445 sender: T::AccountId,1446 new_owner: T::AccountId,1447 ) -> DispatchResult {1448 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1449 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14501451 let mut collection = collection.into_inner();14521453 collection.owner = new_owner;1454 collection.save()1455 }14561457 pub fn check_collection_owner(1458 collection: &NonfungibleHandle<T>,1459 account: &T::CrossAccountId,1460 ) -> DispatchResult {1461 collection1462 .check_is_owner(account)1463 .map_err(Self::map_unique_err_to_proxy)1464 }14651466 pub fn last_collection_idx() -> RmrkCollectionId {1467 <CollectionIndex<T>>::get()1468 }14691470 pub fn unique_collection_id(1471 rmrk_collection_id: RmrkCollectionId,1472 ) -> Result<CollectionId, DispatchError> {1473 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1474 .map_err(|_| <Error<T>>::CollectionUnknown.into())1475 }14761477 pub fn rmrk_collection_id(1478 unique_collection_id: CollectionId,1479 ) -> Result<RmrkCollectionId, DispatchError> {1480 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1481 }14821483 pub fn get_nft_collection(1484 collection_id: CollectionId,1485 ) -> Result<NonfungibleHandle<T>, DispatchError> {1486 let collection = <CollectionHandle<T>>::try_get(collection_id)1487 .map_err(|_| <Error<T>>::CollectionUnknown)?;14881489 match collection.mode {1490 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1491 _ => Err(<Error<T>>::CollectionUnknown.into()),1492 }1493 }14941495 pub fn collection_exists(collection_id: CollectionId) -> bool {1496 <CollectionHandle<T>>::try_get(collection_id).is_ok()1497 }14981499 pub fn get_collection_property(1500 collection_id: CollectionId,1501 key: RmrkProperty,1502 ) -> Result<PropertyValue, DispatchError> {1503 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1504 .get(&Self::rmrk_property_key(key)?)1505 .ok_or(<Error<T>>::CollectionUnknown)?1506 .clone();15071508 Ok(collection_property)1509 }15101511 pub fn get_collection_property_decoded<V: Decode>(1512 collection_id: CollectionId,1513 key: RmrkProperty,1514 ) -> Result<V, DispatchError> {1515 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1516 }15171518 pub fn get_collection_type(1519 collection_id: CollectionId,1520 ) -> Result<misc::CollectionType, DispatchError> {1521 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1522 if err != <Error<T>>::CollectionUnknown.into() {1523 <Error<T>>::CorruptedCollectionType.into()1524 } else {1525 err1526 }1527 })1528 }15291530 pub fn ensure_collection_type(1531 collection_id: CollectionId,1532 collection_type: misc::CollectionType,1533 ) -> DispatchResult {1534 let actual_type = Self::get_collection_type(collection_id)?;1535 ensure!(1536 actual_type == collection_type,1537 <CommonError<T>>::NoPermission1538 );15391540 Ok(())1541 }15421543 pub fn get_typed_nft_collection(1544 collection_id: CollectionId,1545 collection_type: misc::CollectionType,1546 ) -> Result<NonfungibleHandle<T>, DispatchError> {1547 Self::ensure_collection_type(collection_id, collection_type)?;15481549 Self::get_nft_collection(collection_id)1550 }15511552 pub fn get_typed_nft_collection_mapped(1553 rmrk_collection_id: RmrkCollectionId,1554 collection_type: misc::CollectionType,1555 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1556 let unique_collection_id = match collection_type {1557 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1558 _ => rmrk_collection_id.into(),1559 };15601561 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15621563 Ok((collection, unique_collection_id))1564 }15651566 pub fn get_nft_property(1567 collection_id: CollectionId,1568 nft_id: TokenId,1569 key: RmrkProperty,1570 ) -> Result<PropertyValue, DispatchError> {1571 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1572 .get(&Self::rmrk_property_key(key)?)1573 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1574 .clone();15751576 Ok(nft_property)1577 }15781579 pub fn get_nft_property_decoded<V: Decode>(1580 collection_id: CollectionId,1581 nft_id: TokenId,1582 key: RmrkProperty,1583 ) -> Result<V, DispatchError> {1584 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1585 }15861587 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1588 <TokenData<T>>::contains_key((collection_id, nft_id))1589 }15901591 pub fn get_nft_type(1592 collection_id: CollectionId,1593 token_id: TokenId,1594 ) -> Result<NftType, DispatchError> {1595 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1596 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1597 }15981599 pub fn ensure_nft_type(1600 collection_id: CollectionId,1601 token_id: TokenId,1602 nft_type: NftType,1603 ) -> DispatchResult {1604 let actual_type = Self::get_nft_type(collection_id, token_id)?;1605 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16061607 Ok(())1608 }16091610 pub fn ensure_nft_owner(1611 collection_id: CollectionId,1612 token_id: TokenId,1613 possible_owner: &T::CrossAccountId,1614 nesting_budget: &dyn budget::Budget,1615 ) -> DispatchResult {1616 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1617 possible_owner.clone(),1618 collection_id,1619 token_id,1620 None,1621 nesting_budget,1622 )1623 .map_err(Self::map_unique_err_to_proxy)?;16241625 ensure!(is_owned, <Error<T>>::NoPermission);16261627 Ok(())1628 }16291630 pub fn filter_user_properties<Key, Value, R, Mapper>(1631 collection_id: CollectionId,1632 token_id: Option<TokenId>,1633 filter_keys: Option<Vec<RmrkPropertyKey>>,1634 mapper: Mapper,1635 ) -> Result<Vec<R>, DispatchError>1636 where1637 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1638 Value: Decode + Default,1639 Mapper: Fn(Key, Value) -> R,1640 {1641 filter_keys1642 .map(|keys| {1643 let properties = keys1644 .into_iter()1645 .filter_map(|key| {1646 let key: Key = key.try_into().ok()?;16471648 let value = match token_id {1649 Some(token_id) => Self::get_nft_property_decoded(1650 collection_id,1651 token_id,1652 UserProperty(key.as_ref()),1653 ),1654 None => Self::get_collection_property_decoded(1655 collection_id,1656 UserProperty(key.as_ref()),1657 ),1658 }1659 .ok()?;16601661 Some(mapper(key, value))1662 })1663 .collect();16641665 Ok(properties)1666 })1667 .unwrap_or_else(|| {1668 let properties =1669 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16701671 Ok(properties)1672 })1673 }16741675 pub fn iterate_user_properties<Key, Value, R, Mapper>(1676 collection_id: CollectionId,1677 token_id: Option<TokenId>,1678 mapper: Mapper,1679 ) -> Result<impl Iterator<Item = R>, DispatchError>1680 where1681 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1682 Value: Decode + Default,1683 Mapper: Fn(Key, Value) -> R,1684 {1685 let properties = match token_id {1686 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1687 None => <PalletCommon<T>>::collection_properties(collection_id),1688 };16891690 let properties = properties.into_iter().filter_map(move |(key, value)| {1691 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16921693 let key: Key = key.to_vec().try_into().ok()?;1694 let value: Value = value.decode().ok()?;16951696 Some(mapper(key, value))1697 });16981699 Ok(properties)1700 }17011702 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1703 map_unique_err_to_proxy! {1704 match err {1705 CommonError::NoPermission => NoPermission,1706 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1707 CommonError::PublicMintingNotAllowed => NoPermission,1708 CommonError::TokenNotFound => NoAvailableNftId,1709 CommonError::ApprovedValueTooLow => NoPermission,1710 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1711 StructureError::TokenNotFound => NoAvailableNftId,1712 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1713 }1714 }1715 }1716}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//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK specification repository: <https://github.com/rmrk-team/rmrk-spec>52//!53//! ## Terminology54//!55//! For more information on RMRK, see RMRK's own documentation.56//!57//! ### Intro to RMRK58//!59//! - **Resource:** Additional piece of metadata of an NFT usually serving to add60//! a piece of media on top of the root metadata (NFT's own), be it a different wing61//! on the root template bird or something entirely unrelated.62//!63//! - **Base:** A list of possible "components" - Parts, a combination of which can64//! be appended/equipped to/on an NFT.65//!66//! - **Part:** Something that, together with other Parts, can constitute an NFT.67//! Parts are defined in the Base to which they belong. Parts can be either68//! of the `slot` type or `fixed` type. Slots are intended for equippables.69//! Note that "part of something" and "Part of a Base" can be easily confused,70//! and so in this documentation these words are distinguished by the capital letter.71//!72//! - **Theme:** Named objects of variable => value pairs which get interpolated into73//! the Base's `themable` Parts. Themes can hold any value, but are often represented74//! in RMRK's examples as colors applied to visible Parts.75//!76//! ### Peculiarities in Unique77//!78//! - **Scoped properties:** Properties that are normally obscured from users.79//! Their purpose is to contain structured metadata that was not included in the Unique standard80//! for collections and tokens, meant to be operated on by proxies and other outliers.81//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is82//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined83//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.84//!85//! - **Auxiliary properties:** A slightly different structure of properties,86//! trading universality of use for more convenient storage, writes and access.87//! Meant to be inaccessible to end users.88//!89//! ## Proxy Implementation90//!91//! An external user is supposed to be able to utilize this proxy as they would92//! utilize RMRK, and get exactly the same results. Normally, Unique transactions93//! are off-limits to RMRK collections and tokens, and vice versa. However,94//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.95//!96//! ### ID Mapping97//!98//! RMRK's collections' IDs are counted independently of Unique's and start at 0.99//! Note that tokens' IDs still start at 1.100//! The collections themselves, as well as tokens, are stored as Unique collections,101//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).102//!103//! ### External/Internal Collection Insulation104//!105//! A Unique transaction cannot target collections purposed for RMRK,106//! and they are flagged as `external` to specify that. On the other hand,107//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.108//!109//! ### Native Properties110//!111//! Many of RMRK's native parameters are stored as scoped properties of a collection112//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`113//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,114//! makes them impossible to tamper with.115//!116//! ### Collection and NFT Types, or Base, Parts and Themes Handling117//!118//! RMRK introduces the concept of a Base, which is a catalogue of Parts,119//! possible components of an NFT. Due to its similarity with the functionality120//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes121//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].122//!123//! ## Interface124//!125//! ### Dispatchables126//!127//! - `create_collection` - Create a new collection of NFTs.128//! - `destroy_collection` - Destroy a collection.129//! - `change_collection_issuer` - Change the issuer of a collection.130//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).131//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**132//! - `mint_nft` - Mint an NFT in a specified collection.133//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.134//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.135//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.136//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.137//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.138//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.139//! - `set_property` - Add or edit a custom user property of a token or a collection.140//! - `set_priority` - Set a different order of resource priorities for an NFT.141//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.142//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.143//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.144//! - `remove_resource` - Remove and erase a resource from an NFT.145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};149use frame_system::{pallet_prelude::*, ensure_signed};150use sp_runtime::{DispatchError, Permill, traits::StaticLookup};151use sp_std::{152 vec::Vec,153 collections::{btree_set::BTreeSet, btree_map::BTreeMap},154};155use up_data_structs::{*, mapping::TokenAddressMapping};156use pallet_common::{157 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,158};159use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};160use pallet_structure::{Pallet as PalletStructure, Error as StructureError};161use pallet_evm::account::CrossAccountId;162use core::convert::AsRef;163164pub use pallet::*;165166#[cfg(feature = "runtime-benchmarks")]167pub mod benchmarking;168pub mod misc;169pub mod property;170pub mod rpc;171pub mod weights;172173pub type SelfWeightOf<T> = <T as Config>::WeightInfo;174175use weights::WeightInfo;176use misc::*;177pub use property::*;178179use RmrkProperty::*;180181/// Maximum number of levels of depth in the token nesting tree.182pub const NESTING_BUDGET: u32 = 5;183184type PendingTarget = (CollectionId, TokenId);185type PendingChild = (RmrkCollectionId, RmrkNftId);186type PendingChildrenSet = BTreeSet<PendingChild>;187188type BasesMap = BTreeMap<RmrkBaseId, u32>;189190#[frame_support::pallet]191pub mod pallet {192 use super::*;193 use pallet_evm::account;194195 #[pallet::config]196 pub trait Config:197 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config198 {199 /// Overarching event type.200 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;201202 /// The weight information of this pallet.203 type WeightInfo: WeightInfo;204 }205206 /// Latest yet-unused collection ID.207 #[pallet::storage]208 #[pallet::getter(fn collection_index)]209 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;210211 /// Mapping from RMRK collection ID to Unique's.212 #[pallet::storage]213 pub type UniqueCollectionId<T: Config> =214 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;215216 #[pallet::pallet]217 #[pallet::generate_store(pub(super) trait Store)]218 pub struct Pallet<T>(_);219220 #[pallet::event]221 #[pallet::generate_deposit(pub(super) fn deposit_event)]222 pub enum Event<T: Config> {223 CollectionCreated {224 issuer: T::AccountId,225 collection_id: RmrkCollectionId,226 },227 CollectionDestroyed {228 issuer: T::AccountId,229 collection_id: RmrkCollectionId,230 },231 IssuerChanged {232 old_issuer: T::AccountId,233 new_issuer: T::AccountId,234 collection_id: RmrkCollectionId,235 },236 CollectionLocked {237 issuer: T::AccountId,238 collection_id: RmrkCollectionId,239 },240 NftMinted {241 owner: T::AccountId,242 collection_id: RmrkCollectionId,243 nft_id: RmrkNftId,244 },245 NFTBurned {246 owner: T::AccountId,247 nft_id: RmrkNftId,248 },249 NFTSent {250 sender: T::AccountId,251 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,252 collection_id: RmrkCollectionId,253 nft_id: RmrkNftId,254 approval_required: bool,255 },256 NFTAccepted {257 sender: T::AccountId,258 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,259 collection_id: RmrkCollectionId,260 nft_id: RmrkNftId,261 },262 NFTRejected {263 sender: T::AccountId,264 collection_id: RmrkCollectionId,265 nft_id: RmrkNftId,266 },267 PropertySet {268 collection_id: RmrkCollectionId,269 maybe_nft_id: Option<RmrkNftId>,270 key: RmrkKeyString,271 value: RmrkValueString,272 },273 ResourceAdded {274 nft_id: RmrkNftId,275 resource_id: RmrkResourceId,276 },277 ResourceRemoval {278 nft_id: RmrkNftId,279 resource_id: RmrkResourceId,280 },281 ResourceAccepted {282 nft_id: RmrkNftId,283 resource_id: RmrkResourceId,284 },285 ResourceRemovalAccepted {286 nft_id: RmrkNftId,287 resource_id: RmrkResourceId,288 },289 PrioritySet {290 collection_id: RmrkCollectionId,291 nft_id: RmrkNftId,292 },293 }294295 #[pallet::error]296 pub enum Error<T> {297 /* Unique proxy-specific events */298 /// Property of the type of RMRK collection could not be read successfully.299 CorruptedCollectionType,300 // NftTypeEncodeError,301 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).302 RmrkPropertyKeyIsTooLong,303 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).304 RmrkPropertyValueIsTooLong,305 /// Could not find a property by the supplied key.306 RmrkPropertyIsNotFound,307 /// Something went wrong when decoding encoded data from the storage.308 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.309 UnableToDecodeRmrkData,310311 /* RMRK compatible events */312 /// Only destroying collections without tokens is allowed.313 CollectionNotEmpty,314 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.315 NoAvailableCollectionId,316 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.317 NoAvailableNftId,318 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.319 CollectionUnknown,320 /// No permission to perform action.321 NoPermission,322 /// Token is marked as non-transferable, and thus cannot be transferred.323 NonTransferable,324 /// Too many tokens created in the collection, no new ones are allowed.325 CollectionFullOrLocked,326 /// No such resource found.327 ResourceDoesntExist,328 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.329 /// Sending to self is redundant.330 CannotSendToDescendentOrSelf,331 /// Not the target owner of the sent NFT.332 CannotAcceptNonOwnedNft,333 /// Not the target owner of the sent NFT.334 CannotRejectNonOwnedNft,335 /// NFT was not sent and is not pending.336 CannotRejectNonPendingNft,337 /// Resource is not pending for the operation.338 ResourceNotPending,339 /// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.340 NoAvailableResourceId,341 }342343 #[pallet::call]344 impl<T: Config> Pallet<T> {345 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?346347 /// Create a new collection of NFTs.348 ///349 /// # Permissions:350 /// * Anyone - will be assigned as the issuer of the collection.351 ///352 /// # Arguments:353 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.354 /// - `max`: Optional maximum number of tokens.355 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.356 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.357 #[transactional]358 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]359 pub fn create_collection(360 origin: OriginFor<T>,361 metadata: RmrkString,362 max: Option<u32>,363 symbol: RmrkCollectionSymbol,364 ) -> DispatchResult {365 let sender = ensure_signed(origin)?;366367 let limits = CollectionLimits {368 owner_can_transfer: Some(false),369 token_limit: max,370 ..Default::default()371 };372373 let data = CreateCollectionData {374 limits: Some(limits),375 token_prefix: symbol376 .into_inner()377 .try_into()378 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,379 permissions: Some(CollectionPermissions {380 nesting: Some(NestingPermissions {381 token_owner: true,382 collection_admin: false,383 restricted: None,384 #[cfg(feature = "runtime-benchmarks")]385 permissive: false,386 }),387 ..Default::default()388 }),389 ..Default::default()390 };391392 let unique_collection_id = Self::init_collection(393 T::CrossAccountId::from_sub(sender.clone()),394 data,395 [396 Self::encode_rmrk_property(Metadata, &metadata)?,397 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,398 ]399 .into_iter(),400 )?;401 let rmrk_collection_id = <CollectionIndex<T>>::get();402403 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);404405 <PalletCommon<T>>::set_scoped_collection_property(406 unique_collection_id,407 RMRK_SCOPE,408 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,409 )?;410411 <CollectionIndex<T>>::mutate(|n| *n += 1);412413 Self::deposit_event(Event::CollectionCreated {414 issuer: sender,415 collection_id: rmrk_collection_id,416 });417418 Ok(())419 }420421 /// Destroy a collection.422 ///423 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.424 ///425 /// # Permissions:426 /// * Collection issuer427 ///428 /// # Arguments:429 /// - `collection_id`: RMRK ID of the collection to destroy.430 #[transactional]431 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]432 pub fn destroy_collection(433 origin: OriginFor<T>,434 collection_id: RmrkCollectionId,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437 let cross_sender = T::CrossAccountId::from_sub(sender.clone());438439 let collection = Self::get_typed_nft_collection(440 Self::unique_collection_id(collection_id)?,441 misc::CollectionType::Regular,442 )?;443 collection.check_is_external()?;444445 <PalletNft<T>>::destroy_collection(collection, &cross_sender)446 .map_err(Self::map_unique_err_to_proxy)?;447448 Self::deposit_event(Event::CollectionDestroyed {449 issuer: sender,450 collection_id,451 });452453 Ok(())454 }455456 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).457 ///458 /// # Permissions:459 /// * Collection issuer460 ///461 /// # Arguments:462 /// - `collection_id`: RMRK collection ID to change the issuer of.463 /// - `new_issuer`: Collection's new issuer.464 #[transactional]465 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]466 pub fn change_collection_issuer(467 origin: OriginFor<T>,468 collection_id: RmrkCollectionId,469 new_issuer: <T::Lookup as StaticLookup>::Source,470 ) -> DispatchResult {471 let sender = ensure_signed(origin)?;472473 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;474 collection.check_is_external()?;475476 let new_issuer = T::Lookup::lookup(new_issuer)?;477478 Self::change_collection_owner(479 Self::unique_collection_id(collection_id)?,480 misc::CollectionType::Regular,481 sender.clone(),482 new_issuer.clone(),483 )?;484485 Self::deposit_event(Event::IssuerChanged {486 old_issuer: sender,487 new_issuer,488 collection_id,489 });490491 Ok(())492 }493494 /// "Lock" the collection and prevent new token creation. Cannot be undone.495 ///496 /// # Permissions:497 /// * Collection issuer498 ///499 /// # Arguments:500 /// - `collection_id`: RMRK ID of the collection to lock.501 #[transactional]502 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]503 pub fn lock_collection(504 origin: OriginFor<T>,505 collection_id: RmrkCollectionId,506 ) -> DispatchResult {507 let sender = ensure_signed(origin)?;508 let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510 let collection = Self::get_typed_nft_collection(511 Self::unique_collection_id(collection_id)?,512 misc::CollectionType::Regular,513 )?;514 collection.check_is_external()?;515516 Self::check_collection_owner(&collection, &cross_sender)?;517518 let token_count = collection.total_supply();519520 let mut collection = collection.into_inner();521 collection.limits.token_limit = Some(token_count);522 collection.save()?;523524 Self::deposit_event(Event::CollectionLocked {525 issuer: sender,526 collection_id,527 });528529 Ok(())530 }531532 /// Mint an NFT in a specified collection.533 ///534 /// # Permissions:535 /// * Collection issuer536 ///537 /// # Arguments:538 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).539 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.540 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.541 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.542 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.543 /// - `transferable`: Can this NFT be transferred? Cannot be changed.544 /// - `resources`: Resource data to be added to the NFT immediately after minting.545 #[transactional]546 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]547 pub fn mint_nft(548 origin: OriginFor<T>,549 owner: Option<T::AccountId>,550 collection_id: RmrkCollectionId,551 recipient: Option<T::AccountId>,552 royalty_amount: Option<Permill>,553 metadata: RmrkString,554 transferable: bool,555 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,556 ) -> DispatchResult {557 let sender = ensure_signed(origin)?;558 let cross_sender = T::CrossAccountId::from_sub(sender.clone());559560 let owner = owner.unwrap_or(sender.clone());561 let cross_owner = T::CrossAccountId::from_sub(owner.clone());562563 let collection = Self::get_typed_nft_collection(564 Self::unique_collection_id(collection_id)?,565 misc::CollectionType::Regular,566 )?;567 collection.check_is_external()?;568569 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {570 recipient: recipient.unwrap_or_else(|| owner.clone()),571 amount,572 });573574 let nft_id = Self::create_nft(575 &cross_sender,576 &cross_owner,577 &collection,578 [579 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,580 Self::encode_rmrk_property(Transferable, &transferable)?,581 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,582 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,583 Self::encode_rmrk_property(Metadata, &metadata)?,584 Self::encode_rmrk_property(Equipped, &false)?,585 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,586 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,587 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,588 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,589 ]590 .into_iter(),591 )592 .map_err(|err| match err {593 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),594 err => Self::map_unique_err_to_proxy(err),595 })?;596597 if let Some(resources) = resources {598 for resource in resources {599 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;600 }601 }602603 Self::deposit_event(Event::NftMinted {604 owner,605 collection_id,606 nft_id: nft_id.0,607 });608609 Ok(())610 }611612 /// Burn an NFT, destroying it and its nested tokens up to the specified limit.613 /// If the burning budget is exceeded, the transaction is reverted.614 ///615 /// This is the way to burn a nested token as well.616 ///617 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).618 ///619 /// # Permissions:620 /// * Token owner621 ///622 /// # Arguments:623 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.624 /// - `nft_id`: ID of the NFT to be destroyed.625 /// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction626 /// is reverted if there are more tokens to burn in the nesting tree than this number.627 /// This is primarily a mechanism of transaction weight control.628 #[transactional]629 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]630 pub fn burn_nft(631 origin: OriginFor<T>,632 collection_id: RmrkCollectionId,633 nft_id: RmrkNftId,634 max_burns: u32,635 ) -> DispatchResult {636 let sender = ensure_signed(origin)?;637 let cross_sender = T::CrossAccountId::from_sub(sender.clone());638639 let collection = Self::get_typed_nft_collection(640 Self::unique_collection_id(collection_id)?,641 misc::CollectionType::Regular,642 )?;643 collection.check_is_external()?;644645 Self::destroy_nft(646 cross_sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 max_burns,650 <Error<T>>::NoPermission,651 )652 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;653654 Self::deposit_event(Event::NFTBurned {655 owner: sender,656 nft_id,657 });658659 Ok(())660 }661662 /// Transfer an NFT from an account/NFT A to another account/NFT B.663 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].664 ///665 /// If the target owner is an NFT owned by another account, then the NFT will enter666 /// the pending state and will have to be accepted by the other account.667 ///668 /// # Permissions:669 /// - Token owner670 ///671 /// # Arguments:672 /// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.673 /// - `nft_id`: ID of the NFT to be transferred.674 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.675 #[transactional]676 #[pallet::weight(<SelfWeightOf<T>>::send())]677 pub fn send(678 origin: OriginFor<T>,679 rmrk_collection_id: RmrkCollectionId,680 rmrk_nft_id: RmrkNftId,681 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,682 ) -> DispatchResult {683 let sender = ensure_signed(origin.clone())?;684 let cross_sender = T::CrossAccountId::from_sub(sender.clone());685686 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;687 let nft_id = rmrk_nft_id.into();688689 let collection =690 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691 collection.check_is_external()?;692693 let token_data =694 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;695696 let from = token_data.owner;697698 ensure!(699 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,700 <Error<T>>::NonTransferable701 );702703 ensure!(704 Self::get_nft_property_decoded::<Option<PendingTarget>>(705 collection_id,706 nft_id,707 RmrkProperty::PendingNftAccept708 )?709 .is_none(),710 <Error<T>>::NoPermission711 );712713 let target_owner;714 let approval_required;715716 match new_owner {717 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {718 target_owner = T::CrossAccountId::from_sub(account_id.clone());719 approval_required = false;720 }721 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(722 target_collection_id,723 target_nft_id,724 ) => {725 let target_collection_id = Self::unique_collection_id(target_collection_id)?;726727 let target_nft_budget = budget::Value::new(NESTING_BUDGET);728729 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(730 target_collection_id,731 target_nft_id.into(),732 Some((collection_id, nft_id)),733 &target_nft_budget,734 )735 .map_err(Self::map_unique_err_to_proxy)?;736737 approval_required = cross_sender != target_nft_owner;738739 if approval_required {740 target_owner = target_nft_owner;741742 <PalletNft<T>>::set_scoped_token_property(743 collection.id,744 nft_id,745 RMRK_SCOPE,746 Self::encode_rmrk_property::<Option<PendingTarget>>(747 PendingNftAccept,748 &Some((target_collection_id, target_nft_id.into())),749 )?,750 )?;751752 Self::insert_pending_child(753 (target_collection_id, target_nft_id.into()),754 (rmrk_collection_id, rmrk_nft_id),755 )?;756 } else {757 target_owner = T::CrossTokenAddressMapping::token_to_address(758 target_collection_id,759 target_nft_id.into(),760 );761 }762 }763 }764765 let src_nft_budget = budget::Value::new(NESTING_BUDGET);766767 <PalletNft<T>>::transfer_from(768 &collection,769 &cross_sender,770 &from,771 &target_owner,772 nft_id,773 &src_nft_budget,774 )775 .map_err(Self::map_unique_err_to_proxy)?;776777 Self::deposit_event(Event::NFTSent {778 sender,779 recipient: new_owner,780 collection_id: rmrk_collection_id,781 nft_id: rmrk_nft_id,782 approval_required,783 });784785 Ok(())786 }787788 /// Accept an NFT sent from another account to self or an owned NFT.789 ///790 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.791 ///792 /// # Permissions:793 /// - Token-owner-to-be794 ///795 /// # Arguments:796 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.797 /// - `rmrk_nft_id`: ID of the NFT to be accepted.798 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,799 /// whichever the accepted NFT was sent to.800 #[transactional]801 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]802 pub fn accept_nft(803 origin: OriginFor<T>,804 rmrk_collection_id: RmrkCollectionId,805 rmrk_nft_id: RmrkNftId,806 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,807 ) -> DispatchResult {808 let sender = ensure_signed(origin.clone())?;809 let cross_sender = T::CrossAccountId::from_sub(sender.clone());810811 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812 let nft_id = rmrk_nft_id.into();813814 let collection =815 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816 collection.check_is_external()?;817818 let new_cross_owner = match new_owner {819 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {820 T::CrossAccountId::from_sub(account_id.clone())821 }822 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(823 target_collection_id,824 target_nft_id,825 ) => {826 let target_collection_id = Self::unique_collection_id(target_collection_id)?;827828 T::CrossTokenAddressMapping::token_to_address(829 target_collection_id,830 TokenId(target_nft_id),831 )832 }833 };834835 let budget = budget::Value::new(NESTING_BUDGET);836837 <PalletNft<T>>::transfer(838 &collection,839 &cross_sender,840 &new_cross_owner,841 nft_id,842 &budget,843 )844 .map_err(|err| {845 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {846 <Error<T>>::CannotAcceptNonOwnedNft.into()847 } else {848 Self::map_unique_err_to_proxy(err)849 }850 })?;851852 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(853 collection_id,854 nft_id,855 RmrkProperty::PendingNftAccept,856 )?;857858 if let Some(pending_target) = pending_target {859 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;860861 <PalletNft<T>>::set_scoped_token_property(862 collection.id,863 nft_id,864 RMRK_SCOPE,865 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,866 )?;867 }868869 Self::deposit_event(Event::NFTAccepted {870 sender,871 recipient: new_owner,872 collection_id: rmrk_collection_id,873 nft_id: rmrk_nft_id,874 });875876 Ok(())877 }878879 /// Reject an NFT sent from another account to self or owned NFT.880 /// The NFT in question will not be sent back and burnt instead.881 ///882 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.883 ///884 /// # Permissions:885 /// - Token-owner-to-be-not886 ///887 /// # Arguments:888 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.889 /// - `rmrk_nft_id`: ID of the NFT to be rejected.890 #[transactional]891 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]892 pub fn reject_nft(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 rmrk_nft_id: RmrkNftId,896 ) -> DispatchResult {897 let sender = ensure_signed(origin)?;898 let cross_sender = T::CrossAccountId::from_sub(sender.clone());899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let nft_id = rmrk_nft_id.into();902903 let collection =904 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905 collection.check_is_external()?;906907 ensure!(908 <TokenData<T>>::get((collection_id, nft_id)).is_some(),909 <Error<T>>::NoAvailableNftId910 );911912 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(913 collection_id,914 nft_id,915 RmrkProperty::PendingNftAccept,916 )?;917918 match pending_target {919 Some(pending_target) => {920 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?921 }922 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),923 }924925 Self::destroy_nft(926 cross_sender,927 collection_id,928 nft_id,929 NESTING_BUDGET,930 <Error<T>>::CannotRejectNonOwnedNft,931 )932 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;933934 Self::deposit_event(Event::NFTRejected {935 sender,936 collection_id: rmrk_collection_id,937 nft_id: rmrk_nft_id,938 });939940 Ok(())941 }942943 /// Accept the addition of a newly created pending resource to an existing NFT.944 ///945 /// This transaction is needed when a resource is created and assigned to an NFT946 /// by a non-owner, i.e. the collection issuer, with one of the947 /// [`add_...` transactions](Pallet::add_basic_resource).948 ///949 /// # Permissions:950 /// - Token owner951 ///952 /// # Arguments:953 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.954 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.955 /// - `resource_id`: ID of the newly created pending resource.956 #[transactional]957 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]958 pub fn accept_resource(959 origin: OriginFor<T>,960 rmrk_collection_id: RmrkCollectionId,961 rmrk_nft_id: RmrkNftId,962 resource_id: RmrkResourceId,963 ) -> DispatchResult {964 let sender = ensure_signed(origin)?;965 let cross_sender = T::CrossAccountId::from_sub(sender);966967 let collection_id = Self::unique_collection_id(rmrk_collection_id)968 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;969 let collection =970 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;971 collection.check_is_external()?;972973 let nft_id = rmrk_nft_id.into();974975 let budget = budget::Value::new(NESTING_BUDGET);976977 let nft_owner =978 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)979 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;980981 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {982 ensure!(res.pending, <Error<T>>::ResourceNotPending);983 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);984985 res.pending = false;986987 Ok(())988 })?;989990 Self::deposit_event(Event::<T>::ResourceAccepted {991 nft_id: rmrk_nft_id,992 resource_id,993 });994995 Ok(())996 }997998 /// Accept the removal of a removal-pending resource from an NFT.999 ///1000 /// This transaction is needed when a non-owner, i.e. the collection issuer,1001 /// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1002 ///1003 /// # Permissions:1004 /// - Token owner1005 ///1006 /// # Arguments:1007 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1008 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1009 /// - `resource_id`: ID of the removal-pending resource.1010 #[transactional]1011 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1012 pub fn accept_resource_removal(1013 origin: OriginFor<T>,1014 rmrk_collection_id: RmrkCollectionId,1015 rmrk_nft_id: RmrkNftId,1016 resource_id: RmrkResourceId,1017 ) -> DispatchResult {1018 let sender = ensure_signed(origin)?;1019 let cross_sender = T::CrossAccountId::from_sub(sender);10201021 let collection_id = Self::unique_collection_id(rmrk_collection_id)1022 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1023 let collection =1024 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1025 collection.check_is_external()?;10261027 let nft_id = rmrk_nft_id.into();10281029 let budget = budget::Value::new(NESTING_BUDGET);10301031 let nft_owner =1032 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1033 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10341035 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10361037 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10381039 let resource_info = <PalletNft<T>>::token_aux_property((1040 collection_id,1041 nft_id,1042 RMRK_SCOPE,1043 resource_id_key.clone(),1044 ))1045 .ok_or(<Error<T>>::ResourceDoesntExist)?;10461047 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10481049 ensure!(1050 resource_info.pending_removal,1051 <Error<T>>::ResourceNotPending1052 );10531054 <PalletNft<T>>::remove_token_aux_property(1055 collection_id,1056 nft_id,1057 RMRK_SCOPE,1058 resource_id_key,1059 );10601061 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1062 let base_id = resource.base;10631064 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1065 }10661067 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1068 nft_id: rmrk_nft_id,1069 resource_id,1070 });10711072 Ok(())1073 }10741075 /// Add or edit a custom user property, a key-value pair, describing the metadata1076 /// of a token or a collection, on either one of these.1077 ///1078 /// Note that in this proxy implementation many details regarding RMRK are stored1079 /// as scoped properties prefixed with "rmrk:", normally inaccessible1080 /// to external transactions and RPCs.1081 ///1082 /// # Permissions:1083 /// - Collection issuer - in case of collection property1084 /// - Token owner - in case of NFT property1085 ///1086 /// # Arguments:1087 /// - `rmrk_collection_id`: RMRK collection ID.1088 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1089 /// - `key`: Key of the custom property to be referenced by.1090 /// - `value`: Value of the custom property to be stored.1091 #[transactional]1092 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1093 pub fn set_property(1094 origin: OriginFor<T>,1095 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1096 maybe_nft_id: Option<RmrkNftId>,1097 key: RmrkKeyString,1098 value: RmrkValueString,1099 ) -> DispatchResult {1100 let sender = ensure_signed(origin)?;1101 let sender = T::CrossAccountId::from_sub(sender);11021103 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1104 let collection =1105 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1106 collection.check_is_external()?;11071108 let budget = budget::Value::new(NESTING_BUDGET);11091110 match maybe_nft_id {1111 Some(nft_id) => {1112 let token_id: TokenId = nft_id.into();11131114 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1115 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11161117 <PalletNft<T>>::set_scoped_token_property(1118 collection_id,1119 token_id,1120 RMRK_SCOPE,1121 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1122 )?;1123 }1124 None => {1125 let collection = Self::get_typed_nft_collection(1126 collection_id,1127 misc::CollectionType::Regular,1128 )?;11291130 Self::check_collection_owner(&collection, &sender)?;11311132 <PalletCommon<T>>::set_scoped_collection_property(1133 collection_id,1134 RMRK_SCOPE,1135 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1136 )?;1137 }1138 }11391140 Self::deposit_event(Event::PropertySet {1141 collection_id: rmrk_collection_id,1142 maybe_nft_id,1143 key,1144 value,1145 });11461147 Ok(())1148 }11491150 /// Set a different order of resource priorities for an NFT. Priorities can be used,1151 /// for example, for order of rendering.1152 ///1153 /// Note that the priorities are not updated automatically, and are an empty vector1154 /// by default. There is no pre-set definition for the order to be particular,1155 /// it can be interpreted arbitrarily use-case by use-case.1156 ///1157 /// # Permissions:1158 /// - Token owner1159 ///1160 /// # Arguments:1161 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1162 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1163 /// - `priorities`: Ordered vector of resource IDs.1164 #[transactional]1165 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1166 pub fn set_priority(1167 origin: OriginFor<T>,1168 rmrk_collection_id: RmrkCollectionId,1169 rmrk_nft_id: RmrkNftId,1170 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1171 ) -> DispatchResult {1172 let sender = ensure_signed(origin)?;1173 let sender = T::CrossAccountId::from_sub(sender);11741175 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1176 let nft_id = rmrk_nft_id.into();11771178 let collection =1179 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1180 collection.check_is_external()?;11811182 let budget = budget::Value::new(NESTING_BUDGET);11831184 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1185 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11861187 <PalletNft<T>>::set_scoped_token_property(1188 collection_id,1189 nft_id,1190 RMRK_SCOPE,1191 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1192 )?;11931194 Self::deposit_event(Event::<T>::PrioritySet {1195 collection_id: rmrk_collection_id,1196 nft_id: rmrk_nft_id,1197 });11981199 Ok(())1200 }12011202 /// Create and set/propose a basic resource for an NFT.1203 ///1204 /// A basic resource is the simplest, lacking a Base and anything that comes with it.1205 /// See RMRK docs for more information and examples.1206 ///1207 /// # Permissions:1208 /// - Collection issuer - if not the token owner, adding the resource will warrant1209 /// the owner's [acceptance](Pallet::accept_resource).1210 ///1211 /// # Arguments:1212 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1213 /// - `nft_id`: ID of the NFT to assign a resource to.1214 /// - `resource`: Data of the resource to be created.1215 #[transactional]1216 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1217 pub fn add_basic_resource(1218 origin: OriginFor<T>,1219 rmrk_collection_id: RmrkCollectionId,1220 nft_id: RmrkNftId,1221 resource: RmrkBasicResource,1222 ) -> DispatchResult {1223 let sender = ensure_signed(origin.clone())?;12241225 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1226 let collection =1227 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1228 collection.check_is_external()?;12291230 let resource_id = Self::resource_add(1231 sender,1232 collection_id,1233 nft_id.into(),1234 RmrkResourceTypes::Basic(resource),1235 )?;12361237 Self::deposit_event(Event::ResourceAdded {1238 nft_id,1239 resource_id,1240 });1241 Ok(())1242 }12431244 /// Create and set/propose a composable resource for an NFT.1245 ///1246 /// A composable resource links to a Base and has a subset of its Parts it is composed of.1247 /// See RMRK docs for more information and examples.1248 ///1249 /// # Permissions:1250 /// - Collection issuer - if not the token owner, adding the resource will warrant1251 /// the owner's [acceptance](Pallet::accept_resource).1252 ///1253 /// # Arguments:1254 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1255 /// - `nft_id`: ID of the NFT to assign a resource to.1256 /// - `resource`: Data of the resource to be created.1257 #[transactional]1258 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1259 pub fn add_composable_resource(1260 origin: OriginFor<T>,1261 rmrk_collection_id: RmrkCollectionId,1262 nft_id: RmrkNftId,1263 resource: RmrkComposableResource,1264 ) -> DispatchResult {1265 let sender = ensure_signed(origin.clone())?;12661267 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1268 let collection =1269 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1270 collection.check_is_external()?;12711272 let base_id = resource.base;12731274 let resource_id = Self::resource_add(1275 sender,1276 collection_id,1277 nft_id.into(),1278 RmrkResourceTypes::Composable(resource),1279 )?;12801281 <PalletNft<T>>::try_mutate_token_aux_property(1282 collection_id,1283 nft_id.into(),1284 RMRK_SCOPE,1285 Self::get_scoped_property_key(AssociatedBases)?,1286 |value| -> DispatchResult {1287 let mut bases: BasesMap = match value {1288 Some(value) => Self::decode_property_value(value)?,1289 None => BasesMap::new(),1290 };12911292 *bases.entry(base_id).or_insert(0) += 1;12931294 *value = Some(Self::encode_property_value(&bases)?);1295 Ok(())1296 },1297 )?;12981299 Self::deposit_event(Event::ResourceAdded {1300 nft_id,1301 resource_id,1302 });1303 Ok(())1304 }13051306 /// Create and set/propose a slot resource for an NFT.1307 ///1308 /// A slot resource links to a Base and a slot ID in it which it can fit into.1309 /// See RMRK docs for more information and examples.1310 ///1311 /// # Permissions:1312 /// - Collection issuer - if not the token owner, adding the resource will warrant1313 /// the owner's [acceptance](Pallet::accept_resource).1314 ///1315 /// # Arguments:1316 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1317 /// - `nft_id`: ID of the NFT to assign a resource to.1318 /// - `resource`: Data of the resource to be created.1319 #[transactional]1320 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1321 pub fn add_slot_resource(1322 origin: OriginFor<T>,1323 rmrk_collection_id: RmrkCollectionId,1324 nft_id: RmrkNftId,1325 resource: RmrkSlotResource,1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin.clone())?;13281329 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1330 let collection =1331 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1332 collection.check_is_external()?;13331334 let resource_id = Self::resource_add(1335 sender,1336 collection_id,1337 nft_id.into(),1338 RmrkResourceTypes::Slot(resource),1339 )?;13401341 Self::deposit_event(Event::ResourceAdded {1342 nft_id,1343 resource_id,1344 });1345 Ok(())1346 }13471348 /// Remove and erase a resource from an NFT.1349 ///1350 /// If the sender does not own the NFT, then it will be pending confirmation,1351 /// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1352 ///1353 /// # Permissions1354 /// - Collection issuer1355 ///1356 /// # Arguments1357 /// - `collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1358 /// - `nft_id`: ID of the NFT with a resource to be removed.1359 /// - `resource_id`: ID of the resource to be removed.1360 #[transactional]1361 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1362 pub fn remove_resource(1363 origin: OriginFor<T>,1364 rmrk_collection_id: RmrkCollectionId,1365 nft_id: RmrkNftId,1366 resource_id: RmrkResourceId,1367 ) -> DispatchResult {1368 let sender = ensure_signed(origin.clone())?;13691370 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1371 let collection =1372 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1373 collection.check_is_external()?;13741375 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13761377 Self::deposit_event(Event::ResourceRemoval {1378 nft_id,1379 resource_id,1380 });1381 Ok(())1382 }1383 }1384}13851386impl<T: Config> Pallet<T> {1387 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1388 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1389 let key = rmrk_key.to_key::<T>()?;13901391 let scoped_key = RMRK_SCOPE1392 .apply(key)1393 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13941395 Ok(scoped_key)1396 }13971398 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1399 /// and encoding the value from an arbitrary type into bytes.1400 pub fn encode_rmrk_property<E: Encode>(1401 rmrk_key: RmrkProperty,1402 value: &E,1403 ) -> Result<Property, DispatchError> {1404 let key = rmrk_key.to_key::<T>()?;14051406 let value = Self::encode_property_value(value)?;14071408 let property = Property { key, value };14091410 Ok(property)1411 }14121413 /// Encode property value from an arbitrary type into bytes for storage.1414 pub fn encode_property_value<E: Encode, S: Get<u32>>(1415 value: &E,1416 ) -> Result<BoundedBytes<S>, DispatchError> {1417 let value = value1418 .encode()1419 .try_into()1420 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14211422 Ok(value)1423 }14241425 /// Decode property value from bytes into an arbitrary type.1426 pub fn decode_property_value<D: Decode, S: Get<u32>>(1427 vec: &BoundedBytes<S>,1428 ) -> Result<D, DispatchError> {1429 vec.decode()1430 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1431 }14321433 /// Change the limit of a property value byte vector.1434 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1435 where1436 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1437 {1438 vec.rebind()1439 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1440 }14411442 /// Initialize a new NFT collection with certain RMRK-scoped properties.1443 ///1444 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1445 fn init_collection(1446 sender: T::CrossAccountId,1447 data: CreateCollectionData<T::AccountId>,1448 properties: impl Iterator<Item = Property>,1449 ) -> Result<CollectionId, DispatchError> {1450 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14511452 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1453 return Err(<Error<T>>::NoAvailableCollectionId.into());1454 }14551456 <PalletCommon<T>>::set_scoped_collection_properties(1457 collection_id?,1458 RMRK_SCOPE,1459 properties,1460 )?;14611462 collection_id1463 }14641465 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1466 ///1467 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1468 pub fn create_nft(1469 sender: &T::CrossAccountId,1470 owner: &T::CrossAccountId,1471 collection: &NonfungibleHandle<T>,1472 properties: impl Iterator<Item = Property>,1473 ) -> Result<TokenId, DispatchError> {1474 let data = CreateNftExData {1475 properties: BoundedVec::default(),1476 owner: owner.clone(),1477 };14781479 let budget = budget::Value::new(NESTING_BUDGET);14801481 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14821483 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14841485 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14861487 Ok(nft_id)1488 }14891490 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1491 ///1492 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1493 fn destroy_nft(1494 sender: T::CrossAccountId,1495 collection_id: CollectionId,1496 token_id: TokenId,1497 max_burns: u32,1498 error_if_not_owned: Error<T>,1499 ) -> DispatchResultWithPostInfo {1500 let collection =1501 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15021503 let token_data =1504 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15051506 let from = token_data.owner;15071508 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15091510 ensure!(1511 <PalletStructure<T>>::check_indirectly_owned(1512 sender.clone(),1513 collection_id,1514 token_id,1515 None,1516 &owner_check_budget1517 )?,1518 error_if_not_owned,1519 );15201521 let burns_budget = budget::Value::new(max_burns);1522 let breadth_budget = budget::Value::new(max_burns);15231524 <PalletNft<T>>::burn_recursively(1525 &collection,1526 &from,1527 token_id,1528 &burns_budget,1529 &breadth_budget,1530 )1531 }15321533 /// Add a sent token pending acceptance to the target owning token as a property.1534 fn insert_pending_child(1535 target: (CollectionId, TokenId),1536 child: (RmrkCollectionId, RmrkNftId),1537 ) -> DispatchResult {1538 Self::mutate_pending_children(target, |pending_children| {1539 pending_children.insert(child);1540 })1541 }15421543 /// Remove a sent token pending acceptance from the target token's properties.1544 fn remove_pending_child(1545 target: (CollectionId, TokenId),1546 child: (RmrkCollectionId, RmrkNftId),1547 ) -> DispatchResult {1548 Self::mutate_pending_children(target, |pending_children| {1549 pending_children.remove(&child);1550 })1551 }15521553 /// Apply a mutation to the property of a token containing sent tokens1554 /// that are currently pending acceptance.1555 fn mutate_pending_children(1556 (target_collection_id, target_nft_id): (CollectionId, TokenId),1557 f: impl FnOnce(&mut PendingChildrenSet),1558 ) -> DispatchResult {1559 <PalletNft<T>>::try_mutate_token_aux_property(1560 target_collection_id,1561 target_nft_id,1562 RMRK_SCOPE,1563 Self::get_scoped_property_key(PendingChildren)?,1564 |pending_children| -> DispatchResult {1565 let mut map = match pending_children {1566 Some(map) => Self::decode_property_value(map)?,1567 None => PendingChildrenSet::new(),1568 };15691570 f(&mut map);15711572 *pending_children = Some(Self::encode_property_value(&map)?);15731574 Ok(())1575 },1576 )1577 }15781579 /// Get an iterator from a token's property containing tokens sent to it1580 /// that are currently pending acceptance.1581 fn iterate_pending_children(1582 collection_id: CollectionId,1583 nft_id: TokenId,1584 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1585 let property = <PalletNft<T>>::token_aux_property((1586 collection_id,1587 nft_id,1588 RMRK_SCOPE,1589 Self::get_scoped_property_key(PendingChildren)?,1590 ));15911592 let pending_children = match property {1593 Some(map) => Self::decode_property_value(&map)?,1594 None => PendingChildrenSet::new(),1595 };15961597 Ok(pending_children.into_iter())1598 }15991600 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1601 /// Thus, the returned resource ID should be used.1602 ///1603 /// Resource IDs are unique only across an NFT.1604 fn acquire_next_resource_id(1605 collection_id: CollectionId,1606 nft_id: TokenId,1607 ) -> Result<RmrkResourceId, DispatchError> {1608 let resource_id: RmrkResourceId =1609 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16101611 let next_id = resource_id1612 .checked_add(1)1613 .ok_or(<Error<T>>::NoAvailableResourceId)?;16141615 <PalletNft<T>>::set_scoped_token_property(1616 collection_id,1617 nft_id,1618 RMRK_SCOPE,1619 Self::encode_rmrk_property(NextResourceId, &next_id)?,1620 )?;16211622 Ok(resource_id)1623 }16241625 /// Create and add a resource for a regular NFT, mark it as pending if the sender1626 /// is not the token owner. The sender must be the collection owner.1627 fn resource_add(1628 sender: T::AccountId,1629 collection_id: CollectionId,1630 nft_id: TokenId,1631 resource: RmrkResourceTypes,1632 ) -> Result<RmrkResourceId, DispatchError> {1633 let collection =1634 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1635 ensure!(collection.owner == sender, Error::<T>::NoPermission);16361637 let sender = T::CrossAccountId::from_sub(sender);1638 let budget = budget::Value::new(NESTING_BUDGET);16391640 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1641 .map_err(Self::map_unique_err_to_proxy)?;16421643 let pending = sender != nft_owner;16441645 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16461647 let resource_info = RmrkResourceInfo {1648 id,1649 resource,1650 pending,1651 pending_removal: false,1652 };16531654 <PalletNft<T>>::try_mutate_token_aux_property(1655 collection_id,1656 nft_id,1657 RMRK_SCOPE,1658 Self::get_scoped_property_key(ResourceId(id))?,1659 |value| -> DispatchResult {1660 *value = Some(Self::encode_property_value(&resource_info)?);16611662 Ok(())1663 },1664 )?;16651666 Ok(id)1667 }16681669 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1670 /// The sender must be the collection owner.1671 fn resource_remove(1672 sender: T::AccountId,1673 collection_id: CollectionId,1674 nft_id: TokenId,1675 resource_id: RmrkResourceId,1676 ) -> DispatchResult {1677 let collection =1678 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1679 ensure!(collection.owner == sender, Error::<T>::NoPermission);16801681 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16821683 let resource = <PalletNft<T>>::token_aux_property((1684 collection_id,1685 nft_id,1686 RMRK_SCOPE,1687 resource_id_key.clone(),1688 ))1689 .ok_or(<Error<T>>::ResourceDoesntExist)?;16901691 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16921693 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1694 let topmost_owner =1695 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16961697 let sender = T::CrossAccountId::from_sub(sender);1698 if topmost_owner == sender {1699 <PalletNft<T>>::remove_token_aux_property(1700 collection_id,1701 nft_id,1702 RMRK_SCOPE,1703 Self::get_scoped_property_key(ResourceId(resource_id))?,1704 );17051706 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1707 let base_id = resource.base;17081709 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1710 }1711 } else {1712 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1713 res.pending_removal = true;17141715 Ok(())1716 })?;1717 }17181719 Ok(())1720 }17211722 /// Remove a Base ID from an NFT if they are associated.1723 /// The Base itself is deleted if the number of associated NFTs reaches 0.1724 fn remove_associated_base_id(1725 collection_id: CollectionId,1726 nft_id: TokenId,1727 base_id: RmrkBaseId,1728 ) -> DispatchResult {1729 <PalletNft<T>>::try_mutate_token_aux_property(1730 collection_id,1731 nft_id,1732 RMRK_SCOPE,1733 Self::get_scoped_property_key(AssociatedBases)?,1734 |value| -> DispatchResult {1735 let mut bases: BasesMap = match value {1736 Some(value) => Self::decode_property_value(value)?,1737 None => BasesMap::new(),1738 };17391740 let remaining = bases.get(&base_id);17411742 if let Some(remaining) = remaining {1743 if let Some(0) | None = remaining.checked_sub(1) {1744 bases.remove(&base_id);1745 }1746 }17471748 *value = Some(Self::encode_property_value(&bases)?);1749 Ok(())1750 },1751 )1752 }17531754 /// Apply a mutation to a resource stored in the token properties of an NFT.1755 fn try_mutate_resource_info(1756 collection_id: CollectionId,1757 nft_id: TokenId,1758 resource_id: RmrkResourceId,1759 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1760 ) -> DispatchResult {1761 <PalletNft<T>>::try_mutate_token_aux_property(1762 collection_id,1763 nft_id,1764 RMRK_SCOPE,1765 Self::get_scoped_property_key(ResourceId(resource_id))?,1766 |value| match value {1767 Some(value) => {1768 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17691770 f(&mut resource_info)?;17711772 *value = Self::encode_property_value(&resource_info)?;17731774 Ok(())1775 }1776 None => Err(<Error<T>>::ResourceDoesntExist.into()),1777 },1778 )1779 }17801781 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1782 fn change_collection_owner(1783 collection_id: CollectionId,1784 collection_type: misc::CollectionType,1785 sender: T::AccountId,1786 new_owner: T::AccountId,1787 ) -> DispatchResult {1788 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1789 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17901791 let mut collection = collection.into_inner();17921793 collection.owner = new_owner;1794 collection.save()1795 }17961797 /// Ensure that an account is the collection owner/issuer, return an error if not.1798 pub fn check_collection_owner(1799 collection: &NonfungibleHandle<T>,1800 account: &T::CrossAccountId,1801 ) -> DispatchResult {1802 collection1803 .check_is_owner(account)1804 .map_err(Self::map_unique_err_to_proxy)1805 }18061807 /// Get the latest yet-unused RMRK collection index from the storage.1808 pub fn last_collection_idx() -> RmrkCollectionId {1809 <CollectionIndex<T>>::get()1810 }18111812 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1813 pub fn unique_collection_id(1814 rmrk_collection_id: RmrkCollectionId,1815 ) -> Result<CollectionId, DispatchError> {1816 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1817 .map_err(|_| <Error<T>>::CollectionUnknown.into())1818 }18191820 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1821 pub fn rmrk_collection_id(1822 unique_collection_id: CollectionId,1823 ) -> Result<RmrkCollectionId, DispatchError> {1824 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1825 }18261827 /// Fetch a Unique NFT collection.1828 pub fn get_nft_collection(1829 collection_id: CollectionId,1830 ) -> Result<NonfungibleHandle<T>, DispatchError> {1831 let collection = <CollectionHandle<T>>::try_get(collection_id)1832 .map_err(|_| <Error<T>>::CollectionUnknown)?;18331834 match collection.mode {1835 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1836 _ => Err(<Error<T>>::CollectionUnknown.into()),1837 }1838 }18391840 /// Check if an NFT collection with such an ID exists.1841 pub fn collection_exists(collection_id: CollectionId) -> bool {1842 <CollectionHandle<T>>::try_get(collection_id).is_ok()1843 }18441845 /// Fetch and decode a RMRK-scoped collection property value in bytes.1846 pub fn get_collection_property(1847 collection_id: CollectionId,1848 key: RmrkProperty,1849 ) -> Result<PropertyValue, DispatchError> {1850 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1851 .get(&Self::get_scoped_property_key(key)?)1852 .ok_or(<Error<T>>::CollectionUnknown)?1853 .clone();18541855 Ok(collection_property)1856 }18571858 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1859 pub fn get_collection_property_decoded<V: Decode>(1860 collection_id: CollectionId,1861 key: RmrkProperty,1862 ) -> Result<V, DispatchError> {1863 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1864 }18651866 /// Get the type of a collection stored as a scoped property.1867 ///1868 /// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1869 pub fn get_collection_type(1870 collection_id: CollectionId,1871 ) -> Result<misc::CollectionType, DispatchError> {1872 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1873 if err != <Error<T>>::CollectionUnknown.into() {1874 <Error<T>>::CorruptedCollectionType.into()1875 } else {1876 err1877 }1878 })1879 }18801881 /// Ensure that the type of the collection equals the provided type,1882 /// otherwise return an error.1883 pub fn ensure_collection_type(1884 collection_id: CollectionId,1885 collection_type: misc::CollectionType,1886 ) -> DispatchResult {1887 let actual_type = Self::get_collection_type(collection_id)?;1888 ensure!(1889 actual_type == collection_type,1890 <CommonError<T>>::NoPermission1891 );18921893 Ok(())1894 }18951896 /// Fetch an NFT collection, but make sure it has the appropriate type.1897 pub fn get_typed_nft_collection(1898 collection_id: CollectionId,1899 collection_type: misc::CollectionType,1900 ) -> Result<NonfungibleHandle<T>, DispatchError> {1901 Self::ensure_collection_type(collection_id, collection_type)?;19021903 Self::get_nft_collection(collection_id)1904 }19051906 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1907 /// but also return the Unique collection ID.1908 pub fn get_typed_nft_collection_mapped(1909 rmrk_collection_id: RmrkCollectionId,1910 collection_type: misc::CollectionType,1911 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1912 let unique_collection_id = match collection_type {1913 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1914 _ => rmrk_collection_id.into(),1915 };19161917 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19181919 Ok((collection, unique_collection_id))1920 }19211922 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1923 pub fn get_nft_property(1924 collection_id: CollectionId,1925 nft_id: TokenId,1926 key: RmrkProperty,1927 ) -> Result<PropertyValue, DispatchError> {1928 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1929 .get(&Self::get_scoped_property_key(key)?)1930 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1931 .clone();19321933 Ok(nft_property)1934 }19351936 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1937 pub fn get_nft_property_decoded<V: Decode>(1938 collection_id: CollectionId,1939 nft_id: TokenId,1940 key: RmrkProperty,1941 ) -> Result<V, DispatchError> {1942 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1943 }19441945 /// Check that an NFT exists.1946 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1947 <TokenData<T>>::contains_key((collection_id, nft_id))1948 }19491950 /// Get the type of an NFT stored as a scoped property.1951 ///1952 /// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1953 pub fn get_nft_type(1954 collection_id: CollectionId,1955 token_id: TokenId,1956 ) -> Result<NftType, DispatchError> {1957 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1958 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1959 }19601961 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1962 pub fn ensure_nft_type(1963 collection_id: CollectionId,1964 token_id: TokenId,1965 nft_type: NftType,1966 ) -> DispatchResult {1967 let actual_type = Self::get_nft_type(collection_id, token_id)?;1968 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19691970 Ok(())1971 }19721973 /// Ensure that an account is the owner of the token, either directly1974 /// or at the top of the nesting hierarchy; return an error if it is not.1975 pub fn ensure_nft_owner(1976 collection_id: CollectionId,1977 token_id: TokenId,1978 possible_owner: &T::CrossAccountId,1979 nesting_budget: &dyn budget::Budget,1980 ) -> DispatchResult {1981 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1982 possible_owner.clone(),1983 collection_id,1984 token_id,1985 None,1986 nesting_budget,1987 )1988 .map_err(Self::map_unique_err_to_proxy)?;19891990 ensure!(is_owned, <Error<T>>::NoPermission);19911992 Ok(())1993 }19941995 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,1996 /// or, if None are provided, return all non-scoped properties.1997 pub fn filter_user_properties<Key, Value, R, Mapper>(1998 collection_id: CollectionId,1999 token_id: Option<TokenId>,2000 filter_keys: Option<Vec<RmrkPropertyKey>>,2001 mapper: Mapper,2002 ) -> Result<Vec<R>, DispatchError>2003 where2004 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2005 Value: Decode + Default,2006 Mapper: Fn(Key, Value) -> R,2007 {2008 filter_keys2009 .map(|keys| {2010 let properties = keys2011 .into_iter()2012 .filter_map(|key| {2013 let key: Key = key.try_into().ok()?;20142015 let value = match token_id {2016 Some(token_id) => Self::get_nft_property_decoded(2017 collection_id,2018 token_id,2019 UserProperty(key.as_ref()),2020 ),2021 None => Self::get_collection_property_decoded(2022 collection_id,2023 UserProperty(key.as_ref()),2024 ),2025 }2026 .ok()?;20272028 Some(mapper(key, value))2029 })2030 .collect();20312032 Ok(properties)2033 })2034 .unwrap_or_else(|| {2035 let properties =2036 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20372038 Ok(properties)2039 })2040 }20412042 /// Get all non-scoped properties from a collection or a token, and apply some transformation,2043 /// supplied by `mapper`, to each key-value pair.2044 pub fn iterate_user_properties<Key, Value, R, Mapper>(2045 collection_id: CollectionId,2046 token_id: Option<TokenId>,2047 mapper: Mapper,2048 ) -> Result<impl Iterator<Item = R>, DispatchError>2049 where2050 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2051 Value: Decode + Default,2052 Mapper: Fn(Key, Value) -> R,2053 {2054 let properties = match token_id {2055 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2056 None => <PalletCommon<T>>::collection_properties(collection_id),2057 };20582059 let properties = properties.into_iter().filter_map(move |(key, value)| {2060 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20612062 let key: Key = key.to_vec().try_into().ok()?;2063 let value: Value = value.decode().ok()?;20642065 Some(mapper(key, value))2066 });20672068 Ok(properties)2069 }20702071 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2072 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2073 map_unique_err_to_proxy! {2074 match err {2075 CommonError::NoPermission => NoPermission,2076 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2077 CommonError::PublicMintingNotAllowed => NoPermission,2078 CommonError::TokenNotFound => NoAvailableNftId,2079 CommonError::ApprovedValueTooLow => NoPermission,2080 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2081 StructureError::TokenNotFound => NoAvailableNftId,2082 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2083 }2084 }2085 }2086}pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -14,9 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! Miscellaneous helpers and utilities used by the proxy pallet.
+
use super::*;
use codec::{Encode, Decode, Error};
+/// Match an error to a provided pattern matcher and get
+/// the corresponding error of another type if a match is successful.
#[macro_export]
macro_rules! map_unique_err_to_proxy {
(match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {
@@ -30,8 +34,10 @@
};
}
-// Utilize the RmrkCore pallet for access to Runtime errors.
+/// Interface to decode some serialized bytes into an arbitrary type `T`,
+/// preferably if these bytes were originally encoded from `T`.
pub trait RmrkDecode<T: Decode, S> {
+ /// Try to decode self into an arbitrary type `T`.
fn decode(&self) -> Result<T, Error>;
}
@@ -43,8 +49,9 @@
}
}
-// Utilize the RmrkCore pallet for access to Runtime errors.
+/// Interface to "rebind" - change the limit of a bounded byte vector.
pub trait RmrkRebind<T, S> {
+ /// Try to change the limit of a bounded byte vector.
fn rebind(&self) -> Result<BoundedVec<u8, S>, Error>;
}
@@ -58,12 +65,16 @@
}
}
+/// RMRK Base shares functionality with a regular collection, and is thus
+/// stored as one, but they are used for different purposes and need to be differentiated.
#[derive(Encode, Decode, PartialEq, Eq)]
pub enum CollectionType {
Regular,
Base,
}
+/// RMRK Base, being stored as a collection, can have different kinds of tokens,
+/// all except the `Regular` type, which is attributed to `Regular` collection.
#[derive(Encode, Decode, PartialEq, Eq)]
pub enum NftType {
Regular,
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -14,13 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! Details of storing and handling RMRK properties.
+
use super::*;
use up_data_structs::PropertyScope;
use core::convert::AsRef;
+/// Property prefix for storing resources.
pub const RESOURCE_ID_PREFIX: &str = "rsid-";
+/// Property prefix for storing custom user-defined properties.
pub const USER_PROPERTY_PREFIX: &str = "userprop-";
+/// Property scope for RMRK, used to signify that this property
+/// was created and is used by RMRK.
+pub const RMRK_SCOPE: PropertyScope = PropertyScope::Rmrk;
+/// Predefined RMRK property keys for storage of RMRK data format on the Unique chain.
pub enum RmrkProperty<'r> {
Metadata,
CollectionType,
@@ -49,6 +57,7 @@
}
impl<'r> RmrkProperty<'r> {
+ /// Convert a predefined RMRK property key enum into string bytes.
pub fn to_key<T: Config>(self) -> Result<PropertyKey, Error<T>> {
fn get_bytes<T: AsRef<[u8]>>(container: &T) -> &[u8] {
container.as_ref()
@@ -94,9 +103,10 @@
}
}
+/// Strip a property key of its prefix and RMRK scope.
pub fn strip_key_prefix(key: &PropertyKey, prefix: &str) -> Option<PropertyKey> {
let key_prefix = PropertyKey::try_from(prefix.as_bytes().to_vec()).ok()?;
- let key_prefix = PropertyScope::Rmrk.apply(key_prefix).ok()?;
+ let key_prefix = RMRK_SCOPE.apply(key_prefix).ok()?;
key.as_slice()
.strip_prefix(key_prefix.as_slice())?
@@ -105,6 +115,7 @@
.ok()
}
+/// Check that the key has the prefix.
pub fn is_valid_key_prefix(key: &PropertyKey, prefix: &str) -> bool {
strip_key_prefix(key, prefix).is_some()
}
pallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -1,9 +1,29 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! Realizations of RMRK RPCs (remote procedure calls) related to the Core pallet.
+
use super::*;
+/// Get the latest created collection ID.
pub fn last_collection_idx<T: Config>() -> Result<RmrkCollectionId, DispatchError> {
Ok(<Pallet<T>>::last_collection_idx())
}
+/// Get collection info by ID.
pub fn collection_by_id<T: Config>(
collection_id: RmrkCollectionId,
) -> Result<Option<RmrkCollectionInfo<T::AccountId>>, DispatchError> {
@@ -29,6 +49,7 @@
}))
}
+/// Get NFT info by collection and NFT IDs.
pub fn nft_by_id<T: Config>(
collection_id: RmrkCollectionId,
nft_by_id: RmrkNftId,
@@ -83,6 +104,7 @@
}))
}
+/// Get tokens owned by an account in a collection.
pub fn account_tokens<T: Config>(
account_id: T::AccountId,
collection_id: RmrkCollectionId,
@@ -116,6 +138,7 @@
Ok(tokens)
}
+/// Get tokens nested in an NFT - its direct children (not the children's children).
pub fn nft_children<T: Config>(
collection_id: RmrkCollectionId,
nft_id: RmrkNftId,
@@ -152,6 +175,7 @@
)
}
+/// Get collection properties, created by the user - not the proxy-specific properties.
pub fn collection_properties<T: Config>(
collection_id: RmrkCollectionId,
filter_keys: Option<Vec<RmrkPropertyKey>>,
@@ -174,6 +198,7 @@
Ok(properties)
}
+/// Get NFT properties, created by the user - not the proxy-specific properties.
pub fn nft_properties<T: Config>(
collection_id: RmrkCollectionId,
nft_id: RmrkNftId,
@@ -199,6 +224,7 @@
Ok(properties)
}
+/// Get full information on each resource of an NFT, including pending.
pub fn nft_resources<T: Config>(
collection_id: RmrkCollectionId,
nft_id: RmrkNftId,
@@ -226,7 +252,7 @@
return None;
}
- let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property(&value).ok()?;
+ let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property_value(&value).ok()?;
Some(resource_info)
})
@@ -235,6 +261,7 @@
Ok(resources)
}
+/// Get the priority of a resource in an NFT.
pub fn nft_resource_priority<T: Config>(
collection_id: RmrkCollectionId,
nft_id: RmrkNftId,
pallets/proxy-rmrk-equip/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-equip/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
use sp_std::vec;
use frame_benchmarking::{benchmarks, account};
pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -14,6 +14,123 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # RMRK Core Proxy Pallet
+//!
+//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).
+//!
+//! - [`Config`]
+//! - [`Call`]
+//! - [`Pallet`]
+//!
+//! ## Overview
+//!
+//! The RMRK Equip Proxy pallet mirrors the functionality of RMRK Equip,
+//! binding its externalities to Unique's own underlying structure.
+//! It is purposed to mimic RMRK Equip exactly, allowing seamless integrations
+//! of solutions based on RMRK.
+//!
+//! RMRK Equip itself contains functionality to equip NFTs, and work with Bases,
+//! Parts, and Themes. See [Proxy Implementation](#proxy-implementation) for details.
+//!
+//! Equip Proxy is responsible for a more specific area of RMRK, and heavily relies on the Core.
+//! For a more foundational description of proxy implementation, please refer to [`pallet_rmrk_core`].
+//!
+//! *Note*, that while RMRK itself is subject to active development and restructuring,
+//! the proxy may be caught temporarily out of date.
+//!
+//! ### What is RMRK?
+//!
+//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.
+//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.
+//!
+//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,
+//! make use of specific changeable and partially shared metadata in the form of resources,
+//! and more.
+//!
+//! Visit RMRK documentation and repositories to learn more:
+//! - Docs: <https://docs.rmrk.app/getting-started/>
+//! - FAQ: <https://coda.io/@rmrk/faq>
+//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>
+//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>
+//!
+//! ## Terminology
+//!
+//! For more information on RMRK, see RMRK's own documentation.
+//!
+//! ### Intro to RMRK
+//!
+//! - **Resource:** Additional piece of metadata of an NFT usually serving to add
+//! a piece of media on top of the root metadata (NFT's own), be it a different wing
+//! on the root template bird or something entirely unrelated.
+//!
+//! - **Base:** A list of possible "components" - Parts, a combination of which can
+//! be appended/equipped to/on an NFT.
+//!
+//! - **Part:** Something that, together with other Parts, can constitute an NFT.
+//! Parts are defined in the Base to which they belong. Parts can be either
+//! of the `slot` type or `fixed` type. Slots are intended for equippables.
+//! Note that "part of something" and "Part of a Base" can be easily confused,
+//! and so in this documentation these words are distinguished by the capital letter.
+//!
+//! - **Theme:** Named objects of variable => value pairs which get interpolated into
+//! the Base's `themable` Parts. Themes can hold any value, but are often represented
+//! in RMRK's examples as colors applied to visible Parts.
+//!
+//! ### Peculiarities in Unique
+//!
+//! - **Scoped properties:** Properties that are normally obscured from users.
+//! Their purpose is to contain structured metadata that was not included in the Unique standard
+//! for collections and tokens, meant to be operated on by proxies and other outliers.
+//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is
+//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined
+//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.
+//!
+//! - **Auxiliary properties:** A slightly different structure of properties,
+//! trading universality of use for more convenient storage, writes and access.
+//! Meant to be inaccessible to end users.
+//!
+//! ## Proxy Implementation
+//!
+//! An external user is supposed to be able to utilize this proxy as they would
+//! utilize RMRK, and get exactly the same results. Normally, Unique transactions
+//! are off-limits to RMRK collections and tokens, and vice versa. However,
+//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.
+//!
+//! ### ID Mapping
+//!
+//! RMRK's collections' IDs are counted independently of Unique's and start at 0.
+//! Note that tokens' IDs still start at 1.
+//! The collections themselves, as well as tokens, are stored as Unique collections,
+//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).
+//!
+//! ### External/Internal Collection Insulation
+//!
+//! A Unique transaction cannot target collections purposed for RMRK,
+//! and they are flagged as `external` to specify that. On the other hand,
+//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.
+//!
+//! ### Native Properties
+//!
+//! Many of RMRK's native parameters are stored as scoped properties of a collection
+//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`
+//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,
+//! makes them impossible to tamper with.
+//!
+//! ### Collection and NFT Types, or Base, Parts and Themes Handling
+//!
+//! RMRK introduces the concept of a Base, which is a catalogue of Parts,
+//! possible components of an NFT. Due to its similarity with the functionality
+//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes
+//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].
+//!
+//! ## Interface
+//!
+//! ### Dispatchables
+//!
+//! - `create_base` - Create a new Base.
+//! - `theme_add` - Add a Theme to a Base.
+//! - `equippable` - Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
+
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};
@@ -45,15 +162,20 @@
#[pallet::config]
pub trait Config: frame_system::Config + pallet_rmrk_core::Config {
+ /// Overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+
+ /// The weight information of this pallet.
type WeightInfo: WeightInfo;
}
+ /// Map of a Base ID and a Part ID to an NFT in the Base collection serving as the Part.
#[pallet::storage]
#[pallet::getter(fn internal_part_id)]
pub type InernalPartId<T: Config> =
StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;
+ /// Checkmark that a Base has a Theme NFT named "default".
#[pallet::storage]
#[pallet::getter(fn base_has_default_theme)]
pub type BaseHasDefaultTheme<T: Config> =
@@ -78,26 +200,36 @@
#[pallet::error]
pub enum Error<T> {
+ /// No permission to perform action.
PermissionError,
+ /// Could not find an ID for a Base collection. It is likely there were too many collections created on the chain, causing an overflow.
NoAvailableBaseId,
+ /// Could not find a suitable ID for a Part, likely too many Part tokens were created in the Base, causing an overflow
NoAvailablePartId,
+ /// Base collection linked to this ID does not exist.
BaseDoesntExist,
+ /// No Theme named "default" is associated with the Base.
NeedsDefaultThemeFirst,
+ /// Part linked to this ID does not exist.
PartDoesntExist,
+ /// Cannot assign equippables to a fixed Part.
NoEquippableOnFixedPart,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
- /// Creates a new Base.
- /// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+ /// Create a new Base.
///
- /// Parameters:
- /// - origin: Caller, will be assigned as the issuer of the Base
- /// - base_type: media type, e.g. "svg"
- /// - symbol: arbitrary client-chosen symbol
- /// - parts: array of Fixed and Slot parts composing the base, confined in length by
- /// RmrkPartsLimit
+ /// Modeled after the [Base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+ ///
+ /// # Permissions
+ /// - Anyone - will be assigned as the issuer of the Base.
+ ///
+ /// # Arguments:
+ /// - `base_type`: Arbitrary media type, e.g. "svg".
+ /// - `symbol`: Arbitrary client-chosen symbol.
+ /// - `parts`: Array of Fixed and Slot Parts composing the Base,
+ /// confined in length by [`RmrkPartsLimit`](up_data_structs::RmrkPartsLimit).
#[transactional]
#[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]
pub fn create_base(
@@ -131,8 +263,11 @@
collection_id,
PropertyScope::Rmrk,
[
- <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,
- <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,
+ <PalletCore<T>>::encode_rmrk_property(
+ CollectionType,
+ &misc::CollectionType::Base,
+ )?,
+ <PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,
]
.into_iter(),
)?;
@@ -151,19 +286,21 @@
Ok(())
}
- /// Adds a Theme to a Base.
- /// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)
- /// Themes are stored in the Themes storage
+ /// Add a Theme to a Base.
/// A Theme named "default" is required prior to adding other Themes.
///
- /// Parameters:
- /// - origin: The caller of the function, must be issuer of the base
- /// - base_id: The Base containing the Theme to be updated
- /// - theme: The Theme to add to the Base. A Theme has a name and properties, which are an
+ /// Modeled after [Themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md).
+ ///
+ /// # Permissions:
+ /// - Base issuer
+ ///
+ /// # Arguments:
+ /// - `base_id`: Base ID containing the Theme to be updated.
+ /// - `theme`: Theme to add to the Base. A Theme has a name and properties, which are an
/// array of [key, value, inherit].
- /// - key: arbitrary BoundedString, defined by client
- /// - value: arbitrary BoundedString, defined by client
- /// - inherit: optional bool
+ /// - `key`: Arbitrary BoundedString, defined by client.
+ /// - `value`: Arbitrary BoundedString, defined by client.
+ /// - `inherit`: Optional bool.
#[transactional]
#[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]
pub fn theme_add(
@@ -191,9 +328,9 @@
owner,
&collection,
[
- <PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,
- <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,
- <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,
+ <PalletCore<T>>::encode_rmrk_property(TokenType, &NftType::Theme)?,
+ <PalletCore<T>>::encode_rmrk_property(ThemeName, &theme.name)?,
+ <PalletCore<T>>::encode_rmrk_property(ThemeInherit, &theme.inherit)?,
]
.into_iter(),
)
@@ -204,7 +341,7 @@
collection_id,
token_id,
PropertyScope::Rmrk,
- <PalletCore<T>>::rmrk_property(
+ <PalletCore<T>>::encode_rmrk_property(
UserProperty(property.key.as_slice()),
&property.value,
)?,
@@ -214,6 +351,17 @@
Ok(())
}
+ /// Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
+ ///
+ /// Modeled after [equippable interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/equippable.md).
+ ///
+ /// # Permissions:
+ /// - Base issuer
+ ///
+ /// # Arguments:
+ /// - `base_id`: Base containing the Slot Part to be updated.
+ /// - `part_id`: Slot Part whose Equippable List is being updated.
+ /// - `equippables`: List of equippables that will override the current Equippables list.
#[transactional]
#[pallet::weight(<SelfWeightOf<T>>::equippable())]
pub fn equippable(
@@ -253,7 +401,7 @@
base_collection_id,
part_id,
PropertyScope::Rmrk,
- <PalletCore<T>>::rmrk_property(EquippableList, &equippables)?,
+ <PalletCore<T>>::encode_rmrk_property(EquippableList, &equippables)?,
)?;
}
}
@@ -266,6 +414,8 @@
}
impl<T: Config> Pallet<T> {
+ /// Create (or overwrite) a Part in a Base.
+ /// The Part and the Base are represented as an NFT and a Collection.
fn create_part(
sender: &T::CrossAccountId,
collection: &NonfungibleHandle<T>,
@@ -298,7 +448,7 @@
collection.id,
token_id,
PropertyScope::Rmrk,
- <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,
+ <PalletCore<T>>::encode_rmrk_property(ExternalPartId, &part_id)?,
)?;
token_id
@@ -310,9 +460,9 @@
token_id,
PropertyScope::Rmrk,
[
- <PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,
- <PalletCore<T>>::rmrk_property(Src, &src)?,
- <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,
+ <PalletCore<T>>::encode_rmrk_property(TokenType, &nft_type)?,
+ <PalletCore<T>>::encode_rmrk_property(Src, &src)?,
+ <PalletCore<T>>::encode_rmrk_property(ZIndex, &z_index)?,
]
.into_iter(),
)?;
@@ -322,13 +472,15 @@
collection.id,
token_id,
PropertyScope::Rmrk,
- <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,
+ <PalletCore<T>>::encode_rmrk_property(EquippableList, &part.equippable)?,
)?;
}
Ok(())
}
+ /// Ensure that the collection under the Base ID is a Base collection,
+ /// and fetch it.
fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {
let collection =
<PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)
pallets/proxy-rmrk-equip/src/rpc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/rpc.rs
+++ b/pallets/proxy-rmrk-equip/src/rpc.rs
@@ -1,7 +1,26 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! Realizations of RMRK RPCs (remote procedure calls) related to the Equip pallet.
+
use super::*;
use pallet_rmrk_core::{misc, property::*};
use sp_std::vec::Vec;
+/// Get base info by its ID.
pub fn base<T: Config>(
base_id: RmrkBaseId,
) -> Result<Option<RmrkBaseInfo<T::AccountId>>, DispatchError> {
@@ -22,6 +41,7 @@
}))
}
+/// Get all parts of a base.
pub fn base_parts<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
use pallet_common::CommonCollectionOperations;
@@ -93,6 +113,7 @@
Ok(parts)
}
+/// Get the theme names belonging to a base.
pub fn theme_names<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
use pallet_common::CommonCollectionOperations;
@@ -124,6 +145,7 @@
Ok(theme_names)
}
+/// Get theme info, including properties, optionally limited to the provided keys.
pub fn theme<T: Config>(
base_id: RmrkBaseId,
theme_name: RmrkThemeName,
primitives/rmrk-traits/src/resource.rsdiffbeforeafterboth--- a/primitives/rmrk-traits/src/resource.rs
+++ b/primitives/rmrk-traits/src/resource.rs
@@ -151,13 +151,13 @@
"#)
)]
pub struct ResourceInfo<BoundedString, BoundedParts> {
- /// id is a 5-character string of reasonable uniqueness.
- /// The combination of base ID and resource id should be unique across the entire RMRK
- /// ecosystem which
+ /// ID is a unique identifier for a resource across all those of a single NFT.
+ /// The combination of a collection ID, an NFT ID, and the resource ID must be
+ /// unique across the entire RMRK ecosystem.
//#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
pub id: ResourceId,
- /// Resource
+ /// Resource type and the accordingly structured data stored
pub resource: ResourceTypes<BoundedString, BoundedParts>,
/// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted