difftreelog
Merge pull request #439 from UniqueNetwork/doc/rmrk
in: master
9 files changed
pallets/proxy-rmrk-core/src/benchmarking.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/>.161use sp_std::vec;17use sp_std::vec;218pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.161617//! # 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.14517#![cfg_attr(not(feature = "std"), no_std)]146#![cfg_attr(not(feature = "std"), no_std)]181474917850use RmrkProperty::*;179use RmrkProperty::*;51180181/// Maximum number of levels of depth in the token nesting tree.52pub const NESTING_BUDGET: u32 = 5;182pub const NESTING_BUDGET: u32 = 5;5318354type PendingTarget = (CollectionId, TokenId);184type PendingTarget = (CollectionId, TokenId);66 pub trait Config:196 pub trait Config:67 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config197 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config68 {198 {199 /// Overarching event type.69 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;200 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;201202 /// The weight information of this pallet.70 type WeightInfo: WeightInfo;203 type WeightInfo: WeightInfo;71 }204 }72205206 /// Latest yet-unused collection ID.73 #[pallet::storage]207 #[pallet::storage]74 #[pallet::getter(fn collection_index)]208 #[pallet::getter(fn collection_index)]75 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;209 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;76210211 /// Mapping from RMRK collection ID to Unique's.77 #[pallet::storage]212 #[pallet::storage]78 pub type UniqueCollectionId<T: Config> =213 pub type UniqueCollectionId<T: Config> =79 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;214 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;159294160 #[pallet::error]295 #[pallet::error]161 pub enum Error<T> {296 pub enum Error<T> {162 /* Unique-specific events */297 /* Unique proxy-specific events */298 /// Property of the type of RMRK collection could not be read successfully.163 CorruptedCollectionType,299 CorruptedCollectionType,164 NftTypeEncodeError,300 // NftTypeEncodeError,301 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).165 RmrkPropertyKeyIsTooLong,302 RmrkPropertyKeyIsTooLong,303 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).166 RmrkPropertyValueIsTooLong,304 RmrkPropertyValueIsTooLong,305 /// Could not find a property by the supplied key.167 RmrkPropertyIsNotFound,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.168 UnableToDecodeRmrkData,309 UnableToDecodeRmrkData,169310170 /* RMRK compatible events */311 /* RMRK compatible events */312 /// Only destroying collections without tokens is allowed.171 CollectionNotEmpty,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.172 NoAvailableCollectionId,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.173 NoAvailableNftId,317 NoAvailableNftId,318 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.174 CollectionUnknown,319 CollectionUnknown,320 /// No permission to perform action.175 NoPermission,321 NoPermission,322 /// Token is marked as non-transferable, and thus cannot be transferred.176 NonTransferable,323 NonTransferable,324 /// Too many tokens created in the collection, no new ones are allowed.177 CollectionFullOrLocked,325 CollectionFullOrLocked,326 /// No such resource found.178 ResourceDoesntExist,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.179 CannotSendToDescendentOrSelf,330 CannotSendToDescendentOrSelf,331 /// Not the target owner of the sent NFT.180 CannotAcceptNonOwnedNft,332 CannotAcceptNonOwnedNft,333 /// Not the target owner of the sent NFT.181 CannotRejectNonOwnedNft,334 CannotRejectNonOwnedNft,335 /// NFT was not sent and is not pending.182 CannotRejectNonPendingNft,336 CannotRejectNonPendingNft,337 /// Resource is not pending for the operation.183 ResourceNotPending,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.184 NoAvailableResourceId,340 NoAvailableResourceId,185 }341 }186342187 #[pallet::call]343 #[pallet::call]188 impl<T: Config> Pallet<T> {344 impl<T: Config> Pallet<T> {189 /// Create a collection345 // 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.190 #[transactional]357 #[transactional]191 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]358 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]192 pub fn create_collection(359 pub fn create_collection(226 T::CrossAccountId::from_sub(sender.clone()),393 T::CrossAccountId::from_sub(sender.clone()),227 data,394 data,228 [395 [229 Self::rmrk_property(Metadata, &metadata)?,396 Self::encode_rmrk_property(Metadata, &metadata)?,230 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,397 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,231 ]398 ]232 .into_iter(),399 .into_iter(),233 )?;400 )?;237404238 <PalletCommon<T>>::set_scoped_collection_property(405 <PalletCommon<T>>::set_scoped_collection_property(239 unique_collection_id,406 unique_collection_id,240 PropertyScope::Rmrk,407 RMRK_SCOPE,241 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,408 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,242 )?;409 )?;243410244 <CollectionIndex<T>>::mutate(|n| *n += 1);411 <CollectionIndex<T>>::mutate(|n| *n += 1);251 Ok(())418 Ok(())252 }419 }253420254 /// destroy collection421 /// 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.255 #[transactional]430 #[transactional]256 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]431 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]257 pub fn destroy_collection(432 pub fn destroy_collection(278 Ok(())453 Ok(())279 }454 }280455281 /// Change the issuer of a collection456 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).282 ///457 ///283 /// Parameters:458 /// # Permissions:284 /// - `origin`: sender of the transaction459 /// * Collection issuer460 ///461 /// # Arguments:285 /// - `collection_id`: collection id of the nft to change issuer of462 /// - `collection_id`: RMRK collection ID to change the issuer of.286 /// - `new_issuer`: Collection's new issuer463 /// - `new_issuer`: Collection's new issuer.287 #[transactional]464 #[transactional]288 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]465 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]289 pub fn change_collection_issuer(466 pub fn change_collection_issuer(314 Ok(())491 Ok(())315 }492 }316493317 /// lock collection494 /// "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.318 #[transactional]501 #[transactional]319 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]502 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]320 pub fn lock_collection(503 pub fn lock_collection(346 Ok(())529 Ok(())347 }530 }348531349 /// Mints an NFT in the specified collection532 /// Mint an NFT in a specified collection.350 /// Sets metadata and the royalty attribute351 ///533 ///352 /// Parameters:534 /// # Permissions:353 /// - `collection_id`: The class of the asset to be minted.535 /// * Collection issuer536 ///537 /// # Arguments:538 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).354 /// - `nft_id`: The nft value of the asset to be minted.539 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.355 /// - `recipient`: Receiver of the royalty540 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.356 /// - `royalty`: Permillage reward from each trade for the Recipient541 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.357 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash542 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.358 /// - `transferable`: Ability to transfer this NFT543 /// - `transferable`: Can this NFT be transferred? Cannot be changed.544 /// - `resources`: Resource data to be added to the NFT immediately after minting.359 #[transactional]545 #[transactional]360 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]546 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]361 pub fn mint_nft(547 pub fn mint_nft(390 &cross_owner,576 &cross_owner,391 &collection,577 &collection,392 [578 [393 Self::rmrk_property(TokenType, &NftType::Regular)?,579 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,394 Self::rmrk_property(Transferable, &transferable)?,580 Self::encode_rmrk_property(Transferable, &transferable)?,395 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,581 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,396 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,582 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,397 Self::rmrk_property(Metadata, &metadata)?,583 Self::encode_rmrk_property(Metadata, &metadata)?,398 Self::rmrk_property(Equipped, &false)?,584 Self::encode_rmrk_property(Equipped, &false)?,399 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,585 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,400 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,586 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,401 Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,587 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,402 Self::rmrk_property(AssociatedBases, &BasesMap::new())?,588 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,403 ]589 ]404 .into_iter(),590 .into_iter(),405 )591 )423 Ok(())609 Ok(())424 }610 }425611426 /// burn nft612 /// 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.427 #[transactional]628 #[transactional]428 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]629 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]429 pub fn burn_nft(630 pub fn burn_nft(458 Ok(())659 Ok(())459 }660 }460661461 /// Transfers a NFT from an Account or NFT A to another Account or NFT B662 /// 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`].462 ///664 ///463 /// Parameters:665 /// If the target owner is an NFT owned by another account, then the NFT will enter464 /// - `origin`: sender of the transaction666 /// the pending state and will have to be accepted by the other account.465 /// - `rmrk_collection_id`: collection id of the nft to be transferred667 ///668 /// # Permissions:669 /// - Token owner670 ///671 /// # Arguments:672 /// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.466 /// - `rmrk_nft_id`: nft id of the nft to be transferred673 /// - `nft_id`: ID of the NFT to be transferred.467 /// - `new_owner`: new owner of the nft which can be either an account or a NFT674 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.468 #[transactional]675 #[transactional]469 #[pallet::weight(<SelfWeightOf<T>>::send())]676 #[pallet::weight(<SelfWeightOf<T>>::send())]470 pub fn send(677 pub fn send(535 <PalletNft<T>>::set_scoped_token_property(742 <PalletNft<T>>::set_scoped_token_property(536 collection.id,743 collection.id,537 nft_id,744 nft_id,538 PropertyScope::Rmrk,745 RMRK_SCOPE,539 Self::rmrk_property::<Option<PendingTarget>>(746 Self::encode_rmrk_property::<Option<PendingTarget>>(540 PendingNftAccept,747 PendingNftAccept,541 &Some((target_collection_id, target_nft_id.into())),748 &Some((target_collection_id, target_nft_id.into())),542 )?,749 )?,578 Ok(())785 Ok(())579 }786 }580787581 /// Accepts an NFT sent from another account to self or owned NFT788 /// Accept an NFT sent from another account to self or an owned NFT.582 ///789 ///583 /// Parameters:790 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.584 /// - `origin`: sender of the transaction791 ///792 /// # Permissions:793 /// - Token-owner-to-be794 ///795 /// # Arguments:585 /// - `rmrk_collection_id`: collection id of the nft to be accepted796 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.586 /// - `rmrk_nft_id`: nft id of the nft to be accepted797 /// - `rmrk_nft_id`: ID of the NFT to be accepted.587 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was798 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,588 /// sent to799 /// whichever the accepted NFT was sent to.589 #[transactional]800 #[transactional]590 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]801 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]591 pub fn accept_nft(802 pub fn accept_nft(650 <PalletNft<T>>::set_scoped_token_property(861 <PalletNft<T>>::set_scoped_token_property(651 collection.id,862 collection.id,652 nft_id,863 nft_id,653 PropertyScope::Rmrk,864 RMRK_SCOPE,654 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,865 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,655 )?;866 )?;656 }867 }657868665 Ok(())876 Ok(())666 }877 }667878668 /// Rejects an NFT sent from another account to self or owned NFT879 /// 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.669 ///881 ///670 /// Parameters:882 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.671 /// - `origin`: sender of the transaction883 ///884 /// # Permissions:885 /// - Token-owner-to-be-not886 ///887 /// # Arguments:672 /// - `rmrk_collection_id`: collection id of the nft to be accepted888 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.673 /// - `rmrk_nft_id`: nft id of the nft to be accepted889 /// - `rmrk_nft_id`: ID of the NFT to be rejected.674 #[transactional]890 #[transactional]675 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]891 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]676 pub fn reject_nft(892 pub fn reject_nft(724 Ok(())940 Ok(())725 }941 }726942727 /// accept the addition of a new resource to an existing NFT943 /// 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.728 #[transactional]956 #[transactional]729 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]957 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730 pub fn accept_resource(958 pub fn accept_resource(767 Ok(())995 Ok(())768 }996 }769997770 /// accept the removal of a resource of an existing NFT998 /// 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.771 #[transactional]1010 #[transactional]772 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1011 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773 pub fn accept_resource_removal(1012 pub fn accept_resource_removal(7951034796 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);1035 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);7971036798 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1037 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;7991038800 let resource_info = <PalletNft<T>>::token_aux_property((1039 let resource_info = <PalletNft<T>>::token_aux_property((801 collection_id,1040 collection_id,802 nft_id,1041 nft_id,803 PropertyScope::Rmrk,1042 RMRK_SCOPE,804 resource_id_key.clone(),1043 resource_id_key.clone(),805 ))1044 ))806 .ok_or(<Error<T>>::ResourceDoesntExist)?;1045 .ok_or(<Error<T>>::ResourceDoesntExist)?;8071046808 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;1047 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;8091048810 ensure!(1049 ensure!(811 resource_info.pending_removal,1050 resource_info.pending_removal,815 <PalletNft<T>>::remove_token_aux_property(1054 <PalletNft<T>>::remove_token_aux_property(816 collection_id,1055 collection_id,817 nft_id,1056 nft_id,818 PropertyScope::Rmrk,1057 RMRK_SCOPE,819 resource_id_key,1058 resource_id_key,820 );1059 );8211060833 Ok(())1072 Ok(())834 }1073 }8351074836 /// set a custom value on an NFT1075 /// 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.837 #[transactional]1091 #[transactional]838 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1092 #[pallet::weight(<SelfWeightOf<T>>::set_property())]839 pub fn set_property(1093 pub fn set_property(863 <PalletNft<T>>::set_scoped_token_property(1117 <PalletNft<T>>::set_scoped_token_property(864 collection_id,1118 collection_id,865 token_id,1119 token_id,866 PropertyScope::Rmrk,1120 RMRK_SCOPE,867 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,1121 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,868 )?;1122 )?;869 }1123 }870 None => {1124 None => {8771131878 <PalletCommon<T>>::set_scoped_collection_property(1132 <PalletCommon<T>>::set_scoped_collection_property(879 collection_id,1133 collection_id,880 PropertyScope::Rmrk,1134 RMRK_SCOPE,881 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,1135 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,882 )?;1136 )?;883 }1137 }884 }1138 }893 Ok(())1147 Ok(())894 }1148 }8951149896 /// set a different order of resource priority1150 /// 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.897 #[transactional]1164 #[transactional]898 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1165 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]899 pub fn set_priority(1166 pub fn set_priority(920 <PalletNft<T>>::set_scoped_token_property(1187 <PalletNft<T>>::set_scoped_token_property(921 collection_id,1188 collection_id,922 nft_id,1189 nft_id,923 PropertyScope::Rmrk,1190 RMRK_SCOPE,924 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,1191 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,925 )?;1192 )?;9261193927 Self::deposit_event(Event::<T>::PrioritySet {1194 Self::deposit_event(Event::<T>::PrioritySet {932 Ok(())1199 Ok(())933 }1200 }9341201935 /// Create basic resource1202 /// 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.936 #[transactional]1215 #[transactional]937 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1216 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938 pub fn add_basic_resource(1217 pub fn add_basic_resource(962 Ok(())1241 Ok(())963 }1242 }9641243965 /// Create composable resource1244 /// 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.966 #[transactional]1257 #[transactional]967 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1258 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968 pub fn add_composable_resource(1259 pub fn add_composable_resource(990 <PalletNft<T>>::try_mutate_token_aux_property(1281 <PalletNft<T>>::try_mutate_token_aux_property(991 collection_id,1282 collection_id,992 nft_id.into(),1283 nft_id.into(),993 PropertyScope::Rmrk,1284 RMRK_SCOPE,994 Self::rmrk_property_key(AssociatedBases)?,1285 Self::get_scoped_property_key(AssociatedBases)?,995 |value| -> DispatchResult {1286 |value| -> DispatchResult {996 let mut bases: BasesMap = match value {1287 let mut bases: BasesMap = match value {997 Some(value) => Self::decode_property(value)?,1288 Some(value) => Self::decode_property_value(value)?,998 None => BasesMap::new(),1289 None => BasesMap::new(),999 };1290 };100012911001 *bases.entry(base_id).or_insert(0) += 1;1292 *bases.entry(base_id).or_insert(0) += 1;100212931003 *value = Some(Self::encode_property(&bases)?);1294 *value = Some(Self::encode_property_value(&bases)?);1004 Ok(())1295 Ok(())1005 },1296 },1006 )?;1297 )?;1012 Ok(())1303 Ok(())1013 }1304 }101413051015 /// Create slot resource1306 /// 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.1016 #[transactional]1319 #[transactional]1017 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1320 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018 pub fn add_slot_resource(1321 pub fn add_slot_resource(1042 Ok(())1345 Ok(())1043 }1346 }104413471045 /// remove resource1348 /// 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.1046 #[transactional]1360 #[transactional]1047 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1361 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048 pub fn remove_resource(1362 pub fn remove_resource(1070}1384}107113851072impl<T: Config> Pallet<T> {1386impl<T: Config> Pallet<T> {1073 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {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> {1074 let key = rmrk_key.to_key::<T>()?;1389 let key = rmrk_key.to_key::<T>()?;107513901076 let scoped_key = PropertyScope::Rmrk1391 let scoped_key = RMRK_SCOPE1077 .apply(key)1392 .apply(key)1078 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;1393 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;107913941080 Ok(scoped_key)1395 Ok(scoped_key)1081 }1396 }108213971083 // todo think about renaming these1398 /// 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.1084 pub fn rmrk_property<E: Encode>(1400 pub fn encode_rmrk_property<E: Encode>(1085 rmrk_key: RmrkProperty,1401 rmrk_key: RmrkProperty,1086 value: &E,1402 value: &E,1087 ) -> Result<Property, DispatchError> {1403 ) -> Result<Property, DispatchError> {1088 let key = rmrk_key.to_key::<T>()?;1404 let key = rmrk_key.to_key::<T>()?;108914051090 let value = Self::encode_property(value)?;1406 let value = Self::encode_property_value(value)?;109114071092 let property = Property { key, value };1408 let property = Property { key, value };109314091094 Ok(property)1410 Ok(property)1095 }1411 }109614121097 pub fn encode_property<E: Encode, S: Get<u32>>(1413 /// Encode property value from an arbitrary type into bytes for storage.1414 pub fn encode_property_value<E: Encode, S: Get<u32>>(1098 value: &E,1415 value: &E,1099 ) -> Result<BoundedBytes<S>, DispatchError> {1416 ) -> Result<BoundedBytes<S>, DispatchError> {1100 let value = value1417 let value = value1105 Ok(value)1422 Ok(value)1106 }1423 }110714241108 pub fn decode_property<D: Decode, S: Get<u32>>(1425 /// Decode property value from bytes into an arbitrary type.1426 pub fn decode_property_value<D: Decode, S: Get<u32>>(1109 vec: &BoundedBytes<S>,1427 vec: &BoundedBytes<S>,1110 ) -> Result<D, DispatchError> {1428 ) -> Result<D, DispatchError> {1111 vec.decode()1429 vec.decode()1112 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1430 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113 }1431 }111414321433 /// Change the limit of a property value byte vector.1115 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1434 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116 where1435 where1117 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1436 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1120 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1439 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121 }1440 }112214411442 /// Initialize a new NFT collection with certain RMRK-scoped properties.1443 ///1444 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1123 fn init_collection(1445 fn init_collection(1124 sender: T::CrossAccountId,1446 sender: T::CrossAccountId,1125 data: CreateCollectionData<T::AccountId>,1447 data: CreateCollectionData<T::AccountId>,113314551134 <PalletCommon<T>>::set_scoped_collection_properties(1456 <PalletCommon<T>>::set_scoped_collection_properties(1135 collection_id?,1457 collection_id?,1136 PropertyScope::Rmrk,1458 RMRK_SCOPE,1137 properties,1459 properties,1138 )?;1460 )?;113914611140 collection_id1462 collection_id1141 }1463 }114214641465 /// 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.1143 pub fn create_nft(1468 pub fn create_nft(1144 sender: &T::CrossAccountId,1469 sender: &T::CrossAccountId,1145 owner: &T::CrossAccountId,1470 owner: &T::CrossAccountId,115714821158 let nft_id = <PalletNft<T>>::current_token_id(collection.id);1483 let nft_id = <PalletNft<T>>::current_token_id(collection.id);115914841160 <PalletNft<T>>::set_scoped_token_properties(1485 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;1161 collection.id,1162 nft_id,1163 PropertyScope::Rmrk,1164 properties,1165 )?;116614861167 Ok(nft_id)1487 Ok(nft_id)1168 }1488 }116914891490 /// 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.1170 fn destroy_nft(1493 fn destroy_nft(1171 sender: T::CrossAccountId,1494 sender: T::CrossAccountId,1172 collection_id: CollectionId,1495 collection_id: CollectionId,1207 )1530 )1208 }1531 }120915321533 /// Add a sent token pending acceptance to the target owning token as a property.1210 fn insert_pending_child(1534 fn insert_pending_child(1211 target: (CollectionId, TokenId),1535 target: (CollectionId, TokenId),1212 child: (RmrkCollectionId, RmrkNftId),1536 child: (RmrkCollectionId, RmrkNftId),1213 ) -> DispatchResult {1537 ) -> DispatchResult {1214 Self::mutate_pending_child(target, |pending_children| {1538 Self::mutate_pending_children(target, |pending_children| {1215 pending_children.insert(child);1539 pending_children.insert(child);1216 })1540 })1217 }1541 }121815421543 /// Remove a sent token pending acceptance from the target token's properties.1219 fn remove_pending_child(1544 fn remove_pending_child(1220 target: (CollectionId, TokenId),1545 target: (CollectionId, TokenId),1221 child: (RmrkCollectionId, RmrkNftId),1546 child: (RmrkCollectionId, RmrkNftId),1222 ) -> DispatchResult {1547 ) -> DispatchResult {1223 Self::mutate_pending_child(target, |pending_children| {1548 Self::mutate_pending_children(target, |pending_children| {1224 pending_children.remove(&child);1549 pending_children.remove(&child);1225 })1550 })1226 }1551 }122715521228 fn mutate_pending_child(1553 /// Apply a mutation to the property of a token containing sent tokens1554 /// that are currently pending acceptance.1555 fn mutate_pending_children(1229 (target_collection_id, target_nft_id): (CollectionId, TokenId),1556 (target_collection_id, target_nft_id): (CollectionId, TokenId),1230 f: impl FnOnce(&mut PendingChildrenSet),1557 f: impl FnOnce(&mut PendingChildrenSet),1231 ) -> DispatchResult {1558 ) -> DispatchResult {1232 <PalletNft<T>>::try_mutate_token_aux_property(1559 <PalletNft<T>>::try_mutate_token_aux_property(1233 target_collection_id,1560 target_collection_id,1234 target_nft_id,1561 target_nft_id,1235 PropertyScope::Rmrk,1562 RMRK_SCOPE,1236 Self::rmrk_property_key(PendingChildren)?,1563 Self::get_scoped_property_key(PendingChildren)?,1237 |pending_children| -> DispatchResult {1564 |pending_children| -> DispatchResult {1238 let mut map = match pending_children {1565 let mut map = match pending_children {1239 Some(map) => Self::decode_property(map)?,1566 Some(map) => Self::decode_property_value(map)?,1240 None => PendingChildrenSet::new(),1567 None => PendingChildrenSet::new(),1241 };1568 };124215691243 f(&mut map);1570 f(&mut map);124415711245 *pending_children = Some(Self::encode_property(&map)?);1572 *pending_children = Some(Self::encode_property_value(&map)?);124615731247 Ok(())1574 Ok(())1248 },1575 },1249 )1576 )1250 }1577 }125115781579 /// Get an iterator from a token's property containing tokens sent to it1580 /// that are currently pending acceptance.1252 fn iterate_pending_children(1581 fn iterate_pending_children(1253 collection_id: CollectionId,1582 collection_id: CollectionId,1254 nft_id: TokenId,1583 nft_id: TokenId,1255 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1584 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1256 let property = <PalletNft<T>>::token_aux_property((1585 let property = <PalletNft<T>>::token_aux_property((1257 collection_id,1586 collection_id,1258 nft_id,1587 nft_id,1259 PropertyScope::Rmrk,1588 RMRK_SCOPE,1260 Self::rmrk_property_key(PendingChildren)?,1589 Self::get_scoped_property_key(PendingChildren)?,1261 ));1590 ));126215911263 let pending_children = match property {1592 let pending_children = match property {1264 Some(map) => Self::decode_property(&map)?,1593 Some(map) => Self::decode_property_value(&map)?,1265 None => PendingChildrenSet::new(),1594 None => PendingChildrenSet::new(),1266 };1595 };126715961268 Ok(pending_children.into_iter())1597 Ok(pending_children.into_iter())1269 }1598 }127015991600 /// 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.1271 fn acquire_next_resource_id(1604 fn acquire_next_resource_id(1272 collection_id: CollectionId,1605 collection_id: CollectionId,1273 nft_id: TokenId,1606 nft_id: TokenId,1282 <PalletNft<T>>::set_scoped_token_property(1615 <PalletNft<T>>::set_scoped_token_property(1283 collection_id,1616 collection_id,1284 nft_id,1617 nft_id,1285 PropertyScope::Rmrk,1618 RMRK_SCOPE,1286 Self::rmrk_property(NextResourceId, &next_id)?,1619 Self::encode_rmrk_property(NextResourceId, &next_id)?,1287 )?;1620 )?;128816211289 Ok(resource_id)1622 Ok(resource_id)1290 }1623 }129116241625 /// 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.1292 fn resource_add(1627 fn resource_add(1293 sender: T::AccountId,1628 sender: T::AccountId,1294 collection_id: CollectionId,1629 collection_id: CollectionId,1319 <PalletNft<T>>::try_mutate_token_aux_property(1654 <PalletNft<T>>::try_mutate_token_aux_property(1320 collection_id,1655 collection_id,1321 nft_id,1656 nft_id,1322 PropertyScope::Rmrk,1657 RMRK_SCOPE,1323 Self::rmrk_property_key(ResourceId(id))?,1658 Self::get_scoped_property_key(ResourceId(id))?,1324 |value| -> DispatchResult {1659 |value| -> DispatchResult {1325 *value = Some(Self::encode_property(&resource_info)?);1660 *value = Some(Self::encode_property_value(&resource_info)?);132616611327 Ok(())1662 Ok(())1328 },1663 },1331 Ok(id)1666 Ok(id)1332 }1667 }133316681669 /// 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.1334 fn resource_remove(1671 fn resource_remove(1335 sender: T::AccountId,1672 sender: T::AccountId,1336 collection_id: CollectionId,1673 collection_id: CollectionId,1341 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1678 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1342 ensure!(collection.owner == sender, Error::<T>::NoPermission);1679 ensure!(collection.owner == sender, Error::<T>::NoPermission);134316801344 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1681 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;1345 let scope = PropertyScope::Rmrk;134616821347 let resource = <PalletNft<T>>::token_aux_property((1683 let resource = <PalletNft<T>>::token_aux_property((1348 collection_id,1684 collection_id,1349 nft_id,1685 nft_id,1350 scope,1686 RMRK_SCOPE,1351 resource_id_key.clone(),1687 resource_id_key.clone(),1352 ))1688 ))1353 .ok_or(<Error<T>>::ResourceDoesntExist)?;1689 .ok_or(<Error<T>>::ResourceDoesntExist)?;135416901355 let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;1691 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;135616921357 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1693 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1358 let topmost_owner =1694 let topmost_owner =1363 <PalletNft<T>>::remove_token_aux_property(1699 <PalletNft<T>>::remove_token_aux_property(1364 collection_id,1700 collection_id,1365 nft_id,1701 nft_id,1366 PropertyScope::Rmrk,1702 RMRK_SCOPE,1367 Self::rmrk_property_key(ResourceId(resource_id))?,1703 Self::get_scoped_property_key(ResourceId(resource_id))?,1368 );1704 );136917051370 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1706 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1383 Ok(())1719 Ok(())1384 }1720 }138517211722 /// 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.1386 fn remove_associated_base_id(1724 fn remove_associated_base_id(1387 collection_id: CollectionId,1725 collection_id: CollectionId,1388 nft_id: TokenId,1726 nft_id: TokenId,1391 <PalletNft<T>>::try_mutate_token_aux_property(1729 <PalletNft<T>>::try_mutate_token_aux_property(1392 collection_id,1730 collection_id,1393 nft_id,1731 nft_id,1394 PropertyScope::Rmrk,1732 RMRK_SCOPE,1395 Self::rmrk_property_key(AssociatedBases)?,1733 Self::get_scoped_property_key(AssociatedBases)?,1396 |value| -> DispatchResult {1734 |value| -> DispatchResult {1397 let mut bases: BasesMap = match value {1735 let mut bases: BasesMap = match value {1398 Some(value) => Self::decode_property(value)?,1736 Some(value) => Self::decode_property_value(value)?,1399 None => BasesMap::new(),1737 None => BasesMap::new(),1400 };1738 };140117391407 }1745 }1408 }1746 }140917471410 *value = Some(Self::encode_property(&bases)?);1748 *value = Some(Self::encode_property_value(&bases)?);1411 Ok(())1749 Ok(())1412 },1750 },1413 )1751 )1414 }1752 }141517531754 /// Apply a mutation to a resource stored in the token properties of an NFT.1416 fn try_mutate_resource_info(1755 fn try_mutate_resource_info(1417 collection_id: CollectionId,1756 collection_id: CollectionId,1418 nft_id: TokenId,1757 nft_id: TokenId,1422 <PalletNft<T>>::try_mutate_token_aux_property(1761 <PalletNft<T>>::try_mutate_token_aux_property(1423 collection_id,1762 collection_id,1424 nft_id,1763 nft_id,1425 PropertyScope::Rmrk,1764 RMRK_SCOPE,1426 Self::rmrk_property_key(ResourceId(resource_id))?,1765 Self::get_scoped_property_key(ResourceId(resource_id))?,1427 |value| match value {1766 |value| match value {1428 Some(value) => {1767 Some(value) => {1429 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;1768 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;143017691431 f(&mut resource_info)?;1770 f(&mut resource_info)?;143217711433 *value = Self::encode_property(&resource_info)?;1772 *value = Self::encode_property_value(&resource_info)?;143417731435 Ok(())1774 Ok(())1436 }1775 }1439 )1778 )1440 }1779 }144117801781 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1442 fn change_collection_owner(1782 fn change_collection_owner(1443 collection_id: CollectionId,1783 collection_id: CollectionId,1444 collection_type: misc::CollectionType,1784 collection_type: misc::CollectionType,1454 collection.save()1794 collection.save()1455 }1795 }145617961797 /// Ensure that an account is the collection owner/issuer, return an error if not.1457 pub fn check_collection_owner(1798 pub fn check_collection_owner(1458 collection: &NonfungibleHandle<T>,1799 collection: &NonfungibleHandle<T>,1459 account: &T::CrossAccountId,1800 account: &T::CrossAccountId,1463 .map_err(Self::map_unique_err_to_proxy)1804 .map_err(Self::map_unique_err_to_proxy)1464 }1805 }146518061807 /// Get the latest yet-unused RMRK collection index from the storage.1466 pub fn last_collection_idx() -> RmrkCollectionId {1808 pub fn last_collection_idx() -> RmrkCollectionId {1467 <CollectionIndex<T>>::get()1809 <CollectionIndex<T>>::get()1468 }1810 }146918111812 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1470 pub fn unique_collection_id(1813 pub fn unique_collection_id(1471 rmrk_collection_id: RmrkCollectionId,1814 rmrk_collection_id: RmrkCollectionId,1472 ) -> Result<CollectionId, DispatchError> {1815 ) -> Result<CollectionId, DispatchError> {1473 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1816 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1474 .map_err(|_| <Error<T>>::CollectionUnknown.into())1817 .map_err(|_| <Error<T>>::CollectionUnknown.into())1475 }1818 }147618191820 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1477 pub fn rmrk_collection_id(1821 pub fn rmrk_collection_id(1478 unique_collection_id: CollectionId,1822 unique_collection_id: CollectionId,1479 ) -> Result<RmrkCollectionId, DispatchError> {1823 ) -> Result<RmrkCollectionId, DispatchError> {1480 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1824 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1481 }1825 }148218261827 /// Fetch a Unique NFT collection.1483 pub fn get_nft_collection(1828 pub fn get_nft_collection(1484 collection_id: CollectionId,1829 collection_id: CollectionId,1485 ) -> Result<NonfungibleHandle<T>, DispatchError> {1830 ) -> Result<NonfungibleHandle<T>, DispatchError> {1492 }1837 }1493 }1838 }149418391840 /// Check if an NFT collection with such an ID exists.1495 pub fn collection_exists(collection_id: CollectionId) -> bool {1841 pub fn collection_exists(collection_id: CollectionId) -> bool {1496 <CollectionHandle<T>>::try_get(collection_id).is_ok()1842 <CollectionHandle<T>>::try_get(collection_id).is_ok()1497 }1843 }149818441845 /// Fetch and decode a RMRK-scoped collection property value in bytes.1499 pub fn get_collection_property(1846 pub fn get_collection_property(1500 collection_id: CollectionId,1847 collection_id: CollectionId,1501 key: RmrkProperty,1848 key: RmrkProperty,1502 ) -> Result<PropertyValue, DispatchError> {1849 ) -> Result<PropertyValue, DispatchError> {1503 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1850 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1504 .get(&Self::rmrk_property_key(key)?)1851 .get(&Self::get_scoped_property_key(key)?)1505 .ok_or(<Error<T>>::CollectionUnknown)?1852 .ok_or(<Error<T>>::CollectionUnknown)?1506 .clone();1853 .clone();150718541508 Ok(collection_property)1855 Ok(collection_property)1509 }1856 }151018571858 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1511 pub fn get_collection_property_decoded<V: Decode>(1859 pub fn get_collection_property_decoded<V: Decode>(1512 collection_id: CollectionId,1860 collection_id: CollectionId,1513 key: RmrkProperty,1861 key: RmrkProperty,1514 ) -> Result<V, DispatchError> {1862 ) -> Result<V, DispatchError> {1515 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1863 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1516 }1864 }151718651866 /// 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.1518 pub fn get_collection_type(1869 pub fn get_collection_type(1519 collection_id: CollectionId,1870 collection_id: CollectionId,1520 ) -> Result<misc::CollectionType, DispatchError> {1871 ) -> Result<misc::CollectionType, DispatchError> {1527 })1878 })1528 }1879 }152918801881 /// Ensure that the type of the collection equals the provided type,1882 /// otherwise return an error.1530 pub fn ensure_collection_type(1883 pub fn ensure_collection_type(1531 collection_id: CollectionId,1884 collection_id: CollectionId,1532 collection_type: misc::CollectionType,1885 collection_type: misc::CollectionType,1540 Ok(())1893 Ok(())1541 }1894 }154218951896 /// Fetch an NFT collection, but make sure it has the appropriate type.1543 pub fn get_typed_nft_collection(1897 pub fn get_typed_nft_collection(1544 collection_id: CollectionId,1898 collection_id: CollectionId,1545 collection_type: misc::CollectionType,1899 collection_type: misc::CollectionType,1549 Self::get_nft_collection(collection_id)1903 Self::get_nft_collection(collection_id)1550 }1904 }155119051906 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1907 /// but also return the Unique collection ID.1552 pub fn get_typed_nft_collection_mapped(1908 pub fn get_typed_nft_collection_mapped(1553 rmrk_collection_id: RmrkCollectionId,1909 rmrk_collection_id: RmrkCollectionId,1554 collection_type: misc::CollectionType,1910 collection_type: misc::CollectionType,1563 Ok((collection, unique_collection_id))1919 Ok((collection, unique_collection_id))1564 }1920 }156519211922 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1566 pub fn get_nft_property(1923 pub fn get_nft_property(1567 collection_id: CollectionId,1924 collection_id: CollectionId,1568 nft_id: TokenId,1925 nft_id: TokenId,1569 key: RmrkProperty,1926 key: RmrkProperty,1570 ) -> Result<PropertyValue, DispatchError> {1927 ) -> Result<PropertyValue, DispatchError> {1571 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1928 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1572 .get(&Self::rmrk_property_key(key)?)1929 .get(&Self::get_scoped_property_key(key)?)1573 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1930 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1574 .clone();1931 .clone();157519321576 Ok(nft_property)1933 Ok(nft_property)1577 }1934 }157819351936 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1579 pub fn get_nft_property_decoded<V: Decode>(1937 pub fn get_nft_property_decoded<V: Decode>(1580 collection_id: CollectionId,1938 collection_id: CollectionId,1581 nft_id: TokenId,1939 nft_id: TokenId,1582 key: RmrkProperty,1940 key: RmrkProperty,1583 ) -> Result<V, DispatchError> {1941 ) -> Result<V, DispatchError> {1584 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1942 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1585 }1943 }158619441945 /// Check that an NFT exists.1587 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1946 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1588 <TokenData<T>>::contains_key((collection_id, nft_id))1947 <TokenData<T>>::contains_key((collection_id, nft_id))1589 }1948 }159019491950 /// 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.1591 pub fn get_nft_type(1953 pub fn get_nft_type(1592 collection_id: CollectionId,1954 collection_id: CollectionId,1593 token_id: TokenId,1955 token_id: TokenId,1596 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1958 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1597 }1959 }159819601961 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1599 pub fn ensure_nft_type(1962 pub fn ensure_nft_type(1600 collection_id: CollectionId,1963 collection_id: CollectionId,1601 token_id: TokenId,1964 token_id: TokenId,1607 Ok(())1970 Ok(())1608 }1971 }160919721973 /// 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.1610 pub fn ensure_nft_owner(1975 pub fn ensure_nft_owner(1611 collection_id: CollectionId,1976 collection_id: CollectionId,1612 token_id: TokenId,1977 token_id: TokenId,1627 Ok(())1992 Ok(())1628 }1993 }162919941995 /// 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.1630 pub fn filter_user_properties<Key, Value, R, Mapper>(1997 pub fn filter_user_properties<Key, Value, R, Mapper>(1631 collection_id: CollectionId,1998 collection_id: CollectionId,1632 token_id: Option<TokenId>,1999 token_id: Option<TokenId>,1672 })2039 })1673 }2040 }167420412042 /// Get all non-scoped properties from a collection or a token, and apply some transformation,2043 /// supplied by `mapper`, to each key-value pair.1675 pub fn iterate_user_properties<Key, Value, R, Mapper>(2044 pub fn iterate_user_properties<Key, Value, R, Mapper>(1676 collection_id: CollectionId,2045 collection_id: CollectionId,1677 token_id: Option<TokenId>,2046 token_id: Option<TokenId>,1699 Ok(properties)2068 Ok(properties)1700 }2069 }170120702071 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.1702 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2072 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1703 map_unique_err_to_proxy! {2073 map_unique_err_to_proxy! {1704 match err {2074 match err {pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Miscellaneous helpers and utilities used by the proxy pallet.161817use super::*;19use super::*;18use codec::{Encode, Decode, Error};20use codec::{Encode, Decode, Error};192122/// Match an error to a provided pattern matcher and get23/// the corresponding error of another type if a match is successful.20#[macro_export]24#[macro_export]21macro_rules! map_unique_err_to_proxy {25macro_rules! map_unique_err_to_proxy {22 (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {26 (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {30 };34 };31}35}323633// Utilize the RmrkCore pallet for access to Runtime errors.37/// Interface to decode some serialized bytes into an arbitrary type `T`,38/// preferably if these bytes were originally encoded from `T`.34pub trait RmrkDecode<T: Decode, S> {39pub trait RmrkDecode<T: Decode, S> {40 /// Try to decode self into an arbitrary type `T`.35 fn decode(&self) -> Result<T, Error>;41 fn decode(&self) -> Result<T, Error>;36}42}374343 }49 }44}50}455146// Utilize the RmrkCore pallet for access to Runtime errors.52/// Interface to "rebind" - change the limit of a bounded byte vector.47pub trait RmrkRebind<T, S> {53pub trait RmrkRebind<T, S> {54 /// Try to change the limit of a bounded byte vector.48 fn rebind(&self) -> Result<BoundedVec<u8, S>, Error>;55 fn rebind(&self) -> Result<BoundedVec<u8, S>, Error>;49}56}505758 }65 }59}66}606768/// RMRK Base shares functionality with a regular collection, and is thus69/// stored as one, but they are used for different purposes and need to be differentiated.61#[derive(Encode, Decode, PartialEq, Eq)]70#[derive(Encode, Decode, PartialEq, Eq)]62pub enum CollectionType {71pub enum CollectionType {63 Regular,72 Regular,64 Base,73 Base,65}74}667576/// RMRK Base, being stored as a collection, can have different kinds of tokens,77/// all except the `Regular` type, which is attributed to `Regular` collection.67#[derive(Encode, Decode, PartialEq, Eq)]78#[derive(Encode, Decode, PartialEq, Eq)]68pub enum NftType {79pub enum NftType {69 Regular,80 Regular,pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Details of storing and handling RMRK properties.161817use super::*;19use super::*;18use up_data_structs::PropertyScope;20use up_data_structs::PropertyScope;19use core::convert::AsRef;21use core::convert::AsRef;202223/// Property prefix for storing resources.21pub const RESOURCE_ID_PREFIX: &str = "rsid-";24pub const RESOURCE_ID_PREFIX: &str = "rsid-";25/// Property prefix for storing custom user-defined properties.22pub const USER_PROPERTY_PREFIX: &str = "userprop-";26pub const USER_PROPERTY_PREFIX: &str = "userprop-";2327/// Property scope for RMRK, used to signify that this property28/// was created and is used by RMRK.29pub const RMRK_SCOPE: PropertyScope = PropertyScope::Rmrk;3031/// Predefined RMRK property keys for storage of RMRK data format on the Unique chain.24pub enum RmrkProperty<'r> {32pub enum RmrkProperty<'r> {25 Metadata,33 Metadata,26 CollectionType,34 CollectionType,49}57}505851impl<'r> RmrkProperty<'r> {59impl<'r> RmrkProperty<'r> {60 /// Convert a predefined RMRK property key enum into string bytes.52 pub fn to_key<T: Config>(self) -> Result<PropertyKey, Error<T>> {61 pub fn to_key<T: Config>(self) -> Result<PropertyKey, Error<T>> {53 fn get_bytes<T: AsRef<[u8]>>(container: &T) -> &[u8] {62 fn get_bytes<T: AsRef<[u8]>>(container: &T) -> &[u8] {54 container.as_ref()63 container.as_ref()94 }103 }95}104}96105106/// Strip a property key of its prefix and RMRK scope.97pub fn strip_key_prefix(key: &PropertyKey, prefix: &str) -> Option<PropertyKey> {107pub fn strip_key_prefix(key: &PropertyKey, prefix: &str) -> Option<PropertyKey> {98 let key_prefix = PropertyKey::try_from(prefix.as_bytes().to_vec()).ok()?;108 let key_prefix = PropertyKey::try_from(prefix.as_bytes().to_vec()).ok()?;99 let key_prefix = PropertyScope::Rmrk.apply(key_prefix).ok()?;109 let key_prefix = RMRK_SCOPE.apply(key_prefix).ok()?;100110101 key.as_slice()111 key.as_slice()102 .strip_prefix(key_prefix.as_slice())?112 .strip_prefix(key_prefix.as_slice())?105 .ok()115 .ok()106}116}107117118/// Check that the key has the prefix.108pub fn is_valid_key_prefix(key: &PropertyKey, prefix: &str) -> bool {119pub fn is_valid_key_prefix(key: &PropertyKey, prefix: &str) -> bool {109 strip_key_prefix(key, prefix).is_some()120 strip_key_prefix(key, prefix).is_some()110}121}pallets/proxy-rmrk-core/src/rpc.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//! Realizations of RMRK RPCs (remote procedure calls) related to the Core pallet.181use super::*;19use super::*;22021/// Get the latest created collection ID.3pub fn last_collection_idx<T: Config>() -> Result<RmrkCollectionId, DispatchError> {22pub fn last_collection_idx<T: Config>() -> Result<RmrkCollectionId, DispatchError> {4 Ok(<Pallet<T>>::last_collection_idx())23 Ok(<Pallet<T>>::last_collection_idx())5}24}62526/// Get collection info by ID.7pub fn collection_by_id<T: Config>(27pub fn collection_by_id<T: Config>(8 collection_id: RmrkCollectionId,28 collection_id: RmrkCollectionId,9) -> Result<Option<RmrkCollectionInfo<T::AccountId>>, DispatchError> {29) -> Result<Option<RmrkCollectionInfo<T::AccountId>>, DispatchError> {29 }))49 }))30}50}315152/// Get NFT info by collection and NFT IDs.32pub fn nft_by_id<T: Config>(53pub fn nft_by_id<T: Config>(33 collection_id: RmrkCollectionId,54 collection_id: RmrkCollectionId,34 nft_by_id: RmrkNftId,55 nft_by_id: RmrkNftId,83 }))104 }))84}105}85106107/// Get tokens owned by an account in a collection.86pub fn account_tokens<T: Config>(108pub fn account_tokens<T: Config>(87 account_id: T::AccountId,109 account_id: T::AccountId,88 collection_id: RmrkCollectionId,110 collection_id: RmrkCollectionId,116 Ok(tokens)138 Ok(tokens)117}139}118140141/// Get tokens nested in an NFT - its direct children (not the children's children).119pub fn nft_children<T: Config>(142pub fn nft_children<T: Config>(120 collection_id: RmrkCollectionId,143 collection_id: RmrkCollectionId,121 nft_id: RmrkNftId,144 nft_id: RmrkNftId,152 )175 )153}176}154177178/// Get collection properties, created by the user - not the proxy-specific properties.155pub fn collection_properties<T: Config>(179pub fn collection_properties<T: Config>(156 collection_id: RmrkCollectionId,180 collection_id: RmrkCollectionId,157 filter_keys: Option<Vec<RmrkPropertyKey>>,181 filter_keys: Option<Vec<RmrkPropertyKey>>,174 Ok(properties)198 Ok(properties)175}199}176200201/// Get NFT properties, created by the user - not the proxy-specific properties.177pub fn nft_properties<T: Config>(202pub fn nft_properties<T: Config>(178 collection_id: RmrkCollectionId,203 collection_id: RmrkCollectionId,179 nft_id: RmrkNftId,204 nft_id: RmrkNftId,199 Ok(properties)224 Ok(properties)200}225}201226227/// Get full information on each resource of an NFT, including pending.202pub fn nft_resources<T: Config>(228pub fn nft_resources<T: Config>(203 collection_id: RmrkCollectionId,229 collection_id: RmrkCollectionId,204 nft_id: RmrkNftId,230 nft_id: RmrkNftId,226 return None;252 return None;227 }253 }228254229 let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property(&value).ok()?;255 let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property_value(&value).ok()?;230256231 Some(resource_info)257 Some(resource_info)232 })258 })235 Ok(resources)261 Ok(resources)236}262}237263264/// Get the priority of a resource in an NFT.238pub fn nft_resource_priority<T: Config>(265pub fn nft_resource_priority<T: Config>(239 collection_id: RmrkCollectionId,266 collection_id: RmrkCollectionId,240 nft_id: RmrkNftId,267 nft_id: RmrkNftId,pallets/proxy-rmrk-equip/src/benchmarking.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/>.161use sp_std::vec;17use sp_std::vec;218pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// 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 Equip Proxy pallet mirrors the functionality of RMRK Equip,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Equip exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Equip itself contains functionality to equip NFTs, and work with Bases,33//! Parts, and Themes. See [Proxy Implementation](#proxy-implementation) for details.34//!35//! Equip Proxy is responsible for a more specific area of RMRK, and heavily relies on the Core.36//! For a more foundational description of proxy implementation, please refer to [`pallet_rmrk_core`].37//!38//! *Note*, that while RMRK itself is subject to active development and restructuring,39//! the proxy may be caught temporarily out of date.40//!41//! ### What is RMRK?42//!43//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.44//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.45//!46//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,47//! make use of specific changeable and partially shared metadata in the form of resources,48//! and more.49//!50//! Visit RMRK documentation and repositories to learn more:51//! - Docs: <https://docs.rmrk.app/getting-started/>52//! - FAQ: <https://coda.io/@rmrk/faq>53//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>54//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>55//!56//! ## Terminology57//!58//! For more information on RMRK, see RMRK's own documentation.59//!60//! ### Intro to RMRK61//!62//! - **Resource:** Additional piece of metadata of an NFT usually serving to add63//! a piece of media on top of the root metadata (NFT's own), be it a different wing64//! on the root template bird or something entirely unrelated.65//!66//! - **Base:** A list of possible "components" - Parts, a combination of which can67//! be appended/equipped to/on an NFT.68//!69//! - **Part:** Something that, together with other Parts, can constitute an NFT.70//! Parts are defined in the Base to which they belong. Parts can be either71//! of the `slot` type or `fixed` type. Slots are intended for equippables.72//! Note that "part of something" and "Part of a Base" can be easily confused,73//! and so in this documentation these words are distinguished by the capital letter.74//!75//! - **Theme:** Named objects of variable => value pairs which get interpolated into76//! the Base's `themable` Parts. Themes can hold any value, but are often represented77//! in RMRK's examples as colors applied to visible Parts.78//!79//! ### Peculiarities in Unique80//!81//! - **Scoped properties:** Properties that are normally obscured from users.82//! Their purpose is to contain structured metadata that was not included in the Unique standard83//! for collections and tokens, meant to be operated on by proxies and other outliers.84//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is85//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined86//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.87//!88//! - **Auxiliary properties:** A slightly different structure of properties,89//! trading universality of use for more convenient storage, writes and access.90//! Meant to be inaccessible to end users.91//!92//! ## Proxy Implementation93//!94//! An external user is supposed to be able to utilize this proxy as they would95//! utilize RMRK, and get exactly the same results. Normally, Unique transactions96//! are off-limits to RMRK collections and tokens, and vice versa. However,97//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.98//!99//! ### ID Mapping100//!101//! RMRK's collections' IDs are counted independently of Unique's and start at 0.102//! Note that tokens' IDs still start at 1.103//! The collections themselves, as well as tokens, are stored as Unique collections,104//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).105//!106//! ### External/Internal Collection Insulation107//!108//! A Unique transaction cannot target collections purposed for RMRK,109//! and they are flagged as `external` to specify that. On the other hand,110//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.111//!112//! ### Native Properties113//!114//! Many of RMRK's native parameters are stored as scoped properties of a collection115//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`116//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,117//! makes them impossible to tamper with.118//!119//! ### Collection and NFT Types, or Base, Parts and Themes Handling120//!121//! RMRK introduces the concept of a Base, which is a catalogue of Parts,122//! possible components of an NFT. Due to its similarity with the functionality123//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes124//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].125//!126//! ## Interface127//!128//! ### Dispatchables129//!130//! - `create_base` - Create a new Base.131//! - `theme_add` - Add a Theme to a Base.132//! - `equippable` - Update the array of Collections allowed to be equipped to a Base's specified Slot Part.1613317#![cfg_attr(not(feature = "std"), no_std)]134#![cfg_attr(not(feature = "std"), no_std)]181354516246 #[pallet::config]163 #[pallet::config]47 pub trait Config: frame_system::Config + pallet_rmrk_core::Config {164 pub trait Config: frame_system::Config + pallet_rmrk_core::Config {165 /// Overarching event type.48 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;166 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;167168 /// The weight information of this pallet.49 type WeightInfo: WeightInfo;169 type WeightInfo: WeightInfo;50 }170 }51171172 /// Map of a Base ID and a Part ID to an NFT in the Base collection serving as the Part.52 #[pallet::storage]173 #[pallet::storage]53 #[pallet::getter(fn internal_part_id)]174 #[pallet::getter(fn internal_part_id)]54 pub type InernalPartId<T: Config> =175 pub type InernalPartId<T: Config> =55 StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;176 StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;56177178 /// Checkmark that a Base has a Theme NFT named "default".57 #[pallet::storage]179 #[pallet::storage]58 #[pallet::getter(fn base_has_default_theme)]180 #[pallet::getter(fn base_has_default_theme)]59 pub type BaseHasDefaultTheme<T: Config> =181 pub type BaseHasDefaultTheme<T: Config> =7820079 #[pallet::error]201 #[pallet::error]80 pub enum Error<T> {202 pub enum Error<T> {203 /// No permission to perform action.81 PermissionError,204 PermissionError,205 /// Could not find an ID for a Base collection. It is likely there were too many collections created on the chain, causing an overflow.82 NoAvailableBaseId,206 NoAvailableBaseId,207 /// Could not find a suitable ID for a Part, likely too many Part tokens were created in the Base, causing an overflow83 NoAvailablePartId,208 NoAvailablePartId,209 /// Base collection linked to this ID does not exist.84 BaseDoesntExist,210 BaseDoesntExist,211 /// No Theme named "default" is associated with the Base.85 NeedsDefaultThemeFirst,212 NeedsDefaultThemeFirst,213 /// Part linked to this ID does not exist.86 PartDoesntExist,214 PartDoesntExist,215 /// Cannot assign equippables to a fixed Part.87 NoEquippableOnFixedPart,216 NoEquippableOnFixedPart,88 }217 }8921890 #[pallet::call]219 #[pallet::call]91 impl<T: Config> Pallet<T> {220 impl<T: Config> Pallet<T> {92 /// Creates a new Base.221 /// Create a new Base.222 ///93 /// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)223 /// Modeled after the [Base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)94 ///224 ///95 /// Parameters:225 /// # Permissions96 /// - origin: Caller, will be assigned as the issuer of the Base226 /// - Anyone - will be assigned as the issuer of the Base.227 ///228 /// # Arguments:97 /// - base_type: media type, e.g. "svg"229 /// - `base_type`: Arbitrary media type, e.g. "svg".98 /// - symbol: arbitrary client-chosen symbol230 /// - `symbol`: Arbitrary client-chosen symbol.99 /// - parts: array of Fixed and Slot parts composing the base, confined in length by231 /// - `parts`: Array of Fixed and Slot Parts composing the Base,100 /// RmrkPartsLimit232 /// confined in length by [`RmrkPartsLimit`](up_data_structs::RmrkPartsLimit).101 #[transactional]233 #[transactional]102 #[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]234 #[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]103 pub fn create_base(235 pub fn create_base(131 collection_id,263 collection_id,132 PropertyScope::Rmrk,264 PropertyScope::Rmrk,133 [265 [134 <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,266 <PalletCore<T>>::encode_rmrk_property(267 CollectionType,268 &misc::CollectionType::Base,269 )?,135 <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,270 <PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,136 ]271 ]137 .into_iter(),272 .into_iter(),138 )?;273 )?;151 Ok(())286 Ok(())152 }287 }153288154 /// Adds a Theme to a Base.289 /// Add a Theme to a Base.155 /// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)156 /// Themes are stored in the Themes storage157 /// A Theme named "default" is required prior to adding other Themes.290 /// A Theme named "default" is required prior to adding other Themes.291 ///292 /// Modeled after [Themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md).158 ///293 ///159 /// Parameters:294 /// # Permissions:160 /// - origin: The caller of the function, must be issuer of the base295 /// - Base issuer296 ///297 /// # Arguments:161 /// - base_id: The Base containing the Theme to be updated298 /// - `base_id`: Base ID containing the Theme to be updated.162 /// - theme: The Theme to add to the Base. A Theme has a name and properties, which are an299 /// - `theme`: Theme to add to the Base. A Theme has a name and properties, which are an163 /// array of [key, value, inherit].300 /// array of [key, value, inherit].164 /// - key: arbitrary BoundedString, defined by client301 /// - `key`: Arbitrary BoundedString, defined by client.165 /// - value: arbitrary BoundedString, defined by client302 /// - `value`: Arbitrary BoundedString, defined by client.166 /// - inherit: optional bool303 /// - `inherit`: Optional bool.167 #[transactional]304 #[transactional]168 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]305 #[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]169 pub fn theme_add(306 pub fn theme_add(191 owner,328 owner,192 &collection,329 &collection,193 [330 [194 <PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,331 <PalletCore<T>>::encode_rmrk_property(TokenType, &NftType::Theme)?,195 <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,332 <PalletCore<T>>::encode_rmrk_property(ThemeName, &theme.name)?,196 <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,333 <PalletCore<T>>::encode_rmrk_property(ThemeInherit, &theme.inherit)?,197 ]334 ]198 .into_iter(),335 .into_iter(),199 )336 )204 collection_id,341 collection_id,205 token_id,342 token_id,206 PropertyScope::Rmrk,343 PropertyScope::Rmrk,207 <PalletCore<T>>::rmrk_property(344 <PalletCore<T>>::encode_rmrk_property(208 UserProperty(property.key.as_slice()),345 UserProperty(property.key.as_slice()),209 &property.value,346 &property.value,210 )?,347 )?,214 Ok(())351 Ok(())215 }352 }216353354 /// Update the array of Collections allowed to be equipped to a Base's specified Slot Part.355 ///356 /// Modeled after [equippable interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/equippable.md).357 ///358 /// # Permissions:359 /// - Base issuer360 ///361 /// # Arguments:362 /// - `base_id`: Base containing the Slot Part to be updated.363 /// - `part_id`: Slot Part whose Equippable List is being updated.364 /// - `equippables`: List of equippables that will override the current Equippables list.217 #[transactional]365 #[transactional]218 #[pallet::weight(<SelfWeightOf<T>>::equippable())]366 #[pallet::weight(<SelfWeightOf<T>>::equippable())]219 pub fn equippable(367 pub fn equippable(253 base_collection_id,401 base_collection_id,254 part_id,402 part_id,255 PropertyScope::Rmrk,403 PropertyScope::Rmrk,256 <PalletCore<T>>::rmrk_property(EquippableList, &equippables)?,404 <PalletCore<T>>::encode_rmrk_property(EquippableList, &equippables)?,257 )?;405 )?;258 }406 }259 }407 }266}414}267415268impl<T: Config> Pallet<T> {416impl<T: Config> Pallet<T> {417 /// Create (or overwrite) a Part in a Base.418 /// The Part and the Base are represented as an NFT and a Collection.269 fn create_part(419 fn create_part(270 sender: &T::CrossAccountId,420 sender: &T::CrossAccountId,271 collection: &NonfungibleHandle<T>,421 collection: &NonfungibleHandle<T>,298 collection.id,448 collection.id,299 token_id,449 token_id,300 PropertyScope::Rmrk,450 PropertyScope::Rmrk,301 <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,451 <PalletCore<T>>::encode_rmrk_property(ExternalPartId, &part_id)?,302 )?;452 )?;303453304 token_id454 token_id310 token_id,460 token_id,311 PropertyScope::Rmrk,461 PropertyScope::Rmrk,312 [462 [313 <PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,463 <PalletCore<T>>::encode_rmrk_property(TokenType, &nft_type)?,314 <PalletCore<T>>::rmrk_property(Src, &src)?,464 <PalletCore<T>>::encode_rmrk_property(Src, &src)?,315 <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,465 <PalletCore<T>>::encode_rmrk_property(ZIndex, &z_index)?,316 ]466 ]317 .into_iter(),467 .into_iter(),318 )?;468 )?;322 collection.id,472 collection.id,323 token_id,473 token_id,324 PropertyScope::Rmrk,474 PropertyScope::Rmrk,325 <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,475 <PalletCore<T>>::encode_rmrk_property(EquippableList, &part.equippable)?,326 )?;476 )?;327 }477 }328478329 Ok(())479 Ok(())330 }480 }331481482 /// Ensure that the collection under the Base ID is a Base collection,483 /// and fetch it.332 fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {484 fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {333 let collection =485 let collection =334 <PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)486 <PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)pallets/proxy-rmrk-equip/src/rpc.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//! Realizations of RMRK RPCs (remote procedure calls) related to the Equip pallet.181use super::*;19use super::*;2use pallet_rmrk_core::{misc, property::*};20use pallet_rmrk_core::{misc, property::*};3use sp_std::vec::Vec;21use sp_std::vec::Vec;42223/// Get base info by its ID.5pub fn base<T: Config>(24pub fn base<T: Config>(6 base_id: RmrkBaseId,25 base_id: RmrkBaseId,7) -> Result<Option<RmrkBaseInfo<T::AccountId>>, DispatchError> {26) -> Result<Option<RmrkBaseInfo<T::AccountId>>, DispatchError> {22 }))41 }))23}42}244344/// Get all parts of a base.25pub fn base_parts<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {45pub fn base_parts<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {26 use pallet_common::CommonCollectionOperations;46 use pallet_common::CommonCollectionOperations;274793 Ok(parts)113 Ok(parts)94}114}95115116/// Get the theme names belonging to a base.96pub fn theme_names<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {117pub fn theme_names<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {97 use pallet_common::CommonCollectionOperations;118 use pallet_common::CommonCollectionOperations;98119124 Ok(theme_names)145 Ok(theme_names)125}146}126147148/// Get theme info, including properties, optionally limited to the provided keys.127pub fn theme<T: Config>(149pub fn theme<T: Config>(128 base_id: RmrkBaseId,150 base_id: RmrkBaseId,129 theme_name: RmrkThemeName,151 theme_name: RmrkThemeName,primitives/rmrk-traits/src/resource.rsdiffbeforeafterboth151 "#)151 "#)152)]152)]153pub struct ResourceInfo<BoundedString, BoundedParts> {153pub struct ResourceInfo<BoundedString, BoundedParts> {154 /// id is a 5-character string of reasonable uniqueness.154 /// ID is a unique identifier for a resource across all those of a single NFT.155 /// The combination of base ID and resource id should be unique across the entire RMRK155 /// The combination of a collection ID, an NFT ID, and the resource ID must be156 /// ecosystem which156 /// unique across the entire RMRK ecosystem.157 //#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]157 //#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]158 pub id: ResourceId,158 pub id: ResourceId,159159160 /// Resource160 /// Resource type and the accordingly structured data stored161 pub resource: ResourceTypes<BoundedString, BoundedParts>,161 pub resource: ResourceTypes<BoundedString, BoundedParts>,162162163 /// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted163 /// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted