difftreelog
fix gramar
in: master
5 files changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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::*, 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::*;193194 #[pallet::config]195 pub trait Config:196 frame_system::Config197 + pallet_common::Config198 + pallet_nonfungible::Config199 + pallet_evm::Config200 {201 /// Overarching event type.202 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;203204 /// The weight information of this pallet.205 type WeightInfo: WeightInfo;206 }207208 /// Latest yet-unused collection ID.209 #[pallet::storage]210 #[pallet::getter(fn collection_index)]211 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;212213 /// Mapping from RMRK collection ID to Unique's.214 #[pallet::storage]215 pub type UniqueCollectionId<T: Config> =216 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;217218 #[pallet::pallet]219 #[pallet::generate_store(pub(super) trait Store)]220 pub struct Pallet<T>(_);221222 #[pallet::event]223 #[pallet::generate_deposit(pub(super) fn deposit_event)]224 pub enum Event<T: Config> {225 CollectionCreated {226 issuer: T::AccountId,227 collection_id: RmrkCollectionId,228 },229 CollectionDestroyed {230 issuer: T::AccountId,231 collection_id: RmrkCollectionId,232 },233 IssuerChanged {234 old_issuer: T::AccountId,235 new_issuer: T::AccountId,236 collection_id: RmrkCollectionId,237 },238 CollectionLocked {239 issuer: T::AccountId,240 collection_id: RmrkCollectionId,241 },242 NftMinted {243 owner: T::AccountId,244 collection_id: RmrkCollectionId,245 nft_id: RmrkNftId,246 },247 NFTBurned {248 owner: T::AccountId,249 nft_id: RmrkNftId,250 },251 NFTSent {252 sender: T::AccountId,253 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,254 collection_id: RmrkCollectionId,255 nft_id: RmrkNftId,256 approval_required: bool,257 },258 NFTAccepted {259 sender: T::AccountId,260 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,261 collection_id: RmrkCollectionId,262 nft_id: RmrkNftId,263 },264 NFTRejected {265 sender: T::AccountId,266 collection_id: RmrkCollectionId,267 nft_id: RmrkNftId,268 },269 PropertySet {270 collection_id: RmrkCollectionId,271 maybe_nft_id: Option<RmrkNftId>,272 key: RmrkKeyString,273 value: RmrkValueString,274 },275 ResourceAdded {276 nft_id: RmrkNftId,277 resource_id: RmrkResourceId,278 },279 ResourceRemoval {280 nft_id: RmrkNftId,281 resource_id: RmrkResourceId,282 },283 ResourceAccepted {284 nft_id: RmrkNftId,285 resource_id: RmrkResourceId,286 },287 ResourceRemovalAccepted {288 nft_id: RmrkNftId,289 resource_id: RmrkResourceId,290 },291 PrioritySet {292 collection_id: RmrkCollectionId,293 nft_id: RmrkNftId,294 },295 }296297 #[pallet::error]298 pub enum Error<T> {299 /* Unique proxy-specific events */300 /// Property of the type of RMRK collection could not be read successfully.301 CorruptedCollectionType,302 // NftTypeEncodeError,303 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).304 RmrkPropertyKeyIsTooLong,305 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).306 RmrkPropertyValueIsTooLong,307 /// Could not find a property by the supplied key.308 RmrkPropertyIsNotFound,309 /// Something went wrong when decoding encoded data from the storage.310 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.311 UnableToDecodeRmrkData,312313 /* RMRK compatible events */314 /// Only destroying collections without tokens is allowed.315 CollectionNotEmpty,316 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.317 NoAvailableCollectionId,318 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.319 NoAvailableNftId,320 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.321 CollectionUnknown,322 /// No permission to perform action.323 NoPermission,324 /// Token is marked as non-transferable, and thus cannot be transferred.325 NonTransferable,326 /// Too many tokens created in the collection, no new ones are allowed.327 CollectionFullOrLocked,328 /// No such resource found.329 ResourceDoesntExist,330 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.331 /// Sending to self is redundant.332 CannotSendToDescendentOrSelf,333 /// Not the target owner of the sent NFT.334 CannotAcceptNonOwnedNft,335 /// Not the target owner of the sent NFT.336 CannotRejectNonOwnedNft,337 /// NFT was not sent and is not pending.338 CannotRejectNonPendingNft,339 /// Resource is not pending for the operation.340 ResourceNotPending,341 /// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.342 NoAvailableResourceId,343 }344345 #[pallet::call]346 impl<T: Config> Pallet<T> {347 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?348349 /// Create a new collection of NFTs.350 ///351 /// # Permissions:352 /// * Anyone - will be assigned as the issuer of the collection.353 ///354 /// # Arguments:355 /// - `origin`: sender of the transaction356 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.357 /// - `max`: Optional maximum number of tokens.358 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.359 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.360 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]361 pub fn create_collection(362 origin: OriginFor<T>,363 metadata: RmrkString,364 max: Option<u32>,365 symbol: RmrkCollectionSymbol,366 ) -> DispatchResult {367 let sender = ensure_signed(origin)?;368369 let limits = CollectionLimits {370 owner_can_transfer: Some(false),371 token_limit: max,372 ..Default::default()373 };374375 let data = CreateCollectionData {376 limits: Some(limits),377 token_prefix: symbol378 .into_inner()379 .try_into()380 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,381 permissions: Some(CollectionPermissions {382 nesting: Some(NestingPermissions {383 token_owner: true,384 collection_admin: false,385 restricted: None,386 #[cfg(feature = "runtime-benchmarks")]387 permissive: false,388 }),389 ..Default::default()390 }),391 ..Default::default()392 };393394 let unique_collection_id = Self::init_collection(395 T::CrossAccountId::from_sub(sender.clone()),396 data,397 [398 Self::encode_rmrk_property(Metadata, &metadata)?,399 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,400 ]401 .into_iter(),402 )?;403 let rmrk_collection_id = <CollectionIndex<T>>::get();404405 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);406407 <PalletCommon<T>>::set_scoped_collection_property(408 unique_collection_id,409 RMRK_SCOPE,410 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,411 )?;412413 <CollectionIndex<T>>::mutate(|n| *n += 1);414415 Self::deposit_event(Event::CollectionCreated {416 issuer: sender,417 collection_id: rmrk_collection_id,418 });419420 Ok(())421 }422423 /// Destroy a collection.424 ///425 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.426 ///427 /// # Permissions:428 /// * Collection issuer429 ///430 /// # Arguments:431 /// - `origin`: sender of the transaction432 /// - `collection_id`: RMRK ID of the collection to destroy.433 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]434 pub fn destroy_collection(435 origin: OriginFor<T>,436 collection_id: RmrkCollectionId,437 ) -> DispatchResult {438 let sender = ensure_signed(origin)?;439 let cross_sender = T::CrossAccountId::from_sub(sender.clone());440441 let collection = Self::get_typed_nft_collection(442 Self::unique_collection_id(collection_id)?,443 misc::CollectionType::Regular,444 )?;445 collection.check_is_external()?;446447 <PalletNft<T>>::destroy_collection(collection, &cross_sender)448 .map_err(Self::map_unique_err_to_proxy)?;449450 Self::deposit_event(Event::CollectionDestroyed {451 issuer: sender,452 collection_id,453 });454455 Ok(())456 }457458 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).459 ///460 /// # Permissions:461 /// * Collection issuer462 ///463 /// # Arguments:464 /// - `origin`: sender of the transaction465 /// - `collection_id`: RMRK collection ID to change the issuer of.466 /// - `new_issuer`: Collection's new issuer.467 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]468 pub fn change_collection_issuer(469 origin: OriginFor<T>,470 collection_id: RmrkCollectionId,471 new_issuer: <T::Lookup as StaticLookup>::Source,472 ) -> DispatchResult {473 let sender = ensure_signed(origin)?;474475 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;476 collection.check_is_external()?;477478 let new_issuer = T::Lookup::lookup(new_issuer)?;479480 Self::change_collection_owner(481 Self::unique_collection_id(collection_id)?,482 misc::CollectionType::Regular,483 sender.clone(),484 new_issuer.clone(),485 )?;486487 Self::deposit_event(Event::IssuerChanged {488 old_issuer: sender,489 new_issuer,490 collection_id,491 });492493 Ok(())494 }495496 /// "Lock" the collection and prevent new token creation. Cannot be undone.497 ///498 /// # Permissions:499 /// * Collection issuer500 ///501 /// # Arguments:502 /// - `origin`: sender of the transaction503 /// - `collection_id`: RMRK ID of the collection to lock.504 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]505 pub fn lock_collection(506 origin: OriginFor<T>,507 collection_id: RmrkCollectionId,508 ) -> DispatchResult {509 let sender = ensure_signed(origin)?;510 let cross_sender = T::CrossAccountId::from_sub(sender.clone());511512 let collection = Self::get_typed_nft_collection(513 Self::unique_collection_id(collection_id)?,514 misc::CollectionType::Regular,515 )?;516 collection.check_is_external()?;517518 Self::check_collection_owner(&collection, &cross_sender)?;519520 let token_count = collection.total_supply();521522 let mut collection = collection.into_inner();523 collection.limits.token_limit = Some(token_count);524 collection.save()?;525526 Self::deposit_event(Event::CollectionLocked {527 issuer: sender,528 collection_id,529 });530531 Ok(())532 }533534 /// Mint an NFT in a specified collection.535 ///536 /// # Permissions:537 /// * Collection issuer538 ///539 /// # Arguments:540 /// - `origin`: sender of the transaction541 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).542 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.543 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.544 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.545 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.546 /// - `transferable`: Can this NFT be transferred? Cannot be changed.547 /// - `resources`: Resource data to be added to the NFT immediately after minting.548 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]549 pub fn mint_nft(550 origin: OriginFor<T>,551 owner: Option<T::AccountId>,552 collection_id: RmrkCollectionId,553 recipient: Option<T::AccountId>,554 royalty_amount: Option<Permill>,555 metadata: RmrkString,556 transferable: bool,557 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,558 ) -> DispatchResult {559 let sender = ensure_signed(origin)?;560 let cross_sender = T::CrossAccountId::from_sub(sender.clone());561562 let owner = owner.unwrap_or(sender.clone());563 let cross_owner = T::CrossAccountId::from_sub(owner.clone());564565 let collection = Self::get_typed_nft_collection(566 Self::unique_collection_id(collection_id)?,567 misc::CollectionType::Regular,568 )?;569 collection.check_is_external()?;570571 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {572 recipient: recipient.unwrap_or_else(|| owner.clone()),573 amount,574 });575576 let nft_id = Self::create_nft(577 &cross_sender,578 &cross_owner,579 &collection,580 [581 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,582 Self::encode_rmrk_property(Transferable, &transferable)?,583 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,584 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,585 Self::encode_rmrk_property(Metadata, &metadata)?,586 Self::encode_rmrk_property(Equipped, &false)?,587 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,588 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,589 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,590 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,591 ]592 .into_iter(),593 )594 .map_err(|err| match err {595 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),596 err => Self::map_unique_err_to_proxy(err),597 })?;598599 if let Some(resources) = resources {600 for resource in resources {601 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;602 }603 }604605 Self::deposit_event(Event::NftMinted {606 owner,607 collection_id,608 nft_id: nft_id.0,609 });610611 Ok(())612 }613614 /// Burn an NFT, destroying it and its nested tokens up to the specified limit.615 /// If the burning budget is exceeded, the transaction is reverted.616 ///617 /// This is the way to burn a nested token as well.618 ///619 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).620 ///621 /// # Permissions:622 /// * Token owner623 ///624 /// # Arguments:625 /// - `origin`: sender of the transaction626 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.627 /// - `nft_id`: ID of the NFT to be destroyed.628 /// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction629 /// is reverted if there are more tokens to burn in the nesting tree than this number.630 /// This is primarily a mechanism of transaction weight control.631 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]632 pub fn burn_nft(633 origin: OriginFor<T>,634 collection_id: RmrkCollectionId,635 nft_id: RmrkNftId,636 max_burns: u32,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection = Self::get_typed_nft_collection(642 Self::unique_collection_id(collection_id)?,643 misc::CollectionType::Regular,644 )?;645 collection.check_is_external()?;646647 Self::destroy_nft(648 cross_sender,649 Self::unique_collection_id(collection_id)?,650 nft_id.into(),651 max_burns,652 <Error<T>>::NoPermission,653 )654 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;655656 Self::deposit_event(Event::NFTBurned {657 owner: sender,658 nft_id,659 });660661 Ok(())662 }663664 /// Transfer an NFT from an account/NFT A to another account/NFT B.665 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].666 ///667 /// If the target owner is an NFT owned by another account, then the NFT will enter668 /// the pending state and will have to be accepted by the other account.669 ///670 /// # Permissions:671 /// - Token owner672 ///673 /// # Arguments:674 /// - `origin`: sender of the transaction675 /// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.676 /// - `rmrk_nft_id`: ID of the NFT to be transferred.677 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.678 #[pallet::weight(<SelfWeightOf<T>>::send())]679 pub fn send(680 origin: OriginFor<T>,681 rmrk_collection_id: RmrkCollectionId,682 rmrk_nft_id: RmrkNftId,683 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,684 ) -> DispatchResult {685 let sender = ensure_signed(origin.clone())?;686 let cross_sender = T::CrossAccountId::from_sub(sender.clone());687688 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;689 let nft_id = rmrk_nft_id.into();690691 let collection =692 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;693 collection.check_is_external()?;694695 let token_data =696 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;697698 let from = token_data.owner;699700 ensure!(701 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,702 <Error<T>>::NonTransferable703 );704705 ensure!(706 Self::get_nft_property_decoded::<Option<PendingTarget>>(707 collection_id,708 nft_id,709 RmrkProperty::PendingNftAccept710 )?711 .is_none(),712 <Error<T>>::NoPermission713 );714715 let target_owner;716 let approval_required;717718 match new_owner {719 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {720 target_owner = T::CrossAccountId::from_sub(account_id.clone());721 approval_required = false;722 }723 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(724 target_collection_id,725 target_nft_id,726 ) => {727 let target_collection_id = Self::unique_collection_id(target_collection_id)?;728729 let target_nft_budget = budget::Value::new(NESTING_BUDGET);730731 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(732 target_collection_id,733 target_nft_id.into(),734 Some((collection_id, nft_id)),735 &target_nft_budget,736 )737 .map_err(Self::map_unique_err_to_proxy)?;738739 approval_required = cross_sender != target_nft_owner;740741 if approval_required {742 target_owner = target_nft_owner;743744 <PalletNft<T>>::set_scoped_token_property(745 collection.id,746 nft_id,747 RMRK_SCOPE,748 Self::encode_rmrk_property::<Option<PendingTarget>>(749 PendingNftAccept,750 &Some((target_collection_id, target_nft_id.into())),751 )?,752 )?;753754 Self::insert_pending_child(755 (target_collection_id, target_nft_id.into()),756 (rmrk_collection_id, rmrk_nft_id),757 )?;758 } else {759 target_owner = T::CrossTokenAddressMapping::token_to_address(760 target_collection_id,761 target_nft_id.into(),762 );763 }764 }765 }766767 let src_nft_budget = budget::Value::new(NESTING_BUDGET);768769 <PalletNft<T>>::transfer_from(770 &collection,771 &cross_sender,772 &from,773 &target_owner,774 nft_id,775 &src_nft_budget,776 )777 .map_err(Self::map_unique_err_to_proxy)?;778779 Self::deposit_event(Event::NFTSent {780 sender,781 recipient: new_owner,782 collection_id: rmrk_collection_id,783 nft_id: rmrk_nft_id,784 approval_required,785 });786787 Ok(())788 }789790 /// Accept an NFT sent from another account to self or an owned NFT.791 ///792 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.793 ///794 /// # Permissions:795 /// - Token-owner-to-be796 ///797 /// # Arguments:798 /// - `origin`: sender of the transaction799 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.800 /// - `rmrk_nft_id`: ID of the NFT to be accepted.801 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,802 /// whichever the accepted NFT was sent to.803 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]804 pub fn accept_nft(805 origin: OriginFor<T>,806 rmrk_collection_id: RmrkCollectionId,807 rmrk_nft_id: RmrkNftId,808 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,809 ) -> DispatchResult {810 let sender = ensure_signed(origin.clone())?;811 let cross_sender = T::CrossAccountId::from_sub(sender.clone());812813 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;814 let nft_id = rmrk_nft_id.into();815816 let collection =817 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;818 collection.check_is_external()?;819820 let new_cross_owner = match new_owner {821 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {822 T::CrossAccountId::from_sub(account_id.clone())823 }824 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(825 target_collection_id,826 target_nft_id,827 ) => {828 let target_collection_id = Self::unique_collection_id(target_collection_id)?;829830 T::CrossTokenAddressMapping::token_to_address(831 target_collection_id,832 TokenId(target_nft_id),833 )834 }835 };836837 let budget = budget::Value::new(NESTING_BUDGET);838839 <PalletNft<T>>::transfer(840 &collection,841 &cross_sender,842 &new_cross_owner,843 nft_id,844 &budget,845 )846 .map_err(|err| {847 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {848 <Error<T>>::CannotAcceptNonOwnedNft.into()849 } else {850 Self::map_unique_err_to_proxy(err)851 }852 })?;853854 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(855 collection_id,856 nft_id,857 RmrkProperty::PendingNftAccept,858 )?;859860 if let Some(pending_target) = pending_target {861 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;862863 <PalletNft<T>>::set_scoped_token_property(864 collection.id,865 nft_id,866 RMRK_SCOPE,867 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,868 )?;869 }870871 Self::deposit_event(Event::NFTAccepted {872 sender,873 recipient: new_owner,874 collection_id: rmrk_collection_id,875 nft_id: rmrk_nft_id,876 });877878 Ok(())879 }880881 /// Reject an NFT sent from another account to self or owned NFT.882 /// The NFT in question will not be sent back and burnt instead.883 ///884 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.885 ///886 /// # Permissions:887 /// - Token-owner-to-be-not888 ///889 /// # Arguments:890 /// - `origin`: sender of the transaction891 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.892 /// - `rmrk_nft_id`: ID of the NFT to be rejected.893 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]894 pub fn reject_nft(895 origin: OriginFor<T>,896 rmrk_collection_id: RmrkCollectionId,897 rmrk_nft_id: RmrkNftId,898 ) -> DispatchResult {899 let sender = ensure_signed(origin)?;900 let cross_sender = T::CrossAccountId::from_sub(sender.clone());901902 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903 let nft_id = rmrk_nft_id.into();904905 let collection =906 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;907 collection.check_is_external()?;908909 ensure!(910 <TokenData<T>>::get((collection_id, nft_id)).is_some(),911 <Error<T>>::NoAvailableNftId912 );913914 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(915 collection_id,916 nft_id,917 RmrkProperty::PendingNftAccept,918 )?;919920 match pending_target {921 Some(pending_target) => {922 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?923 }924 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),925 }926927 Self::destroy_nft(928 cross_sender,929 collection_id,930 nft_id,931 NESTING_BUDGET,932 <Error<T>>::CannotRejectNonOwnedNft,933 )934 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;935936 Self::deposit_event(Event::NFTRejected {937 sender,938 collection_id: rmrk_collection_id,939 nft_id: rmrk_nft_id,940 });941942 Ok(())943 }944945 /// Accept the addition of a newly created pending resource to an existing NFT.946 ///947 /// This transaction is needed when a resource is created and assigned to an NFT948 /// by a non-owner, i.e. the collection issuer, with one of the949 /// [`add_...` transactions](Pallet::add_basic_resource).950 ///951 /// # Permissions:952 /// - Token owner953 ///954 /// # Arguments:955 /// - `origin`: sender of the transaction956 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.957 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.958 /// - `resource_id`: ID of the newly created pending resource.959 /// accept the addition of a new resource to an existing NFT960 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]961 pub fn accept_resource(962 origin: OriginFor<T>,963 rmrk_collection_id: RmrkCollectionId,964 rmrk_nft_id: RmrkNftId,965 resource_id: RmrkResourceId,966 ) -> DispatchResult {967 let sender = ensure_signed(origin)?;968 let cross_sender = T::CrossAccountId::from_sub(sender);969970 let collection_id = Self::unique_collection_id(rmrk_collection_id)971 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;972 let collection =973 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;974 collection.check_is_external()?;975976 let nft_id = rmrk_nft_id.into();977978 let budget = budget::Value::new(NESTING_BUDGET);979980 let nft_owner =981 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)982 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;983984 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {985 ensure!(res.pending, <Error<T>>::ResourceNotPending);986 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);987988 res.pending = false;989990 Ok(())991 })?;992993 Self::deposit_event(Event::<T>::ResourceAccepted {994 nft_id: rmrk_nft_id,995 resource_id,996 });997998 Ok(())999 }10001001 /// Accept the removal of a removal-pending resource from an NFT.1002 ///1003 /// This transaction is needed when a non-owner, i.e. the collection issuer,1004 /// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1005 ///1006 /// # Permissions:1007 /// - Token owner1008 ///1009 /// # Arguments:1010 /// - `origin`: sender of the transaction1011 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1012 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1013 /// - `resource_id`: ID of the removal-pending resource.1014 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1015 pub fn accept_resource_removal(1016 origin: OriginFor<T>,1017 rmrk_collection_id: RmrkCollectionId,1018 rmrk_nft_id: RmrkNftId,1019 resource_id: RmrkResourceId,1020 ) -> DispatchResult {1021 let sender = ensure_signed(origin)?;1022 let cross_sender = T::CrossAccountId::from_sub(sender);10231024 let collection_id = Self::unique_collection_id(rmrk_collection_id)1025 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1026 let collection =1027 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1028 collection.check_is_external()?;10291030 let nft_id = rmrk_nft_id.into();10311032 let budget = budget::Value::new(NESTING_BUDGET);10331034 let nft_owner =1035 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1036 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10371038 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10391040 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10411042 let resource_info = <PalletNft<T>>::token_aux_property((1043 collection_id,1044 nft_id,1045 RMRK_SCOPE,1046 resource_id_key.clone(),1047 ))1048 .ok_or(<Error<T>>::ResourceDoesntExist)?;10491050 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10511052 ensure!(1053 resource_info.pending_removal,1054 <Error<T>>::ResourceNotPending1055 );10561057 <PalletNft<T>>::remove_token_aux_property(1058 collection_id,1059 nft_id,1060 RMRK_SCOPE,1061 resource_id_key,1062 );10631064 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1065 let base_id = resource.base;10661067 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1068 }10691070 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1071 nft_id: rmrk_nft_id,1072 resource_id,1073 });10741075 Ok(())1076 }10771078 /// Add or edit a custom user property, a key-value pair, describing the metadata1079 /// of a token or a collection, on either one of these.1080 ///1081 /// Note that in this proxy implementation many details regarding RMRK are stored1082 /// as scoped properties prefixed with "rmrk:", normally inaccessible1083 /// to external transactions and RPCs.1084 ///1085 /// # Permissions:1086 /// - Collection issuer - in case of collection property1087 /// - Token owner - in case of NFT property1088 ///1089 /// # Arguments:1090 /// - `origin`: sender of the transaction1091 /// - `rmrk_collection_id`: RMRK collection ID.1092 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1093 /// - `key`: Key of the custom property to be referenced by.1094 /// - `value`: Value of the custom property to be stored.1095 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1096 pub fn set_property(1097 origin: OriginFor<T>,1098 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1099 maybe_nft_id: Option<RmrkNftId>,1100 key: RmrkKeyString,1101 value: RmrkValueString,1102 ) -> DispatchResult {1103 let sender = ensure_signed(origin)?;1104 let sender = T::CrossAccountId::from_sub(sender);11051106 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1107 let collection =1108 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1109 collection.check_is_external()?;11101111 let budget = budget::Value::new(NESTING_BUDGET);11121113 match maybe_nft_id {1114 Some(nft_id) => {1115 let token_id: TokenId = nft_id.into();11161117 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1118 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11191120 <PalletNft<T>>::set_scoped_token_property(1121 collection_id,1122 token_id,1123 RMRK_SCOPE,1124 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1125 )?;1126 }1127 None => {1128 let collection = Self::get_typed_nft_collection(1129 collection_id,1130 misc::CollectionType::Regular,1131 )?;11321133 Self::check_collection_owner(&collection, &sender)?;11341135 <PalletCommon<T>>::set_scoped_collection_property(1136 collection_id,1137 RMRK_SCOPE,1138 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1139 )?;1140 }1141 }11421143 Self::deposit_event(Event::PropertySet {1144 collection_id: rmrk_collection_id,1145 maybe_nft_id,1146 key,1147 value,1148 });11491150 Ok(())1151 }11521153 /// Set a different order of resource priorities for an NFT. Priorities can be used,1154 /// for example, for order of rendering.1155 ///1156 /// Note that the priorities are not updated automatically, and are an empty vector1157 /// by default. There is no pre-set definition for the order to be particular,1158 /// it can be interpreted arbitrarily use-case by use-case.1159 ///1160 /// # Permissions:1161 /// - Token owner1162 ///1163 /// # Arguments:1164 /// - `origin`: sender of the transaction1165 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1166 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1167 /// - `priorities`: Ordered vector of resource IDs.1168 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1169 pub fn set_priority(1170 origin: OriginFor<T>,1171 rmrk_collection_id: RmrkCollectionId,1172 rmrk_nft_id: RmrkNftId,1173 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1174 ) -> DispatchResult {1175 let sender = ensure_signed(origin)?;1176 let sender = T::CrossAccountId::from_sub(sender);11771178 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1179 let nft_id = rmrk_nft_id.into();11801181 let collection =1182 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1183 collection.check_is_external()?;11841185 let budget = budget::Value::new(NESTING_BUDGET);11861187 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1188 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11891190 <PalletNft<T>>::set_scoped_token_property(1191 collection_id,1192 nft_id,1193 RMRK_SCOPE,1194 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1195 )?;11961197 Self::deposit_event(Event::<T>::PrioritySet {1198 collection_id: rmrk_collection_id,1199 nft_id: rmrk_nft_id,1200 });12011202 Ok(())1203 }12041205 /// Create and set/propose a basic resource for an NFT.1206 ///1207 /// A basic resource is the simplest, lacking a Base and anything that comes with it.1208 /// See RMRK docs for more information and examples.1209 ///1210 /// # Permissions:1211 /// - Collection issuer - if not the token owner, adding the resource will warrant1212 /// the owner's [acceptance](Pallet::accept_resource).1213 ///1214 /// # Arguments:1215 /// - `origin`: sender of the transaction1216 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1217 /// - `nft_id`: ID of the NFT to assign a resource to.1218 /// - `resource`: Data of the resource to be created.1219 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1220 pub fn add_basic_resource(1221 origin: OriginFor<T>,1222 rmrk_collection_id: RmrkCollectionId,1223 nft_id: RmrkNftId,1224 resource: RmrkBasicResource,1225 ) -> DispatchResult {1226 let sender = ensure_signed(origin.clone())?;12271228 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1229 let collection =1230 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1231 collection.check_is_external()?;12321233 let resource_id = Self::resource_add(1234 sender,1235 collection_id,1236 nft_id.into(),1237 RmrkResourceTypes::Basic(resource),1238 )?;12391240 Self::deposit_event(Event::ResourceAdded {1241 nft_id,1242 resource_id,1243 });1244 Ok(())1245 }12461247 /// Create and set/propose a composable resource for an NFT.1248 ///1249 /// A composable resource links to a Base and has a subset of its Parts it is composed of.1250 /// See RMRK docs for more information and examples.1251 ///1252 /// # Permissions:1253 /// - Collection issuer - if not the token owner, adding the resource will warrant1254 /// the owner's [acceptance](Pallet::accept_resource).1255 ///1256 /// # Arguments:1257 /// - `origin`: sender of the transaction1258 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1259 /// - `nft_id`: ID of the NFT to assign a resource to.1260 /// - `resource`: Data of the resource to be created.1261 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1262 pub fn add_composable_resource(1263 origin: OriginFor<T>,1264 rmrk_collection_id: RmrkCollectionId,1265 nft_id: RmrkNftId,1266 resource: RmrkComposableResource,1267 ) -> DispatchResult {1268 let sender = ensure_signed(origin.clone())?;12691270 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1271 let collection =1272 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1273 collection.check_is_external()?;12741275 let base_id = resource.base;12761277 let resource_id = Self::resource_add(1278 sender,1279 collection_id,1280 nft_id.into(),1281 RmrkResourceTypes::Composable(resource),1282 )?;12831284 <PalletNft<T>>::try_mutate_token_aux_property(1285 collection_id,1286 nft_id.into(),1287 RMRK_SCOPE,1288 Self::get_scoped_property_key(AssociatedBases)?,1289 |value| -> DispatchResult {1290 let mut bases: BasesMap = match value {1291 Some(value) => Self::decode_property_value(value)?,1292 None => BasesMap::new(),1293 };12941295 *bases.entry(base_id).or_insert(0) += 1;12961297 *value = Some(Self::encode_property_value(&bases)?);1298 Ok(())1299 },1300 )?;13011302 Self::deposit_event(Event::ResourceAdded {1303 nft_id,1304 resource_id,1305 });1306 Ok(())1307 }13081309 /// Create and set/propose a slot resource for an NFT.1310 ///1311 /// A slot resource links to a Base and a slot ID in it which it can fit into.1312 /// See RMRK docs for more information and examples.1313 ///1314 /// # Permissions:1315 /// - Collection issuer - if not the token owner, adding the resource will warrant1316 /// the owner's [acceptance](Pallet::accept_resource).1317 ///1318 /// # Arguments:1319 /// - `origin`: sender of the transaction1320 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1321 /// - `nft_id`: ID of the NFT to assign a resource to.1322 /// - `resource`: Data of the resource to be created.1323 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1324 pub fn add_slot_resource(1325 origin: OriginFor<T>,1326 rmrk_collection_id: RmrkCollectionId,1327 nft_id: RmrkNftId,1328 resource: RmrkSlotResource,1329 ) -> DispatchResult {1330 let sender = ensure_signed(origin.clone())?;13311332 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1333 let collection =1334 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1335 collection.check_is_external()?;13361337 let resource_id = Self::resource_add(1338 sender,1339 collection_id,1340 nft_id.into(),1341 RmrkResourceTypes::Slot(resource),1342 )?;13431344 Self::deposit_event(Event::ResourceAdded {1345 nft_id,1346 resource_id,1347 });1348 Ok(())1349 }13501351 /// Remove and erase a resource from an NFT.1352 ///1353 /// If the sender does not own the NFT, then it will be pending confirmation,1354 /// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1355 ///1356 /// # Permissions1357 /// - Collection issuer1358 ///1359 /// # Arguments1360 /// - `origin`: sender of the transaction1361 /// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1362 /// - `nft_id`: ID of the NFT with a resource to be removed.1363 /// - `resource_id`: ID of the resource to be removed.1364 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1365 pub fn remove_resource(1366 origin: OriginFor<T>,1367 rmrk_collection_id: RmrkCollectionId,1368 nft_id: RmrkNftId,1369 resource_id: RmrkResourceId,1370 ) -> DispatchResult {1371 let sender = ensure_signed(origin.clone())?;13721373 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1374 let collection =1375 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1376 collection.check_is_external()?;13771378 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13791380 Self::deposit_event(Event::ResourceRemoval {1381 nft_id,1382 resource_id,1383 });1384 Ok(())1385 }1386 }1387}13881389impl<T: Config> Pallet<T> {1390 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1391 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1392 let key = rmrk_key.to_key::<T>()?;13931394 let scoped_key = RMRK_SCOPE1395 .apply(key)1396 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13971398 Ok(scoped_key)1399 }14001401 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1402 /// and encoding the value from an arbitrary type into bytes.1403 pub fn encode_rmrk_property<E: Encode>(1404 rmrk_key: RmrkProperty,1405 value: &E,1406 ) -> Result<Property, DispatchError> {1407 let key = rmrk_key.to_key::<T>()?;14081409 let value = Self::encode_property_value(value)?;14101411 let property = Property { key, value };14121413 Ok(property)1414 }14151416 /// Encode property value from an arbitrary type into bytes for storage.1417 pub fn encode_property_value<E: Encode, S: Get<u32>>(1418 value: &E,1419 ) -> Result<BoundedBytes<S>, DispatchError> {1420 let value = value1421 .encode()1422 .try_into()1423 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14241425 Ok(value)1426 }14271428 /// Decode property value from bytes into an arbitrary type.1429 pub fn decode_property_value<D: Decode, S: Get<u32>>(1430 vec: &BoundedBytes<S>,1431 ) -> Result<D, DispatchError> {1432 vec.decode()1433 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1434 }14351436 /// Change the limit of a property value byte vector.1437 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1438 where1439 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1440 {1441 vec.rebind()1442 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1443 }14441445 /// Initialize a new NFT collection with certain RMRK-scoped properties.1446 ///1447 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1448 fn init_collection(1449 sender: T::CrossAccountId,1450 data: CreateCollectionData<T::AccountId>,1451 properties: impl Iterator<Item = Property>,1452 ) -> Result<CollectionId, DispatchError> {1453 let collection_id = <PalletNft<T>>::init_collection(1454 sender.clone(),1455 sender,1456 data,1457 up_data_structs::CollectionFlags {1458 external: true,1459 ..Default::default()1460 },1461 );14621463 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1464 return Err(<Error<T>>::NoAvailableCollectionId.into());1465 }14661467 <PalletCommon<T>>::set_scoped_collection_properties(1468 collection_id?,1469 RMRK_SCOPE,1470 properties,1471 )?;14721473 collection_id1474 }14751476 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1477 ///1478 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1479 pub fn create_nft(1480 sender: &T::CrossAccountId,1481 owner: &T::CrossAccountId,1482 collection: &NonfungibleHandle<T>,1483 properties: impl Iterator<Item = Property>,1484 ) -> Result<TokenId, DispatchError> {1485 let data = CreateNftExData {1486 properties: BoundedVec::default(),1487 owner: owner.clone(),1488 };14891490 let budget = budget::Value::new(NESTING_BUDGET);14911492 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14931494 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14951496 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14971498 Ok(nft_id)1499 }15001501 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1502 ///1503 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1504 fn destroy_nft(1505 sender: T::CrossAccountId,1506 collection_id: CollectionId,1507 token_id: TokenId,1508 max_burns: u32,1509 error_if_not_owned: Error<T>,1510 ) -> DispatchResultWithPostInfo {1511 let collection =1512 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15131514 let token_data =1515 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15161517 let from = token_data.owner;15181519 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15201521 ensure!(1522 <PalletStructure<T>>::check_indirectly_owned(1523 sender.clone(),1524 collection_id,1525 token_id,1526 None,1527 &owner_check_budget1528 )?,1529 error_if_not_owned,1530 );15311532 let burns_budget = budget::Value::new(max_burns);1533 let breadth_budget = budget::Value::new(max_burns);15341535 <PalletNft<T>>::burn_recursively(1536 &collection,1537 &from,1538 token_id,1539 &burns_budget,1540 &breadth_budget,1541 )1542 }15431544 /// Add a sent token pending acceptance to the target owning token as a property.1545 fn insert_pending_child(1546 target: (CollectionId, TokenId),1547 child: (RmrkCollectionId, RmrkNftId),1548 ) -> DispatchResult {1549 Self::mutate_pending_children(target, |pending_children| {1550 pending_children.insert(child);1551 })1552 }15531554 /// Remove a sent token pending acceptance from the target token's properties.1555 fn remove_pending_child(1556 target: (CollectionId, TokenId),1557 child: (RmrkCollectionId, RmrkNftId),1558 ) -> DispatchResult {1559 Self::mutate_pending_children(target, |pending_children| {1560 pending_children.remove(&child);1561 })1562 }15631564 /// Apply a mutation to the property of a token containing sent tokens1565 /// that are currently pending acceptance.1566 fn mutate_pending_children(1567 (target_collection_id, target_nft_id): (CollectionId, TokenId),1568 f: impl FnOnce(&mut PendingChildrenSet),1569 ) -> DispatchResult {1570 <PalletNft<T>>::try_mutate_token_aux_property(1571 target_collection_id,1572 target_nft_id,1573 RMRK_SCOPE,1574 Self::get_scoped_property_key(PendingChildren)?,1575 |pending_children| -> DispatchResult {1576 let mut map = match pending_children {1577 Some(map) => Self::decode_property_value(map)?,1578 None => PendingChildrenSet::new(),1579 };15801581 f(&mut map);15821583 *pending_children = Some(Self::encode_property_value(&map)?);15841585 Ok(())1586 },1587 )1588 }15891590 /// Get an iterator from a token's property containing tokens sent to it1591 /// that are currently pending acceptance.1592 fn iterate_pending_children(1593 collection_id: CollectionId,1594 nft_id: TokenId,1595 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1596 let property = <PalletNft<T>>::token_aux_property((1597 collection_id,1598 nft_id,1599 RMRK_SCOPE,1600 Self::get_scoped_property_key(PendingChildren)?,1601 ));16021603 let pending_children = match property {1604 Some(map) => Self::decode_property_value(&map)?,1605 None => PendingChildrenSet::new(),1606 };16071608 Ok(pending_children.into_iter())1609 }16101611 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1612 /// Thus, the returned resource ID should be used.1613 ///1614 /// Resource IDs are unique only across an NFT.1615 fn acquire_next_resource_id(1616 collection_id: CollectionId,1617 nft_id: TokenId,1618 ) -> Result<RmrkResourceId, DispatchError> {1619 let resource_id: RmrkResourceId =1620 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16211622 let next_id = resource_id1623 .checked_add(1)1624 .ok_or(<Error<T>>::NoAvailableResourceId)?;16251626 <PalletNft<T>>::set_scoped_token_property(1627 collection_id,1628 nft_id,1629 RMRK_SCOPE,1630 Self::encode_rmrk_property(NextResourceId, &next_id)?,1631 )?;16321633 Ok(resource_id)1634 }16351636 /// Create and add a resource for a regular NFT, mark it as pending if the sender1637 /// is not the token owner. The sender must be the collection owner.1638 fn resource_add(1639 sender: T::AccountId,1640 collection_id: CollectionId,1641 nft_id: TokenId,1642 resource: RmrkResourceTypes,1643 ) -> Result<RmrkResourceId, DispatchError> {1644 let collection =1645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1646 ensure!(collection.owner == sender, Error::<T>::NoPermission);16471648 let sender = T::CrossAccountId::from_sub(sender);1649 let budget = budget::Value::new(NESTING_BUDGET);16501651 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1652 .map_err(Self::map_unique_err_to_proxy)?;16531654 let pending = sender != nft_owner;16551656 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16571658 let resource_info = RmrkResourceInfo {1659 id,1660 resource,1661 pending,1662 pending_removal: false,1663 };16641665 <PalletNft<T>>::try_mutate_token_aux_property(1666 collection_id,1667 nft_id,1668 RMRK_SCOPE,1669 Self::get_scoped_property_key(ResourceId(id))?,1670 |value| -> DispatchResult {1671 *value = Some(Self::encode_property_value(&resource_info)?);16721673 Ok(())1674 },1675 )?;16761677 Ok(id)1678 }16791680 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1681 /// The sender must be the collection owner.1682 fn resource_remove(1683 sender: T::AccountId,1684 collection_id: CollectionId,1685 nft_id: TokenId,1686 resource_id: RmrkResourceId,1687 ) -> DispatchResult {1688 let collection =1689 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1690 ensure!(collection.owner == sender, Error::<T>::NoPermission);16911692 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16931694 let resource = <PalletNft<T>>::token_aux_property((1695 collection_id,1696 nft_id,1697 RMRK_SCOPE,1698 resource_id_key.clone(),1699 ))1700 .ok_or(<Error<T>>::ResourceDoesntExist)?;17011702 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17031704 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1705 let topmost_owner =1706 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17071708 let sender = T::CrossAccountId::from_sub(sender);1709 if topmost_owner == sender {1710 <PalletNft<T>>::remove_token_aux_property(1711 collection_id,1712 nft_id,1713 RMRK_SCOPE,1714 Self::get_scoped_property_key(ResourceId(resource_id))?,1715 );17161717 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1718 let base_id = resource.base;17191720 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1721 }1722 } else {1723 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1724 res.pending_removal = true;17251726 Ok(())1727 })?;1728 }17291730 Ok(())1731 }17321733 /// Remove a Base ID from an NFT if they are associated.1734 /// The Base itself is deleted if the number of associated NFTs reaches 0.1735 fn remove_associated_base_id(1736 collection_id: CollectionId,1737 nft_id: TokenId,1738 base_id: RmrkBaseId,1739 ) -> DispatchResult {1740 <PalletNft<T>>::try_mutate_token_aux_property(1741 collection_id,1742 nft_id,1743 RMRK_SCOPE,1744 Self::get_scoped_property_key(AssociatedBases)?,1745 |value| -> DispatchResult {1746 let mut bases: BasesMap = match value {1747 Some(value) => Self::decode_property_value(value)?,1748 None => BasesMap::new(),1749 };17501751 let remaining = bases.get(&base_id);17521753 if let Some(remaining) = remaining {1754 if let Some(0) | None = remaining.checked_sub(1) {1755 bases.remove(&base_id);1756 }1757 }17581759 *value = Some(Self::encode_property_value(&bases)?);1760 Ok(())1761 },1762 )1763 }17641765 /// Apply a mutation to a resource stored in the token properties of an NFT.1766 fn try_mutate_resource_info(1767 collection_id: CollectionId,1768 nft_id: TokenId,1769 resource_id: RmrkResourceId,1770 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1771 ) -> DispatchResult {1772 <PalletNft<T>>::try_mutate_token_aux_property(1773 collection_id,1774 nft_id,1775 RMRK_SCOPE,1776 Self::get_scoped_property_key(ResourceId(resource_id))?,1777 |value| match value {1778 Some(value) => {1779 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17801781 f(&mut resource_info)?;17821783 *value = Self::encode_property_value(&resource_info)?;17841785 Ok(())1786 }1787 None => Err(<Error<T>>::ResourceDoesntExist.into()),1788 },1789 )1790 }17911792 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1793 fn change_collection_owner(1794 collection_id: CollectionId,1795 collection_type: misc::CollectionType,1796 sender: T::AccountId,1797 new_owner: T::AccountId,1798 ) -> DispatchResult {1799 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1800 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18011802 let mut collection = collection.into_inner();18031804 collection.owner = new_owner;1805 collection.save()1806 }18071808 /// Ensure that an account is the collection owner/issuer, return an error if not.1809 pub fn check_collection_owner(1810 collection: &NonfungibleHandle<T>,1811 account: &T::CrossAccountId,1812 ) -> DispatchResult {1813 collection1814 .check_is_owner(account)1815 .map_err(Self::map_unique_err_to_proxy)1816 }18171818 /// Get the latest yet-unused RMRK collection index from the storage.1819 pub fn last_collection_idx() -> RmrkCollectionId {1820 <CollectionIndex<T>>::get()1821 }18221823 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1824 pub fn unique_collection_id(1825 rmrk_collection_id: RmrkCollectionId,1826 ) -> Result<CollectionId, DispatchError> {1827 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1828 .map_err(|_| <Error<T>>::CollectionUnknown.into())1829 }18301831 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1832 pub fn rmrk_collection_id(1833 unique_collection_id: CollectionId,1834 ) -> Result<RmrkCollectionId, DispatchError> {1835 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1836 }18371838 /// Fetch a Unique NFT collection.1839 pub fn get_nft_collection(1840 collection_id: CollectionId,1841 ) -> Result<NonfungibleHandle<T>, DispatchError> {1842 let collection = <CollectionHandle<T>>::try_get(collection_id)1843 .map_err(|_| <Error<T>>::CollectionUnknown)?;18441845 match collection.mode {1846 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1847 _ => Err(<Error<T>>::CollectionUnknown.into()),1848 }1849 }18501851 /// Check if an NFT collection with such an ID exists.1852 pub fn collection_exists(collection_id: CollectionId) -> bool {1853 <CollectionHandle<T>>::try_get(collection_id).is_ok()1854 }18551856 /// Fetch and decode a RMRK-scoped collection property value in bytes.1857 pub fn get_collection_property(1858 collection_id: CollectionId,1859 key: RmrkProperty,1860 ) -> Result<PropertyValue, DispatchError> {1861 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1862 .get(&Self::get_scoped_property_key(key)?)1863 .ok_or(<Error<T>>::CollectionUnknown)?1864 .clone();18651866 Ok(collection_property)1867 }18681869 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1870 pub fn get_collection_property_decoded<V: Decode>(1871 collection_id: CollectionId,1872 key: RmrkProperty,1873 ) -> Result<V, DispatchError> {1874 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1875 }18761877 /// Get the type of a collection stored as a scoped property.1878 ///1879 /// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1880 pub fn get_collection_type(1881 collection_id: CollectionId,1882 ) -> Result<misc::CollectionType, DispatchError> {1883 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1884 if err != <Error<T>>::CollectionUnknown.into() {1885 <Error<T>>::CorruptedCollectionType.into()1886 } else {1887 err1888 }1889 })1890 }18911892 /// Ensure that the type of the collection equals the provided type,1893 /// otherwise return an error.1894 pub fn ensure_collection_type(1895 collection_id: CollectionId,1896 collection_type: misc::CollectionType,1897 ) -> DispatchResult {1898 let actual_type = Self::get_collection_type(collection_id)?;1899 ensure!(1900 actual_type == collection_type,1901 <CommonError<T>>::NoPermission1902 );19031904 Ok(())1905 }19061907 /// Fetch an NFT collection, but make sure it has the appropriate type.1908 pub fn get_typed_nft_collection(1909 collection_id: CollectionId,1910 collection_type: misc::CollectionType,1911 ) -> Result<NonfungibleHandle<T>, DispatchError> {1912 Self::ensure_collection_type(collection_id, collection_type)?;19131914 Self::get_nft_collection(collection_id)1915 }19161917 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1918 /// but also return the Unique collection ID.1919 pub fn get_typed_nft_collection_mapped(1920 rmrk_collection_id: RmrkCollectionId,1921 collection_type: misc::CollectionType,1922 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1923 let unique_collection_id = match collection_type {1924 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1925 _ => rmrk_collection_id.into(),1926 };19271928 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19291930 Ok((collection, unique_collection_id))1931 }19321933 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1934 pub fn get_nft_property(1935 collection_id: CollectionId,1936 nft_id: TokenId,1937 key: RmrkProperty,1938 ) -> Result<PropertyValue, DispatchError> {1939 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1940 .get(&Self::get_scoped_property_key(key)?)1941 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1942 .clone();19431944 Ok(nft_property)1945 }19461947 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1948 pub fn get_nft_property_decoded<V: Decode>(1949 collection_id: CollectionId,1950 nft_id: TokenId,1951 key: RmrkProperty,1952 ) -> Result<V, DispatchError> {1953 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1954 }19551956 /// Check that an NFT exists.1957 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1958 <TokenData<T>>::contains_key((collection_id, nft_id))1959 }19601961 /// Get the type of an NFT stored as a scoped property.1962 ///1963 /// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1964 pub fn get_nft_type(1965 collection_id: CollectionId,1966 token_id: TokenId,1967 ) -> Result<NftType, DispatchError> {1968 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1969 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1970 }19711972 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1973 pub fn ensure_nft_type(1974 collection_id: CollectionId,1975 token_id: TokenId,1976 nft_type: NftType,1977 ) -> DispatchResult {1978 let actual_type = Self::get_nft_type(collection_id, token_id)?;1979 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19801981 Ok(())1982 }19831984 /// Ensure that an account is the owner of the token, either directly1985 /// or at the top of the nesting hierarchy; return an error if it is not.1986 pub fn ensure_nft_owner(1987 collection_id: CollectionId,1988 token_id: TokenId,1989 possible_owner: &T::CrossAccountId,1990 nesting_budget: &dyn budget::Budget,1991 ) -> DispatchResult {1992 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1993 possible_owner.clone(),1994 collection_id,1995 token_id,1996 None,1997 nesting_budget,1998 )1999 .map_err(Self::map_unique_err_to_proxy)?;20002001 ensure!(is_owned, <Error<T>>::NoPermission);20022003 Ok(())2004 }20052006 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2007 /// or, if None are provided, return all non-scoped properties.2008 pub fn filter_user_properties<Key, Value, R, Mapper>(2009 collection_id: CollectionId,2010 token_id: Option<TokenId>,2011 filter_keys: Option<Vec<RmrkPropertyKey>>,2012 mapper: Mapper,2013 ) -> Result<Vec<R>, DispatchError>2014 where2015 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2016 Value: Decode + Default,2017 Mapper: Fn(Key, Value) -> R,2018 {2019 filter_keys2020 .map(|keys| {2021 let properties = keys2022 .into_iter()2023 .filter_map(|key| {2024 let key: Key = key.try_into().ok()?;20252026 let value = match token_id {2027 Some(token_id) => Self::get_nft_property_decoded(2028 collection_id,2029 token_id,2030 UserProperty(key.as_ref()),2031 ),2032 None => Self::get_collection_property_decoded(2033 collection_id,2034 UserProperty(key.as_ref()),2035 ),2036 }2037 .ok()?;20382039 Some(mapper(key, value))2040 })2041 .collect();20422043 Ok(properties)2044 })2045 .unwrap_or_else(|| {2046 let properties =2047 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20482049 Ok(properties)2050 })2051 }20522053 /// Get all non-scoped properties from a collection or a token, and apply some transformation,2054 /// supplied by `mapper`, to each key-value pair.2055 pub fn iterate_user_properties<Key, Value, R, Mapper>(2056 collection_id: CollectionId,2057 token_id: Option<TokenId>,2058 mapper: Mapper,2059 ) -> Result<impl Iterator<Item = R>, DispatchError>2060 where2061 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2062 Value: Decode + Default,2063 Mapper: Fn(Key, Value) -> R,2064 {2065 let properties = match token_id {2066 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2067 None => <PalletCommon<T>>::collection_properties(collection_id),2068 };20692070 let properties = properties.into_iter().filter_map(move |(key, value)| {2071 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20722073 let key: Key = key.to_vec().try_into().ok()?;2074 let value: Value = value.decode().ok()?;20752076 Some(mapper(key, value))2077 });20782079 Ok(properties)2080 }20812082 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2083 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2084 map_unique_err_to_proxy! {2085 match err {2086 CommonError::NoPermission => NoPermission,2087 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2088 CommonError::PublicMintingNotAllowed => NoPermission,2089 CommonError::TokenNotFound => NoAvailableNftId,2090 CommonError::ApprovedValueTooLow => NoPermission,2091 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2092 StructureError::TokenNotFound => NoAvailableNftId,2093 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2094 }2095 }2096 }2097}pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -104,7 +104,7 @@
pub mod weights;
use weights::WeightInfo;
-/// Maximum number of levels of depth in the token nesting tree.
+/// A maximum number of levels of depth in the token nesting tree.
pub const NESTING_BUDGET: u32 = 5;
decl_error! {
@@ -279,34 +279,34 @@
{
type Error = Error<T>;
- #[doc = "Maximum number of levels of depth in the token nesting tree."]
+ #[doc = "A maximum number of levels of depth in the token nesting tree."]
const NESTING_BUDGET: u32 = NESTING_BUDGET;
- #[doc = "Maximum length for collection name."]
+ #[doc = "Maximal length of a collection name."]
const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;
- #[doc = "Maximum length for collection description."]
+ #[doc = "Maximal length of a collection description."]
const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;
- #[doc = "Maximal token prefix length."]
+ #[doc = "Maximal length of a token prefix."]
const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;
#[doc = "Maximum admins per collection."]
const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;
- #[doc = "Maximal lenght of property key."]
+ #[doc = "Maximal length of a property key."]
const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;
- #[doc = "Maximal lenght of property value."]
+ #[doc = "Maximal length of a property value."]
const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;
- #[doc = "Maximum properties that can be assigned to token."]
+ #[doc = "A maximum number of token properties."]
const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;
#[doc = "Maximum size for all collection properties."]
const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;
- #[doc = "Maximum size for all token properties."]
+ #[doc = "Maximum size of all token properties."]
const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;
#[doc = "Default NFT collection limit."]
primitives/data-structs/CHANGELOG.mddiffbeforeafterboth--- a/primitives/data-structs/CHANGELOG.md
+++ b/primitives/data-structs/CHANGELOG.md
@@ -44,4 +44,4 @@
### Added
-- Аields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
+- Fields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -120,22 +120,22 @@
// TODO: not used. Delete?
pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;
-/// Maximum length for collection name.
+/// Maximal length of a collection name.
pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;
-/// Maximum length for collection description.
+/// Maximal length of a collection description.
pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;
-/// Maximal token prefix length.
+/// Maximal length of a token prefix.
pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;
-/// Maximal lenght of property key.
+/// Maximal length of a property key.
pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;
-/// Maximal lenght of property value.
+/// Maximal length of a property value.
pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;
-/// Maximum properties that can be assigned to token.
+/// A maximum number of token properties.
pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;
/// Maximal lenght of extended property value.
@@ -144,7 +144,7 @@
/// Maximum size for all collection properties.
pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;
-/// Maximum size for all token properties.
+/// Maximum size of all token properties.
pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;
/// How much items can be created per single
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -255,11 +255,11 @@
**/
ftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
/**
- * Maximum length for collection description.
+ * Maximal length of a collection description.
**/
maxCollectionDescriptionLength: u32 & AugmentedConst<ApiType>;
/**
- * Maximum length for collection name.
+ * Maximal length of a collection name.
**/
maxCollectionNameLength: u32 & AugmentedConst<ApiType>;
/**
@@ -267,27 +267,27 @@
**/
maxCollectionPropertiesSize: u32 & AugmentedConst<ApiType>;
/**
- * Maximum properties that can be assigned to token.
+ * A maximum number of token properties.
**/
maxPropertiesPerItem: u32 & AugmentedConst<ApiType>;
/**
- * Maximal lenght of property key.
+ * Maximal length of a property key.
**/
maxPropertyKeyLength: u32 & AugmentedConst<ApiType>;
/**
- * Maximal lenght of property value.
+ * Maximal length of a property value.
**/
maxPropertyValueLength: u32 & AugmentedConst<ApiType>;
/**
- * Maximal token prefix length.
+ * Maximal length of a token prefix.
**/
maxTokenPrefixLength: u32 & AugmentedConst<ApiType>;
/**
- * Maximum size for all token properties.
+ * Maximum size of all token properties.
**/
maxTokenPropertiesSize: u32 & AugmentedConst<ApiType>;
/**
- * Maximum number of levels of depth in the token nesting tree.
+ * A maximum number of levels of depth in the token nesting tree.
**/
nestingBudget: u32 & AugmentedConst<ApiType>;
/**