difftreelog
style cargo fmt
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 spec repository: <https://github.com/rmrk-team/rmrk-spec>52//! 53//! ## Proxy Implementation54//! 55//! An external user is supposed to be able to utilize this proxy as they would56//! utilize RMRK, and get exactly the same results. Normally, Unique transactions57//! are off-limits to RMRK collections and tokens, and vice versa. However,58//! the information stored on chain can be freely interpreted by storage reads and RPCs.59//! 60//! ### ID Mapping61//! 62//! RMRK's collections' IDs are counted independently of Unique's and start at 0.63//! Note that tokens' IDs still start at 1.64//! The collections themselves, as well as tokens, are stored as Unique collections,65//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).66//! 67//! ### External/Internal Collection Insulation68//! 69//! A Unique transaction cannot target collections purposed for RMRK,70//! and they are flagged as `external` to specify that. On the other hand, 71//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.72//! 73//! ### Native Properties74//! 75//! Many of RMRK's native parameters are stored as scoped properties of a collection 76//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`77//! is an unacceptable symbol in user-defined proeprties, which, along with other safeguards,78//! makes them impossible to tamper with.79//! 80//! ### Collection and NFT Types81//! 82//! RMRK introduces the concept of a Base, which is a catalgoue of Parts, 83//! possible components of an NFT. Due to its similarity with the functionality84//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes85//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and 86//! [`NftType`](pallet_rmrk_core::misc::NftType).87//! 88//! ## Interface89//! 90//! ### Dispatchables91//! 92//! - `create_collection` - Create a new collection of NFTs.93//! - `destroy_collection` - Destroy a collection.94//! - `change_collection_issuer` - Change the issuer of a collection. 95//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).96//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**97//! - `mint_nft` - Mint an NFT in a specified collection.98//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.99//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.100//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.101//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.102//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.103//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.104//! - `set_property` - Add or edit a custom user property of a token or a collection.105//! - `set_priority` - Set a different order of resource priorities for an NFT.106//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.107//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.108//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.109//! - `remove_resource` - Remove and erase a resource from an NFT.110111#![cfg_attr(not(feature = "std"), no_std)]112113use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};114use frame_system::{pallet_prelude::*, ensure_signed};115use sp_runtime::{DispatchError, Permill, traits::StaticLookup};116use sp_std::{117 vec::Vec,118 collections::{btree_set::BTreeSet, btree_map::BTreeMap},119};120use up_data_structs::{*, mapping::TokenAddressMapping};121use pallet_common::{122 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,123};124use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};125use pallet_structure::{Pallet as PalletStructure, Error as StructureError};126use pallet_evm::account::CrossAccountId;127use core::convert::AsRef;128129pub use pallet::*;130131#[cfg(feature = "runtime-benchmarks")]132pub mod benchmarking;133pub mod misc;134pub mod property;135pub mod rpc;136pub mod weights;137138pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140use weights::WeightInfo;141use misc::*;142pub use property::*;143144use RmrkProperty::*;145146/// Maximum number of levels of depth in the token nesting tree.147pub const NESTING_BUDGET: u32 = 5;148149type PendingTarget = (CollectionId, TokenId);150type PendingChild = (RmrkCollectionId, RmrkNftId);151type PendingChildrenSet = BTreeSet<PendingChild>;152153type BasesMap = BTreeMap<RmrkBaseId, u32>;154155#[frame_support::pallet]156pub mod pallet {157 use super::*;158 use pallet_evm::account;159160 #[pallet::config]161 pub trait Config:162 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config163 {164 /// Overarching event type.165 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;166 167 /// The weight information of this pallet.168 type WeightInfo: WeightInfo;169 }170171 /// Latest yet-unused collection ID.172 #[pallet::storage]173 #[pallet::getter(fn collection_index)]174 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;175176 /// Mapping from RMRK collection ID to Unique's.177 #[pallet::storage]178 pub type UniqueCollectionId<T: Config> =179 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;180181 #[pallet::pallet]182 #[pallet::generate_store(pub(super) trait Store)]183 pub struct Pallet<T>(_);184185 #[pallet::event]186 #[pallet::generate_deposit(pub(super) fn deposit_event)]187 pub enum Event<T: Config> {188 CollectionCreated {189 issuer: T::AccountId,190 collection_id: RmrkCollectionId,191 },192 CollectionDestroyed {193 issuer: T::AccountId,194 collection_id: RmrkCollectionId,195 },196 IssuerChanged {197 old_issuer: T::AccountId,198 new_issuer: T::AccountId,199 collection_id: RmrkCollectionId,200 },201 CollectionLocked {202 issuer: T::AccountId,203 collection_id: RmrkCollectionId,204 },205 NftMinted {206 owner: T::AccountId,207 collection_id: RmrkCollectionId,208 nft_id: RmrkNftId,209 },210 NFTBurned {211 owner: T::AccountId,212 nft_id: RmrkNftId,213 },214 NFTSent {215 sender: T::AccountId,216 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,217 collection_id: RmrkCollectionId,218 nft_id: RmrkNftId,219 approval_required: bool,220 },221 NFTAccepted {222 sender: T::AccountId,223 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,224 collection_id: RmrkCollectionId,225 nft_id: RmrkNftId,226 },227 NFTRejected {228 sender: T::AccountId,229 collection_id: RmrkCollectionId,230 nft_id: RmrkNftId,231 },232 PropertySet {233 collection_id: RmrkCollectionId,234 maybe_nft_id: Option<RmrkNftId>,235 key: RmrkKeyString,236 value: RmrkValueString,237 },238 ResourceAdded {239 nft_id: RmrkNftId,240 resource_id: RmrkResourceId,241 },242 ResourceRemoval {243 nft_id: RmrkNftId,244 resource_id: RmrkResourceId,245 },246 ResourceAccepted {247 nft_id: RmrkNftId,248 resource_id: RmrkResourceId,249 },250 ResourceRemovalAccepted {251 nft_id: RmrkNftId,252 resource_id: RmrkResourceId,253 },254 PrioritySet {255 collection_id: RmrkCollectionId,256 nft_id: RmrkNftId,257 },258 }259260 #[pallet::error]261 pub enum Error<T> {262 /* Unique proxy-specific events */263 /// Property of the type of RMRK collection could not be read successfully.264 CorruptedCollectionType,265 // NftTypeEncodeError,266 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).267 RmrkPropertyKeyIsTooLong,268 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).269 RmrkPropertyValueIsTooLong,270 /// Could not find a property by the supplied key.271 RmrkPropertyIsNotFound,272 /// Something went wrong when decoding encoded data from the storage. 273 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.274 UnableToDecodeRmrkData,275276 /* RMRK compatible events */277 /// Only destroying collections without tokens is allowed.278 CollectionNotEmpty,279 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain.280 NoAvailableCollectionId,281 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection.282 NoAvailableNftId,283 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.284 CollectionUnknown,285 /// No permission to perform action.286 NoPermission,287 /// Token is marked as non-transferable, and thus cannot be transferred.288 NonTransferable,289 /// Too many tokens created in the collection, no new ones are allowed.290 CollectionFullOrLocked,291 /// No such resource found.292 ResourceDoesntExist,293 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros. 294 /// Sending to self is redundant.295 CannotSendToDescendentOrSelf,296 /// Not the target owner of the sent NFT.297 CannotAcceptNonOwnedNft,298 /// Not the target owner of the sent NFT.299 CannotRejectNonOwnedNft,300 /// NFT was not sent and is not pending.301 CannotRejectNonPendingNft,302 /// Resource is not pending for the operation.303 ResourceNotPending,304 /// Could not find an ID for the resource. Is is likely there were too many resources created on an NFT.305 NoAvailableResourceId,306 }307308 #[pallet::call]309 impl<T: Config> Pallet<T> {310 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?311312 /// Create a new collection of NFTs.313 ///314 /// # Permissions:315 /// * Anyone - will be assigned as the issuer of the collection.316 ///317 /// # Arguments:318 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.319 /// - `max`: Optional maximum number of tokens.320 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs. 321 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.322 #[transactional]323 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]324 pub fn create_collection(325 origin: OriginFor<T>,326 metadata: RmrkString,327 max: Option<u32>,328 symbol: RmrkCollectionSymbol,329 ) -> DispatchResult {330 let sender = ensure_signed(origin)?;331332 let limits = CollectionLimits {333 owner_can_transfer: Some(false),334 token_limit: max,335 ..Default::default()336 };337338 let data = CreateCollectionData {339 limits: Some(limits),340 token_prefix: symbol341 .into_inner()342 .try_into()343 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,344 permissions: Some(CollectionPermissions {345 nesting: Some(NestingPermissions {346 token_owner: true,347 collection_admin: false,348 restricted: None,349 #[cfg(feature = "runtime-benchmarks")]350 permissive: false,351 }),352 ..Default::default()353 }),354 ..Default::default()355 };356357 let unique_collection_id = Self::init_collection(358 T::CrossAccountId::from_sub(sender.clone()),359 data,360 [361 Self::encode_rmrk_property(Metadata, &metadata)?,362 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,363 ]364 .into_iter(),365 )?;366 let rmrk_collection_id = <CollectionIndex<T>>::get();367368 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);369370 <PalletCommon<T>>::set_scoped_collection_property(371 unique_collection_id,372 RMRK_SCOPE,373 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,374 )?;375376 <CollectionIndex<T>>::mutate(|n| *n += 1);377378 Self::deposit_event(Event::CollectionCreated {379 issuer: sender,380 collection_id: rmrk_collection_id,381 });382383 Ok(())384 }385386 /// Destroy a collection. 387 /// 388 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.389 ///390 /// # Permissions:391 /// * Collection issuer392 ///393 /// # Arguments:394 /// - `collection_id`: RMRK ID of the collection to destroy.395 #[transactional]396 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]397 pub fn destroy_collection(398 origin: OriginFor<T>,399 collection_id: RmrkCollectionId,400 ) -> DispatchResult {401 let sender = ensure_signed(origin)?;402 let cross_sender = T::CrossAccountId::from_sub(sender.clone());403404 let collection = Self::get_typed_nft_collection(405 Self::unique_collection_id(collection_id)?,406 misc::CollectionType::Regular,407 )?;408 collection.check_is_external()?;409410 <PalletNft<T>>::destroy_collection(collection, &cross_sender)411 .map_err(Self::map_unique_err_to_proxy)?;412413 Self::deposit_event(Event::CollectionDestroyed {414 issuer: sender,415 collection_id,416 });417418 Ok(())419 }420421 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).422 /// 423 /// # Permissions:424 /// * Collection issuer425 ///426 /// # Arguments:427 /// - `collection_id`: RMRK collection ID to change the issuer of.428 /// - `new_issuer`: Collection's new issuer.429 #[transactional]430 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]431 pub fn change_collection_issuer(432 origin: OriginFor<T>,433 collection_id: RmrkCollectionId,434 new_issuer: <T::Lookup as StaticLookup>::Source,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437438 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;439 collection.check_is_external()?;440441 let new_issuer = T::Lookup::lookup(new_issuer)?;442443 Self::change_collection_owner(444 Self::unique_collection_id(collection_id)?,445 misc::CollectionType::Regular,446 sender.clone(),447 new_issuer.clone(),448 )?;449450 Self::deposit_event(Event::IssuerChanged {451 old_issuer: sender,452 new_issuer,453 collection_id,454 });455456 Ok(())457 }458459 /// "Lock" the collection and prevent new token creation. Cannot be undone.460 /// 461 /// # Permissions:462 /// * Collection issuer463 ///464 /// # Arguments:465 /// - `collection_id`: RMRK ID of the collection to lock.466 #[transactional]467 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]468 pub fn lock_collection(469 origin: OriginFor<T>,470 collection_id: RmrkCollectionId,471 ) -> DispatchResult {472 let sender = ensure_signed(origin)?;473 let cross_sender = T::CrossAccountId::from_sub(sender.clone());474475 let collection = Self::get_typed_nft_collection(476 Self::unique_collection_id(collection_id)?,477 misc::CollectionType::Regular,478 )?;479 collection.check_is_external()?;480481 Self::check_collection_owner(&collection, &cross_sender)?;482483 let token_count = collection.total_supply();484485 let mut collection = collection.into_inner();486 collection.limits.token_limit = Some(token_count);487 collection.save()?;488489 Self::deposit_event(Event::CollectionLocked {490 issuer: sender,491 collection_id,492 });493494 Ok(())495 }496497 /// Mint an NFT in a specified collection.498 ///499 /// # Permissions:500 /// * Collection issuer501 /// 502 /// # Arguments:503 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).504 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.505 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.506 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.507 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.508 /// - `transferable`: Can this NFT be transferred? Cannot be changed.509 /// - `resources`: Resource data to be added to the NFT immediately after minting.510 #[transactional]511 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]512 pub fn mint_nft(513 origin: OriginFor<T>,514 owner: Option<T::AccountId>,515 collection_id: RmrkCollectionId,516 recipient: Option<T::AccountId>,517 royalty_amount: Option<Permill>,518 metadata: RmrkString,519 transferable: bool,520 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let cross_sender = T::CrossAccountId::from_sub(sender.clone());524525 let owner = owner.unwrap_or(sender.clone());526 let cross_owner = T::CrossAccountId::from_sub(owner.clone());527528 let collection = Self::get_typed_nft_collection(529 Self::unique_collection_id(collection_id)?,530 misc::CollectionType::Regular,531 )?;532 collection.check_is_external()?;533534 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {535 recipient: recipient.unwrap_or_else(|| owner.clone()),536 amount,537 });538539 let nft_id = Self::create_nft(540 &cross_sender,541 &cross_owner,542 &collection,543 [544 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,545 Self::encode_rmrk_property(Transferable, &transferable)?,546 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,547 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,548 Self::encode_rmrk_property(Metadata, &metadata)?,549 Self::encode_rmrk_property(Equipped, &false)?,550 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,551 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,552 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,553 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,554 ]555 .into_iter(),556 )557 .map_err(|err| match err {558 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),559 err => Self::map_unique_err_to_proxy(err),560 })?;561562 if let Some(resources) = resources {563 for resource in resources {564 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;565 }566 }567568 Self::deposit_event(Event::NftMinted {569 owner,570 collection_id,571 nft_id: nft_id.0,572 });573574 Ok(())575 }576577 /// Burn an NFT, destroying it and its nested tokens up to the specified limit. 578 /// If the burning budget is exceeded, the transaction is reverted.579 /// 580 /// This is the way to burn a nested token as well.581 /// 582 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).583 /// 584 /// # Permissions:585 /// * Token owner586 /// 587 /// # Arguments:588 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.589 /// - `nft_id`: ID of the NFT to be destroyed.590 /// - `max_burns`: Maximum number of tokens to burn, used for nesting. The transaction 591 /// is reverted if there are more tokens to burn in the nesting tree than this number.592 #[transactional]593 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]594 pub fn burn_nft(595 origin: OriginFor<T>,596 collection_id: RmrkCollectionId,597 nft_id: RmrkNftId,598 max_burns: u32,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender.clone());602603 let collection = Self::get_typed_nft_collection(604 Self::unique_collection_id(collection_id)?,605 misc::CollectionType::Regular,606 )?;607 collection.check_is_external()?;608609 Self::destroy_nft(610 cross_sender,611 Self::unique_collection_id(collection_id)?,612 nft_id.into(),613 max_burns,614 <Error<T>>::NoPermission,615 )616 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;617618 Self::deposit_event(Event::NFTBurned {619 owner: sender,620 nft_id,621 });622623 Ok(())624 }625626 /// Transfer an NFT from an account/NFT A to another account/NFT B.627 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].628 /// 629 /// If the target owner is an NFT owned by another account, then the NFT will enter630 /// the pending state and will have to be accepted by the other account.631 ///632 /// # Permissions:633 /// - Token owner634 /// 635 /// # Arguments:636 /// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.637 /// - `nft_id`: ID of the NFT to be transferred.638 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.639 #[transactional]640 #[pallet::weight(<SelfWeightOf<T>>::send())]641 pub fn send(642 origin: OriginFor<T>,643 rmrk_collection_id: RmrkCollectionId,644 rmrk_nft_id: RmrkNftId,645 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,646 ) -> DispatchResult {647 let sender = ensure_signed(origin.clone())?;648 let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651 let nft_id = rmrk_nft_id.into();652653 let collection =654 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655 collection.check_is_external()?;656657 let token_data =658 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;659660 let from = token_data.owner;661662 ensure!(663 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,664 <Error<T>>::NonTransferable665 );666667 ensure!(668 Self::get_nft_property_decoded::<Option<PendingTarget>>(669 collection_id,670 nft_id,671 RmrkProperty::PendingNftAccept672 )?673 .is_none(),674 <Error<T>>::NoPermission675 );676677 let target_owner;678 let approval_required;679680 match new_owner {681 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {682 target_owner = T::CrossAccountId::from_sub(account_id.clone());683 approval_required = false;684 }685 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(686 target_collection_id,687 target_nft_id,688 ) => {689 let target_collection_id = Self::unique_collection_id(target_collection_id)?;690691 let target_nft_budget = budget::Value::new(NESTING_BUDGET);692693 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(694 target_collection_id,695 target_nft_id.into(),696 Some((collection_id, nft_id)),697 &target_nft_budget,698 )699 .map_err(Self::map_unique_err_to_proxy)?;700701 approval_required = cross_sender != target_nft_owner;702703 if approval_required {704 target_owner = target_nft_owner;705706 <PalletNft<T>>::set_scoped_token_property(707 collection.id,708 nft_id,709 RMRK_SCOPE,710 Self::encode_rmrk_property::<Option<PendingTarget>>(711 PendingNftAccept,712 &Some((target_collection_id, target_nft_id.into())),713 )?,714 )?;715716 Self::insert_pending_child(717 (target_collection_id, target_nft_id.into()),718 (rmrk_collection_id, rmrk_nft_id),719 )?;720 } else {721 target_owner = T::CrossTokenAddressMapping::token_to_address(722 target_collection_id,723 target_nft_id.into(),724 );725 }726 }727 }728729 let src_nft_budget = budget::Value::new(NESTING_BUDGET);730731 <PalletNft<T>>::transfer_from(732 &collection,733 &cross_sender,734 &from,735 &target_owner,736 nft_id,737 &src_nft_budget,738 )739 .map_err(Self::map_unique_err_to_proxy)?;740741 Self::deposit_event(Event::NFTSent {742 sender,743 recipient: new_owner,744 collection_id: rmrk_collection_id,745 nft_id: rmrk_nft_id,746 approval_required,747 });748749 Ok(())750 }751752 /// Accept an NFT sent from another account to self or an owned NFT.753 /// 754 /// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.755 /// 756 /// # Permissions:757 /// - Token-owner-to-be758 ///759 /// # Arguments:760 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.761 /// - `rmrk_nft_id`: ID of the NFT to be accepted.762 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT, 763 /// whichever the accepted NFT was sent to.764 #[transactional]765 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]766 pub fn accept_nft(767 origin: OriginFor<T>,768 rmrk_collection_id: RmrkCollectionId,769 rmrk_nft_id: RmrkNftId,770 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,771 ) -> DispatchResult {772 let sender = ensure_signed(origin.clone())?;773 let cross_sender = T::CrossAccountId::from_sub(sender.clone());774775 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;776 let nft_id = rmrk_nft_id.into();777778 let collection =779 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;780 collection.check_is_external()?;781782 let new_cross_owner = match new_owner {783 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {784 T::CrossAccountId::from_sub(account_id.clone())785 }786 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(787 target_collection_id,788 target_nft_id,789 ) => {790 let target_collection_id = Self::unique_collection_id(target_collection_id)?;791792 T::CrossTokenAddressMapping::token_to_address(793 target_collection_id,794 TokenId(target_nft_id),795 )796 }797 };798799 let budget = budget::Value::new(NESTING_BUDGET);800801 <PalletNft<T>>::transfer(802 &collection,803 &cross_sender,804 &new_cross_owner,805 nft_id,806 &budget,807 )808 .map_err(|err| {809 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {810 <Error<T>>::CannotAcceptNonOwnedNft.into()811 } else {812 Self::map_unique_err_to_proxy(err)813 }814 })?;815816 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(817 collection_id,818 nft_id,819 RmrkProperty::PendingNftAccept,820 )?;821822 if let Some(pending_target) = pending_target {823 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;824825 <PalletNft<T>>::set_scoped_token_property(826 collection.id,827 nft_id,828 RMRK_SCOPE,829 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,830 )?;831 }832833 Self::deposit_event(Event::NFTAccepted {834 sender,835 recipient: new_owner,836 collection_id: rmrk_collection_id,837 nft_id: rmrk_nft_id,838 });839840 Ok(())841 }842843 /// Reject an NFT sent from another account to self or owned NFT.844 /// The NFT in question will not be sent back and burnt instead.845 /// 846 /// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.847 /// 848 /// # Permissions:849 /// - Token-owner-to-be-not850 /// 851 /// # Arguments:852 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.853 /// - `rmrk_nft_id`: ID of the NFT to be rejected.854 #[transactional]855 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]856 pub fn reject_nft(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 ) -> DispatchResult {861 let sender = ensure_signed(origin)?;862 let cross_sender = T::CrossAccountId::from_sub(sender.clone());863864 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865 let nft_id = rmrk_nft_id.into();866867 let collection =868 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869 collection.check_is_external()?;870871 ensure!(872 <TokenData<T>>::get((collection_id, nft_id)).is_some(),873 <Error<T>>::NoAvailableNftId874 );875876 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(877 collection_id,878 nft_id,879 RmrkProperty::PendingNftAccept,880 )?;881882 match pending_target {883 Some(pending_target) => {884 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?885 }886 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),887 }888889 Self::destroy_nft(890 cross_sender,891 collection_id,892 nft_id,893 NESTING_BUDGET,894 <Error<T>>::CannotRejectNonOwnedNft,895 )896 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;897898 Self::deposit_event(Event::NFTRejected {899 sender,900 collection_id: rmrk_collection_id,901 nft_id: rmrk_nft_id,902 });903904 Ok(())905 }906907 /// Accept the addition of a newly created pending resource to an existing NFT.908 /// 909 /// This transaction is needed when a resource is created and assigned to an NFT910 /// by a non-owner, i.e. the collection issuer, with one of the 911 /// [`add_...` transactions](crate::pallet::Call::add_basic_resource).912 /// 913 /// # Permissions:914 /// - Token owner915 ///916 /// # Arguments:917 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.918 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.919 /// - `resource_id`: ID of the newly created pending resource.920 #[transactional]921 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]922 pub fn accept_resource(923 origin: OriginFor<T>,924 rmrk_collection_id: RmrkCollectionId,925 rmrk_nft_id: RmrkNftId,926 resource_id: RmrkResourceId,927 ) -> DispatchResult {928 let sender = ensure_signed(origin)?;929 let cross_sender = T::CrossAccountId::from_sub(sender);930931 let collection_id = Self::unique_collection_id(rmrk_collection_id)932 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;933 let collection =934 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935 collection.check_is_external()?;936937 let nft_id = rmrk_nft_id.into();938939 let budget = budget::Value::new(NESTING_BUDGET);940941 let nft_owner =942 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)943 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;944945 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {946 ensure!(res.pending, <Error<T>>::ResourceNotPending);947 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);948949 res.pending = false;950951 Ok(())952 })?;953954 Self::deposit_event(Event::<T>::ResourceAccepted {955 nft_id: rmrk_nft_id,956 resource_id,957 });958959 Ok(())960 }961962 /// Accept the removal of a removal-pending resource from an NFT.963 /// 964 /// This transaction is needed when a non-owner, i.e. the collection issuer, 965 /// requests a [removal](`crate::pallet::Call::remove_resource`) of a resource from an NFT.966 /// 967 /// # Permissions:968 /// - Token owner969 ///970 /// # Arguments:971 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.972 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.973 /// - `resource_id`: ID of the removal-pending resource.974 #[transactional]975 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]976 pub fn accept_resource_removal(977 origin: OriginFor<T>,978 rmrk_collection_id: RmrkCollectionId,979 rmrk_nft_id: RmrkNftId,980 resource_id: RmrkResourceId,981 ) -> DispatchResult {982 let sender = ensure_signed(origin)?;983 let cross_sender = T::CrossAccountId::from_sub(sender);984985 let collection_id = Self::unique_collection_id(rmrk_collection_id)986 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;987 let collection =988 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;989 collection.check_is_external()?;990991 let nft_id = rmrk_nft_id.into();992993 let budget = budget::Value::new(NESTING_BUDGET);994995 let nft_owner =996 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)997 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;998999 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10001001 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10021003 let resource_info = <PalletNft<T>>::token_aux_property((1004 collection_id,1005 nft_id,1006 RMRK_SCOPE,1007 resource_id_key.clone(),1008 ))1009 .ok_or(<Error<T>>::ResourceDoesntExist)?;10101011 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10121013 ensure!(1014 resource_info.pending_removal,1015 <Error<T>>::ResourceNotPending1016 );10171018 <PalletNft<T>>::remove_token_aux_property(1019 collection_id,1020 nft_id,1021 RMRK_SCOPE,1022 resource_id_key,1023 );10241025 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1026 let base_id = resource.base;10271028 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1029 }10301031 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1032 nft_id: rmrk_nft_id,1033 resource_id,1034 });10351036 Ok(())1037 }10381039 /// Add or edit a custom user property, a key-value pair, describing the metadata 1040 /// of a token or a collection, on either one of these.1041 /// 1042 /// Note that in this proxy implementation many details regarding RMRK are stored 1043 /// as scoped properties prefixed with "rmrk:", normally inaccessible 1044 /// to external transactions and RPCs.1045 /// 1046 /// # Permissions:1047 /// - Collection issuer - in case of collection property1048 /// - Token owner - in case of NFT property1049 ///1050 /// # Arguments:1051 /// - `rmrk_collection_id`: RMRK collection ID.1052 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1053 /// - `key`: Key of the custom property to be referenced by.1054 /// - `value`: Value of the custom property to be stored.1055 #[transactional]1056 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1057 pub fn set_property(1058 origin: OriginFor<T>,1059 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1060 maybe_nft_id: Option<RmrkNftId>,1061 key: RmrkKeyString,1062 value: RmrkValueString,1063 ) -> DispatchResult {1064 let sender = ensure_signed(origin)?;1065 let sender = T::CrossAccountId::from_sub(sender);10661067 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1068 let collection =1069 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1070 collection.check_is_external()?;10711072 let budget = budget::Value::new(NESTING_BUDGET);10731074 match maybe_nft_id {1075 Some(nft_id) => {1076 let token_id: TokenId = nft_id.into();10771078 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1079 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;10801081 <PalletNft<T>>::set_scoped_token_property(1082 collection_id,1083 token_id,1084 RMRK_SCOPE,1085 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1086 )?;1087 }1088 None => {1089 let collection = Self::get_typed_nft_collection(1090 collection_id,1091 misc::CollectionType::Regular,1092 )?;10931094 Self::check_collection_owner(&collection, &sender)?;10951096 <PalletCommon<T>>::set_scoped_collection_property(1097 collection_id,1098 RMRK_SCOPE,1099 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1100 )?;1101 }1102 }11031104 Self::deposit_event(Event::PropertySet {1105 collection_id: rmrk_collection_id,1106 maybe_nft_id,1107 key,1108 value,1109 });11101111 Ok(())1112 }11131114 /// Set a different order of resource priorities for an NFT. Priorities can be used,1115 /// for example, for order of rendering.1116 /// 1117 /// Note that the priorities are not updated automatically, and are an empty vector1118 /// by default. There is no pre-set definition for the order to be particular,1119 /// it can be interpreted arbitrarily use-case by use-case.1120 /// 1121 /// # Permissions:1122 /// - Token owner1123 ///1124 /// # Arguments:1125 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1126 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1127 /// - `priorities`: Ordered vector of resource IDs.1128 #[transactional]1129 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1130 pub fn set_priority(1131 origin: OriginFor<T>,1132 rmrk_collection_id: RmrkCollectionId,1133 rmrk_nft_id: RmrkNftId,1134 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1135 ) -> DispatchResult {1136 let sender = ensure_signed(origin)?;1137 let sender = T::CrossAccountId::from_sub(sender);11381139 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1140 let nft_id = rmrk_nft_id.into();11411142 let collection =1143 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1144 collection.check_is_external()?;11451146 let budget = budget::Value::new(NESTING_BUDGET);11471148 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1149 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11501151 <PalletNft<T>>::set_scoped_token_property(1152 collection_id,1153 nft_id,1154 RMRK_SCOPE,1155 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1156 )?;11571158 Self::deposit_event(Event::<T>::PrioritySet {1159 collection_id: rmrk_collection_id,1160 nft_id: rmrk_nft_id,1161 });11621163 Ok(())1164 }11651166 /// Create and set/propose a basic resource for an NFT.1167 /// 1168 /// A resource is considered a part of an NFT, an additional piece of metadata1169 /// usually serving to add a piece of media on top of the root metadata, be it1170 /// a different wing on the root template bird or something entirely unrelated.1171 /// A basic resource is the simplest, lacking a base or composables.1172 /// 1173 /// See RMRK docs for more information and examples.1174 /// 1175 /// # Permissions:1176 /// - Collection issuer - if not the token owner, adding the resource will warrant 1177 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1178 ///1179 /// # Arguments:1180 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1181 /// - `nft_id`: ID of the NFT to assign a resource to.1182 /// - `resource`: Data of the resource to be created.1183 #[transactional]1184 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1185 pub fn add_basic_resource(1186 origin: OriginFor<T>,1187 rmrk_collection_id: RmrkCollectionId,1188 nft_id: RmrkNftId,1189 resource: RmrkBasicResource,1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin.clone())?;11921193 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1194 let collection =1195 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196 collection.check_is_external()?;11971198 let resource_id = Self::resource_add(1199 sender,1200 collection_id,1201 nft_id.into(),1202 RmrkResourceTypes::Basic(resource),1203 )?;12041205 Self::deposit_event(Event::ResourceAdded {1206 nft_id,1207 resource_id,1208 });1209 Ok(())1210 }12111212 /// Create and set/propose a composable resource for an NFT.1213 /// 1214 /// A resource is considered a part of an NFT, an additional piece of metadata1215 /// usually serving to add a piece of media on top of the root metadata, be it1216 /// a different wing on the root template bird or something entirely unrelated.1217 /// A composable resource links to a base and has a subset of its parts it is composed of.1218 /// 1219 /// See RMRK docs for more information and examples.1220 /// 1221 /// # Permissions:1222 /// - Collection issuer - if not the token owner, adding the resource will warrant 1223 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1224 ///1225 /// # Arguments:1226 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1227 /// - `nft_id`: ID of the NFT to assign a resource to.1228 /// - `resource`: Data of the resource to be created.1229 #[transactional]1230 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1231 pub fn add_composable_resource(1232 origin: OriginFor<T>,1233 rmrk_collection_id: RmrkCollectionId,1234 nft_id: RmrkNftId,1235 resource: RmrkComposableResource,1236 ) -> DispatchResult {1237 let sender = ensure_signed(origin.clone())?;12381239 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1240 let collection =1241 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1242 collection.check_is_external()?;12431244 let base_id = resource.base;12451246 let resource_id = Self::resource_add(1247 sender,1248 collection_id,1249 nft_id.into(),1250 RmrkResourceTypes::Composable(resource),1251 )?;12521253 <PalletNft<T>>::try_mutate_token_aux_property(1254 collection_id,1255 nft_id.into(),1256 RMRK_SCOPE,1257 Self::get_scoped_property_key(AssociatedBases)?,1258 |value| -> DispatchResult {1259 let mut bases: BasesMap = match value {1260 Some(value) => Self::decode_property_value(value)?,1261 None => BasesMap::new(),1262 };12631264 *bases.entry(base_id).or_insert(0) += 1;12651266 *value = Some(Self::encode_property_value(&bases)?);1267 Ok(())1268 },1269 )?;12701271 Self::deposit_event(Event::ResourceAdded {1272 nft_id,1273 resource_id,1274 });1275 Ok(())1276 }12771278 /// Create and set/propose a slot resource for an NFT.1279 /// 1280 /// A resource is considered a part of an NFT, an additional piece of metadata1281 /// usually serving to add a piece of media on top of the root metadata, be it1282 /// a different wing on the root template bird or something entirely unrelated.1283 /// A slot resource links to a base and a slot in it which it now occupies.1284 /// 1285 /// See RMRK docs for more information and examples.1286 /// 1287 /// # Permissions:1288 /// - Collection issuer - if not the token owner, adding the resource will warrant 1289 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1290 ///1291 /// # Arguments:1292 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1293 /// - `nft_id`: ID of the NFT to assign a resource to.1294 /// - `resource`: Data of the resource to be created.1295 #[transactional]1296 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1297 pub fn add_slot_resource(1298 origin: OriginFor<T>,1299 rmrk_collection_id: RmrkCollectionId,1300 nft_id: RmrkNftId,1301 resource: RmrkSlotResource,1302 ) -> DispatchResult {1303 let sender = ensure_signed(origin.clone())?;13041305 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1306 let collection =1307 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1308 collection.check_is_external()?;13091310 let resource_id = Self::resource_add(1311 sender,1312 collection_id,1313 nft_id.into(),1314 RmrkResourceTypes::Slot(resource),1315 )?;13161317 Self::deposit_event(Event::ResourceAdded {1318 nft_id,1319 resource_id,1320 });1321 Ok(())1322 }13231324 /// Remove and erase a resource from an NFT.1325 /// 1326 /// If the sender does not own the NFT, then it will be pending confirmation,1327 /// and will have to be [accepted](crate::pallet::Call::accept_resource_removal) by the token owner.1328 /// 1329 /// # Permissions1330 /// - Collection issuer1331 /// 1332 /// # Arguments1333 /// - `collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1334 /// - `nft_id`: ID of the NFT with a resource to be removed.1335 /// - `resource_id`: ID of the resource to be removed.1336 #[transactional]1337 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1338 pub fn remove_resource(1339 origin: OriginFor<T>,1340 rmrk_collection_id: RmrkCollectionId,1341 nft_id: RmrkNftId,1342 resource_id: RmrkResourceId,1343 ) -> DispatchResult {1344 let sender = ensure_signed(origin.clone())?;13451346 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1347 let collection =1348 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1349 collection.check_is_external()?;13501351 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13521353 Self::deposit_event(Event::ResourceRemoval {1354 nft_id,1355 resource_id,1356 });1357 Ok(())1358 }1359 }1360}13611362impl<T: Config> Pallet<T> {1363 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1364 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1365 let key = rmrk_key.to_key::<T>()?;13661367 let scoped_key = RMRK_SCOPE1368 .apply(key)1369 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13701371 Ok(scoped_key)1372 }13731374 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet) 1375 /// and encoding the value from an arbitrary type into bytes.1376 pub fn encode_rmrk_property<E: Encode>(1377 rmrk_key: RmrkProperty,1378 value: &E,1379 ) -> Result<Property, DispatchError> {1380 let key = rmrk_key.to_key::<T>()?;13811382 let value = Self::encode_property_value(value)?;13831384 let property = Property { key, value };13851386 Ok(property)1387 }13881389 /// Encode property value from an arbitrary type into bytes for storage.1390 pub fn encode_property_value<E: Encode, S: Get<u32>>(1391 value: &E,1392 ) -> Result<BoundedBytes<S>, DispatchError> {1393 let value = value1394 .encode()1395 .try_into()1396 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;13971398 Ok(value)1399 }14001401 /// Decode property value from bytes into an arbitrary type.1402 pub fn decode_property_value<D: Decode, S: Get<u32>>(1403 vec: &BoundedBytes<S>,1404 ) -> Result<D, DispatchError> {1405 vec.decode()1406 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1407 }14081409 /// Change the limit of a property value byte vector.1410 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1411 where1412 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1413 {1414 vec.rebind()1415 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1416 }14171418 /// Initialize a new NFT collection with certain RMRK-scoped properties.1419 /// 1420 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1421 fn init_collection(1422 sender: T::CrossAccountId,1423 data: CreateCollectionData<T::AccountId>,1424 properties: impl Iterator<Item = Property>,1425 ) -> Result<CollectionId, DispatchError> {1426 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14271428 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1429 return Err(<Error<T>>::NoAvailableCollectionId.into());1430 }14311432 <PalletCommon<T>>::set_scoped_collection_properties(1433 collection_id?,1434 RMRK_SCOPE,1435 properties,1436 )?;14371438 collection_id1439 }14401441 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1442 /// 1443 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1444 pub fn create_nft(1445 sender: &T::CrossAccountId,1446 owner: &T::CrossAccountId,1447 collection: &NonfungibleHandle<T>,1448 properties: impl Iterator<Item = Property>,1449 ) -> Result<TokenId, DispatchError> {1450 let data = CreateNftExData {1451 properties: BoundedVec::default(),1452 owner: owner.clone(),1453 };14541455 let budget = budget::Value::new(NESTING_BUDGET);14561457 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14581459 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14601461 <PalletNft<T>>::set_scoped_token_properties(1462 collection.id,1463 nft_id,1464 RMRK_SCOPE,1465 properties,1466 )?;14671468 Ok(nft_id)1469 }14701471 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1472 /// 1473 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1474 fn destroy_nft(1475 sender: T::CrossAccountId,1476 collection_id: CollectionId,1477 token_id: TokenId,1478 max_burns: u32,1479 error_if_not_owned: Error<T>,1480 ) -> DispatchResultWithPostInfo {1481 let collection =1482 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14831484 let token_data =1485 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14861487 let from = token_data.owner;14881489 let owner_check_budget = budget::Value::new(NESTING_BUDGET);14901491 ensure!(1492 <PalletStructure<T>>::check_indirectly_owned(1493 sender.clone(),1494 collection_id,1495 token_id,1496 None,1497 &owner_check_budget1498 )?,1499 error_if_not_owned,1500 );15011502 let burns_budget = budget::Value::new(max_burns);1503 let breadth_budget = budget::Value::new(max_burns);15041505 <PalletNft<T>>::burn_recursively(1506 &collection,1507 &from,1508 token_id,1509 &burns_budget,1510 &breadth_budget,1511 )1512 }15131514 /// Add a sent token pending acceptance to the target owning token as a property.1515 fn insert_pending_child(1516 target: (CollectionId, TokenId),1517 child: (RmrkCollectionId, RmrkNftId),1518 ) -> DispatchResult {1519 Self::mutate_pending_children(target, |pending_children| {1520 pending_children.insert(child);1521 })1522 }15231524 /// Remove a sent token pending acceptance from the target token's properties.1525 fn remove_pending_child(1526 target: (CollectionId, TokenId),1527 child: (RmrkCollectionId, RmrkNftId),1528 ) -> DispatchResult {1529 Self::mutate_pending_children(target, |pending_children| {1530 pending_children.remove(&child);1531 })1532 }15331534 /// Apply a mutation to the property of a token containing sent tokens 1535 /// that are currently pending acceptance.1536 fn mutate_pending_children(1537 (target_collection_id, target_nft_id): (CollectionId, TokenId),1538 f: impl FnOnce(&mut PendingChildrenSet),1539 ) -> DispatchResult {1540 <PalletNft<T>>::try_mutate_token_aux_property(1541 target_collection_id,1542 target_nft_id,1543 RMRK_SCOPE,1544 Self::get_scoped_property_key(PendingChildren)?,1545 |pending_children| -> DispatchResult {1546 let mut map = match pending_children {1547 Some(map) => Self::decode_property_value(map)?,1548 None => PendingChildrenSet::new(),1549 };15501551 f(&mut map);15521553 *pending_children = Some(Self::encode_property_value(&map)?);15541555 Ok(())1556 },1557 )1558 }15591560 /// Get an iterator from a token's property containing tokens sent to it 1561 /// that are currently pending acceptance.1562 fn iterate_pending_children(1563 collection_id: CollectionId,1564 nft_id: TokenId,1565 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1566 let property = <PalletNft<T>>::token_aux_property((1567 collection_id,1568 nft_id,1569 RMRK_SCOPE,1570 Self::get_scoped_property_key(PendingChildren)?,1571 ));15721573 let pending_children = match property {1574 Some(map) => Self::decode_property_value(&map)?,1575 None => PendingChildrenSet::new(),1576 };15771578 Ok(pending_children.into_iter())1579 }15801581 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1582 /// Thus, the returned resource ID should be used.1583 fn acquire_next_resource_id(1584 collection_id: CollectionId,1585 nft_id: TokenId,1586 ) -> Result<RmrkResourceId, DispatchError> {1587 let resource_id: RmrkResourceId =1588 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15891590 let next_id = resource_id1591 .checked_add(1)1592 .ok_or(<Error<T>>::NoAvailableResourceId)?;15931594 <PalletNft<T>>::set_scoped_token_property(1595 collection_id,1596 nft_id,1597 RMRK_SCOPE,1598 Self::encode_rmrk_property(NextResourceId, &next_id)?,1599 )?;16001601 Ok(resource_id)1602 }16031604 /// Create and add a resource for a regular NFT, mark it as pending if the sender 1605 /// is not the token owner. The sender must be the collection owner.1606 fn resource_add(1607 sender: T::AccountId,1608 collection_id: CollectionId,1609 nft_id: TokenId,1610 resource: RmrkResourceTypes,1611 ) -> Result<RmrkResourceId, DispatchError> {1612 let collection =1613 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1614 ensure!(collection.owner == sender, Error::<T>::NoPermission);16151616 let sender = T::CrossAccountId::from_sub(sender);1617 let budget = budget::Value::new(NESTING_BUDGET);16181619 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1620 .map_err(Self::map_unique_err_to_proxy)?;16211622 let pending = sender != nft_owner;16231624 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16251626 let resource_info = RmrkResourceInfo {1627 id,1628 resource,1629 pending,1630 pending_removal: false,1631 };16321633 <PalletNft<T>>::try_mutate_token_aux_property(1634 collection_id,1635 nft_id,1636 RMRK_SCOPE,1637 Self::get_scoped_property_key(ResourceId(id))?,1638 |value| -> DispatchResult {1639 *value = Some(Self::encode_property_value(&resource_info)?);16401641 Ok(())1642 },1643 )?;16441645 Ok(id)1646 }16471648 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1649 /// The sender must be the collection owner.1650 fn resource_remove(1651 sender: T::AccountId,1652 collection_id: CollectionId,1653 nft_id: TokenId,1654 resource_id: RmrkResourceId,1655 ) -> DispatchResult {1656 let collection =1657 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1658 ensure!(collection.owner == sender, Error::<T>::NoPermission);16591660 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16611662 let resource = <PalletNft<T>>::token_aux_property((1663 collection_id,1664 nft_id,1665 RMRK_SCOPE,1666 resource_id_key.clone(),1667 ))1668 .ok_or(<Error<T>>::ResourceDoesntExist)?;16691670 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16711672 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1673 let topmost_owner =1674 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16751676 let sender = T::CrossAccountId::from_sub(sender);1677 if topmost_owner == sender {1678 <PalletNft<T>>::remove_token_aux_property(1679 collection_id,1680 nft_id,1681 RMRK_SCOPE,1682 Self::get_scoped_property_key(ResourceId(resource_id))?,1683 );16841685 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1686 let base_id = resource.base;16871688 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1689 }1690 } else {1691 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1692 res.pending_removal = true;16931694 Ok(())1695 })?;1696 }16971698 Ok(())1699 }17001701 /// Remove one usage of a base from an NFT's property of associated bases. The base will stay, however, 1702 /// if the count of resources using the base is still non-zero.1703 fn remove_associated_base_id(1704 collection_id: CollectionId,1705 nft_id: TokenId,1706 base_id: RmrkBaseId,1707 ) -> DispatchResult {1708 <PalletNft<T>>::try_mutate_token_aux_property(1709 collection_id,1710 nft_id,1711 RMRK_SCOPE,1712 Self::get_scoped_property_key(AssociatedBases)?,1713 |value| -> DispatchResult {1714 let mut bases: BasesMap = match value {1715 Some(value) => Self::decode_property_value(value)?,1716 None => BasesMap::new(),1717 };17181719 let remaining = bases.get(&base_id);17201721 if let Some(remaining) = remaining {1722 if let Some(0) | None = remaining.checked_sub(1) {1723 bases.remove(&base_id);1724 }1725 }17261727 *value = Some(Self::encode_property_value(&bases)?);1728 Ok(())1729 },1730 )1731 }17321733 /// Apply a mutation to a resource stored in the token properties of an NFT.1734 fn try_mutate_resource_info(1735 collection_id: CollectionId,1736 nft_id: TokenId,1737 resource_id: RmrkResourceId,1738 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,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(ResourceId(resource_id))?,1745 |value| match value {1746 Some(value) => {1747 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17481749 f(&mut resource_info)?;17501751 *value = Self::encode_property_value(&resource_info)?;17521753 Ok(())1754 }1755 None => Err(<Error<T>>::ResourceDoesntExist.into()),1756 },1757 )1758 }17591760 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1761 fn change_collection_owner(1762 collection_id: CollectionId,1763 collection_type: misc::CollectionType,1764 sender: T::AccountId,1765 new_owner: T::AccountId,1766 ) -> DispatchResult {1767 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1768 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17691770 let mut collection = collection.into_inner();17711772 collection.owner = new_owner;1773 collection.save()1774 }17751776 /// Ensure that an account is the collection owner/issuer, return an error if not.1777 pub fn check_collection_owner(1778 collection: &NonfungibleHandle<T>,1779 account: &T::CrossAccountId,1780 ) -> DispatchResult {1781 collection1782 .check_is_owner(account)1783 .map_err(Self::map_unique_err_to_proxy)1784 }17851786 /// Get the latest yet-unused RMRK collection index from the storage.1787 pub fn last_collection_idx() -> RmrkCollectionId {1788 <CollectionIndex<T>>::get()1789 }17901791 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1792 pub fn unique_collection_id(1793 rmrk_collection_id: RmrkCollectionId,1794 ) -> Result<CollectionId, DispatchError> {1795 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1796 .map_err(|_| <Error<T>>::CollectionUnknown.into())1797 }17981799 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1800 pub fn rmrk_collection_id(1801 unique_collection_id: CollectionId,1802 ) -> Result<RmrkCollectionId, DispatchError> {1803 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1804 }18051806 /// Fetch a Unique NFT collection.1807 pub fn get_nft_collection(1808 collection_id: CollectionId,1809 ) -> Result<NonfungibleHandle<T>, DispatchError> {1810 let collection = <CollectionHandle<T>>::try_get(collection_id)1811 .map_err(|_| <Error<T>>::CollectionUnknown)?;18121813 match collection.mode {1814 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1815 _ => Err(<Error<T>>::CollectionUnknown.into()),1816 }1817 }18181819 /// Check if an NFT collection with such an ID exists.1820 pub fn collection_exists(collection_id: CollectionId) -> bool {1821 <CollectionHandle<T>>::try_get(collection_id).is_ok()1822 }18231824 /// Fetch and decode a RMRK-scoped collection property value in bytes.1825 pub fn get_collection_property(1826 collection_id: CollectionId,1827 key: RmrkProperty,1828 ) -> Result<PropertyValue, DispatchError> {1829 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1830 .get(&Self::get_scoped_property_key(key)?)1831 .ok_or(<Error<T>>::CollectionUnknown)?1832 .clone();18331834 Ok(collection_property)1835 }18361837 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1838 pub fn get_collection_property_decoded<V: Decode>(1839 collection_id: CollectionId,1840 key: RmrkProperty,1841 ) -> Result<V, DispatchError> {1842 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1843 }18441845 /// Get the type of a collection stored in it as a scoped property.1846 /// 1847 /// RMRK Core proxy differentiates between regular collections as well as RMRK bases as collections.1848 pub fn get_collection_type(1849 collection_id: CollectionId,1850 ) -> Result<misc::CollectionType, DispatchError> {1851 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1852 if err != <Error<T>>::CollectionUnknown.into() {1853 <Error<T>>::CorruptedCollectionType.into()1854 } else {1855 err1856 }1857 })1858 }18591860 /// Ensure that the type of the collection equals the provided type,1861 /// otherwise return an error.1862 pub fn ensure_collection_type(1863 collection_id: CollectionId,1864 collection_type: misc::CollectionType,1865 ) -> DispatchResult {1866 let actual_type = Self::get_collection_type(collection_id)?;1867 ensure!(1868 actual_type == collection_type,1869 <CommonError<T>>::NoPermission1870 );18711872 Ok(())1873 }18741875 /// Fetch an NFT collection, but make sure it has the appropriate type.1876 pub fn get_typed_nft_collection(1877 collection_id: CollectionId,1878 collection_type: misc::CollectionType,1879 ) -> Result<NonfungibleHandle<T>, DispatchError> {1880 Self::ensure_collection_type(collection_id, collection_type)?;18811882 Self::get_nft_collection(collection_id)1883 }18841885 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection), 1886 /// but also return the Unique collection ID.1887 pub fn get_typed_nft_collection_mapped(1888 rmrk_collection_id: RmrkCollectionId,1889 collection_type: misc::CollectionType,1890 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1891 let unique_collection_id = match collection_type {1892 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1893 _ => rmrk_collection_id.into(),1894 };18951896 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18971898 Ok((collection, unique_collection_id))1899 }19001901 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1902 pub fn get_nft_property(1903 collection_id: CollectionId,1904 nft_id: TokenId,1905 key: RmrkProperty,1906 ) -> Result<PropertyValue, DispatchError> {1907 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1908 .get(&Self::get_scoped_property_key(key)?)1909 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1910 .clone();19111912 Ok(nft_property)1913 }19141915 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1916 pub fn get_nft_property_decoded<V: Decode>(1917 collection_id: CollectionId,1918 nft_id: TokenId,1919 key: RmrkProperty,1920 ) -> Result<V, DispatchError> {1921 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1922 }19231924 /// Check that an NFT exists.1925 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1926 <TokenData<T>>::contains_key((collection_id, nft_id))1927 }19281929 /// Get the type of an NFT stored in it as a scoped property.1930 /// 1931 /// RMRK Core proxy differentiates between regular NFTs, and RMRK parts and themes.1932 pub fn get_nft_type(1933 collection_id: CollectionId,1934 token_id: TokenId,1935 ) -> Result<NftType, DispatchError> {1936 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1937 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1938 }19391940 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1941 pub fn ensure_nft_type(1942 collection_id: CollectionId,1943 token_id: TokenId,1944 nft_type: NftType,1945 ) -> DispatchResult {1946 let actual_type = Self::get_nft_type(collection_id, token_id)?;1947 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19481949 Ok(())1950 }19511952 /// Ensure that an account is the owner of the token, either directly 1953 /// or at the top of the nesting hierarchy; return an error if it is not.1954 pub fn ensure_nft_owner(1955 collection_id: CollectionId,1956 token_id: TokenId,1957 possible_owner: &T::CrossAccountId,1958 nesting_budget: &dyn budget::Budget,1959 ) -> DispatchResult {1960 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1961 possible_owner.clone(),1962 collection_id,1963 token_id,1964 None,1965 nesting_budget,1966 )1967 .map_err(Self::map_unique_err_to_proxy)?;19681969 ensure!(is_owned, <Error<T>>::NoPermission);19701971 Ok(())1972 }19731974 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied, 1975 /// or, if None are provided, return all non-scoped properties.1976 pub fn filter_user_properties<Key, Value, R, Mapper>(1977 collection_id: CollectionId,1978 token_id: Option<TokenId>,1979 filter_keys: Option<Vec<RmrkPropertyKey>>,1980 mapper: Mapper,1981 ) -> Result<Vec<R>, DispatchError>1982 where1983 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1984 Value: Decode + Default,1985 Mapper: Fn(Key, Value) -> R,1986 {1987 filter_keys1988 .map(|keys| {1989 let properties = keys1990 .into_iter()1991 .filter_map(|key| {1992 let key: Key = key.try_into().ok()?;19931994 let value = match token_id {1995 Some(token_id) => Self::get_nft_property_decoded(1996 collection_id,1997 token_id,1998 UserProperty(key.as_ref()),1999 ),2000 None => Self::get_collection_property_decoded(2001 collection_id,2002 UserProperty(key.as_ref()),2003 ),2004 }2005 .ok()?;20062007 Some(mapper(key, value))2008 })2009 .collect();20102011 Ok(properties)2012 })2013 .unwrap_or_else(|| {2014 let properties =2015 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20162017 Ok(properties)2018 })2019 }20202021 /// Get all non-scoped properties from a collection or a token, and apply some transformation 2022 /// to each key-value pair.2023 pub fn iterate_user_properties<Key, Value, R, Mapper>(2024 collection_id: CollectionId,2025 token_id: Option<TokenId>,2026 mapper: Mapper,2027 ) -> Result<impl Iterator<Item = R>, DispatchError>2028 where2029 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2030 Value: Decode + Default,2031 Mapper: Fn(Key, Value) -> R,2032 {2033 let properties = match token_id {2034 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2035 None => <PalletCommon<T>>::collection_properties(collection_id),2036 };20372038 let properties = properties.into_iter().filter_map(move |(key, value)| {2039 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20402041 let key: Key = key.to_vec().try_into().ok()?;2042 let value: Value = value.decode().ok()?;20432044 Some(mapper(key, value))2045 });20462047 Ok(properties)2048 }20492050 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2051 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2052 map_unique_err_to_proxy! {2053 match err {2054 CommonError::NoPermission => NoPermission,2055 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2056 CommonError::PublicMintingNotAllowed => NoPermission,2057 CommonError::TokenNotFound => NoAvailableNftId,2058 CommonError::ApprovedValueTooLow => NoPermission,2059 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2060 StructureError::TokenNotFound => NoAvailableNftId,2061 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2062 }2063 }2064 }2065}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>52//!53//! ## Proxy Implementation54//!55//! An external user is supposed to be able to utilize this proxy as they would56//! utilize RMRK, and get exactly the same results. Normally, Unique transactions57//! are off-limits to RMRK collections and tokens, and vice versa. However,58//! the information stored on chain can be freely interpreted by storage reads and RPCs.59//!60//! ### ID Mapping61//!62//! RMRK's collections' IDs are counted independently of Unique's and start at 0.63//! Note that tokens' IDs still start at 1.64//! The collections themselves, as well as tokens, are stored as Unique collections,65//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).66//!67//! ### External/Internal Collection Insulation68//!69//! A Unique transaction cannot target collections purposed for RMRK,70//! and they are flagged as `external` to specify that. On the other hand,71//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.72//!73//! ### Native Properties74//!75//! Many of RMRK's native parameters are stored as scoped properties of a collection76//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`77//! is an unacceptable symbol in user-defined proeprties, which, along with other safeguards,78//! makes them impossible to tamper with.79//!80//! ### Collection and NFT Types81//!82//! RMRK introduces the concept of a Base, which is a catalgoue of Parts,83//! possible components of an NFT. Due to its similarity with the functionality84//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes85//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and86//! [`NftType`](pallet_rmrk_core::misc::NftType).87//!88//! ## Interface89//!90//! ### Dispatchables91//!92//! - `create_collection` - Create a new collection of NFTs.93//! - `destroy_collection` - Destroy a collection.94//! - `change_collection_issuer` - Change the issuer of a collection.95//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).96//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**97//! - `mint_nft` - Mint an NFT in a specified collection.98//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.99//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.100//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.101//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.102//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.103//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.104//! - `set_property` - Add or edit a custom user property of a token or a collection.105//! - `set_priority` - Set a different order of resource priorities for an NFT.106//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.107//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.108//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.109//! - `remove_resource` - Remove and erase a resource from an NFT.110111#![cfg_attr(not(feature = "std"), no_std)]112113use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};114use frame_system::{pallet_prelude::*, ensure_signed};115use sp_runtime::{DispatchError, Permill, traits::StaticLookup};116use sp_std::{117 vec::Vec,118 collections::{btree_set::BTreeSet, btree_map::BTreeMap},119};120use up_data_structs::{*, mapping::TokenAddressMapping};121use pallet_common::{122 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,123};124use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};125use pallet_structure::{Pallet as PalletStructure, Error as StructureError};126use pallet_evm::account::CrossAccountId;127use core::convert::AsRef;128129pub use pallet::*;130131#[cfg(feature = "runtime-benchmarks")]132pub mod benchmarking;133pub mod misc;134pub mod property;135pub mod rpc;136pub mod weights;137138pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140use weights::WeightInfo;141use misc::*;142pub use property::*;143144use RmrkProperty::*;145146/// Maximum number of levels of depth in the token nesting tree.147pub const NESTING_BUDGET: u32 = 5;148149type PendingTarget = (CollectionId, TokenId);150type PendingChild = (RmrkCollectionId, RmrkNftId);151type PendingChildrenSet = BTreeSet<PendingChild>;152153type BasesMap = BTreeMap<RmrkBaseId, u32>;154155#[frame_support::pallet]156pub mod pallet {157 use super::*;158 use pallet_evm::account;159160 #[pallet::config]161 pub trait Config:162 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config163 {164 /// Overarching event type.165 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;166167 /// The weight information of this pallet.168 type WeightInfo: WeightInfo;169 }170171 /// Latest yet-unused collection ID.172 #[pallet::storage]173 #[pallet::getter(fn collection_index)]174 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;175176 /// Mapping from RMRK collection ID to Unique's.177 #[pallet::storage]178 pub type UniqueCollectionId<T: Config> =179 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;180181 #[pallet::pallet]182 #[pallet::generate_store(pub(super) trait Store)]183 pub struct Pallet<T>(_);184185 #[pallet::event]186 #[pallet::generate_deposit(pub(super) fn deposit_event)]187 pub enum Event<T: Config> {188 CollectionCreated {189 issuer: T::AccountId,190 collection_id: RmrkCollectionId,191 },192 CollectionDestroyed {193 issuer: T::AccountId,194 collection_id: RmrkCollectionId,195 },196 IssuerChanged {197 old_issuer: T::AccountId,198 new_issuer: T::AccountId,199 collection_id: RmrkCollectionId,200 },201 CollectionLocked {202 issuer: T::AccountId,203 collection_id: RmrkCollectionId,204 },205 NftMinted {206 owner: T::AccountId,207 collection_id: RmrkCollectionId,208 nft_id: RmrkNftId,209 },210 NFTBurned {211 owner: T::AccountId,212 nft_id: RmrkNftId,213 },214 NFTSent {215 sender: T::AccountId,216 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,217 collection_id: RmrkCollectionId,218 nft_id: RmrkNftId,219 approval_required: bool,220 },221 NFTAccepted {222 sender: T::AccountId,223 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,224 collection_id: RmrkCollectionId,225 nft_id: RmrkNftId,226 },227 NFTRejected {228 sender: T::AccountId,229 collection_id: RmrkCollectionId,230 nft_id: RmrkNftId,231 },232 PropertySet {233 collection_id: RmrkCollectionId,234 maybe_nft_id: Option<RmrkNftId>,235 key: RmrkKeyString,236 value: RmrkValueString,237 },238 ResourceAdded {239 nft_id: RmrkNftId,240 resource_id: RmrkResourceId,241 },242 ResourceRemoval {243 nft_id: RmrkNftId,244 resource_id: RmrkResourceId,245 },246 ResourceAccepted {247 nft_id: RmrkNftId,248 resource_id: RmrkResourceId,249 },250 ResourceRemovalAccepted {251 nft_id: RmrkNftId,252 resource_id: RmrkResourceId,253 },254 PrioritySet {255 collection_id: RmrkCollectionId,256 nft_id: RmrkNftId,257 },258 }259260 #[pallet::error]261 pub enum Error<T> {262 /* Unique proxy-specific events */263 /// Property of the type of RMRK collection could not be read successfully.264 CorruptedCollectionType,265 // NftTypeEncodeError,266 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).267 RmrkPropertyKeyIsTooLong,268 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).269 RmrkPropertyValueIsTooLong,270 /// Could not find a property by the supplied key.271 RmrkPropertyIsNotFound,272 /// Something went wrong when decoding encoded data from the storage.273 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.274 UnableToDecodeRmrkData,275276 /* RMRK compatible events */277 /// Only destroying collections without tokens is allowed.278 CollectionNotEmpty,279 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain.280 NoAvailableCollectionId,281 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection.282 NoAvailableNftId,283 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.284 CollectionUnknown,285 /// No permission to perform action.286 NoPermission,287 /// Token is marked as non-transferable, and thus cannot be transferred.288 NonTransferable,289 /// Too many tokens created in the collection, no new ones are allowed.290 CollectionFullOrLocked,291 /// No such resource found.292 ResourceDoesntExist,293 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.294 /// Sending to self is redundant.295 CannotSendToDescendentOrSelf,296 /// Not the target owner of the sent NFT.297 CannotAcceptNonOwnedNft,298 /// Not the target owner of the sent NFT.299 CannotRejectNonOwnedNft,300 /// NFT was not sent and is not pending.301 CannotRejectNonPendingNft,302 /// Resource is not pending for the operation.303 ResourceNotPending,304 /// Could not find an ID for the resource. Is is likely there were too many resources created on an NFT.305 NoAvailableResourceId,306 }307308 #[pallet::call]309 impl<T: Config> Pallet<T> {310 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?311312 /// Create a new collection of NFTs.313 ///314 /// # Permissions:315 /// * Anyone - will be assigned as the issuer of the collection.316 ///317 /// # Arguments:318 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.319 /// - `max`: Optional maximum number of tokens.320 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.321 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.322 #[transactional]323 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]324 pub fn create_collection(325 origin: OriginFor<T>,326 metadata: RmrkString,327 max: Option<u32>,328 symbol: RmrkCollectionSymbol,329 ) -> DispatchResult {330 let sender = ensure_signed(origin)?;331332 let limits = CollectionLimits {333 owner_can_transfer: Some(false),334 token_limit: max,335 ..Default::default()336 };337338 let data = CreateCollectionData {339 limits: Some(limits),340 token_prefix: symbol341 .into_inner()342 .try_into()343 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,344 permissions: Some(CollectionPermissions {345 nesting: Some(NestingPermissions {346 token_owner: true,347 collection_admin: false,348 restricted: None,349 #[cfg(feature = "runtime-benchmarks")]350 permissive: false,351 }),352 ..Default::default()353 }),354 ..Default::default()355 };356357 let unique_collection_id = Self::init_collection(358 T::CrossAccountId::from_sub(sender.clone()),359 data,360 [361 Self::encode_rmrk_property(Metadata, &metadata)?,362 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,363 ]364 .into_iter(),365 )?;366 let rmrk_collection_id = <CollectionIndex<T>>::get();367368 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);369370 <PalletCommon<T>>::set_scoped_collection_property(371 unique_collection_id,372 RMRK_SCOPE,373 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,374 )?;375376 <CollectionIndex<T>>::mutate(|n| *n += 1);377378 Self::deposit_event(Event::CollectionCreated {379 issuer: sender,380 collection_id: rmrk_collection_id,381 });382383 Ok(())384 }385386 /// Destroy a collection.387 ///388 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.389 ///390 /// # Permissions:391 /// * Collection issuer392 ///393 /// # Arguments:394 /// - `collection_id`: RMRK ID of the collection to destroy.395 #[transactional]396 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]397 pub fn destroy_collection(398 origin: OriginFor<T>,399 collection_id: RmrkCollectionId,400 ) -> DispatchResult {401 let sender = ensure_signed(origin)?;402 let cross_sender = T::CrossAccountId::from_sub(sender.clone());403404 let collection = Self::get_typed_nft_collection(405 Self::unique_collection_id(collection_id)?,406 misc::CollectionType::Regular,407 )?;408 collection.check_is_external()?;409410 <PalletNft<T>>::destroy_collection(collection, &cross_sender)411 .map_err(Self::map_unique_err_to_proxy)?;412413 Self::deposit_event(Event::CollectionDestroyed {414 issuer: sender,415 collection_id,416 });417418 Ok(())419 }420421 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).422 ///423 /// # Permissions:424 /// * Collection issuer425 ///426 /// # Arguments:427 /// - `collection_id`: RMRK collection ID to change the issuer of.428 /// - `new_issuer`: Collection's new issuer.429 #[transactional]430 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]431 pub fn change_collection_issuer(432 origin: OriginFor<T>,433 collection_id: RmrkCollectionId,434 new_issuer: <T::Lookup as StaticLookup>::Source,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437438 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;439 collection.check_is_external()?;440441 let new_issuer = T::Lookup::lookup(new_issuer)?;442443 Self::change_collection_owner(444 Self::unique_collection_id(collection_id)?,445 misc::CollectionType::Regular,446 sender.clone(),447 new_issuer.clone(),448 )?;449450 Self::deposit_event(Event::IssuerChanged {451 old_issuer: sender,452 new_issuer,453 collection_id,454 });455456 Ok(())457 }458459 /// "Lock" the collection and prevent new token creation. Cannot be undone.460 ///461 /// # Permissions:462 /// * Collection issuer463 ///464 /// # Arguments:465 /// - `collection_id`: RMRK ID of the collection to lock.466 #[transactional]467 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]468 pub fn lock_collection(469 origin: OriginFor<T>,470 collection_id: RmrkCollectionId,471 ) -> DispatchResult {472 let sender = ensure_signed(origin)?;473 let cross_sender = T::CrossAccountId::from_sub(sender.clone());474475 let collection = Self::get_typed_nft_collection(476 Self::unique_collection_id(collection_id)?,477 misc::CollectionType::Regular,478 )?;479 collection.check_is_external()?;480481 Self::check_collection_owner(&collection, &cross_sender)?;482483 let token_count = collection.total_supply();484485 let mut collection = collection.into_inner();486 collection.limits.token_limit = Some(token_count);487 collection.save()?;488489 Self::deposit_event(Event::CollectionLocked {490 issuer: sender,491 collection_id,492 });493494 Ok(())495 }496497 /// Mint an NFT in a specified collection.498 ///499 /// # Permissions:500 /// * Collection issuer501 ///502 /// # Arguments:503 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).504 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.505 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.506 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.507 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.508 /// - `transferable`: Can this NFT be transferred? Cannot be changed.509 /// - `resources`: Resource data to be added to the NFT immediately after minting.510 #[transactional]511 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]512 pub fn mint_nft(513 origin: OriginFor<T>,514 owner: Option<T::AccountId>,515 collection_id: RmrkCollectionId,516 recipient: Option<T::AccountId>,517 royalty_amount: Option<Permill>,518 metadata: RmrkString,519 transferable: bool,520 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let cross_sender = T::CrossAccountId::from_sub(sender.clone());524525 let owner = owner.unwrap_or(sender.clone());526 let cross_owner = T::CrossAccountId::from_sub(owner.clone());527528 let collection = Self::get_typed_nft_collection(529 Self::unique_collection_id(collection_id)?,530 misc::CollectionType::Regular,531 )?;532 collection.check_is_external()?;533534 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {535 recipient: recipient.unwrap_or_else(|| owner.clone()),536 amount,537 });538539 let nft_id = Self::create_nft(540 &cross_sender,541 &cross_owner,542 &collection,543 [544 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,545 Self::encode_rmrk_property(Transferable, &transferable)?,546 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,547 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,548 Self::encode_rmrk_property(Metadata, &metadata)?,549 Self::encode_rmrk_property(Equipped, &false)?,550 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,551 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,552 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,553 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,554 ]555 .into_iter(),556 )557 .map_err(|err| match err {558 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),559 err => Self::map_unique_err_to_proxy(err),560 })?;561562 if let Some(resources) = resources {563 for resource in resources {564 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;565 }566 }567568 Self::deposit_event(Event::NftMinted {569 owner,570 collection_id,571 nft_id: nft_id.0,572 });573574 Ok(())575 }576577 /// Burn an NFT, destroying it and its nested tokens up to the specified limit.578 /// If the burning budget is exceeded, the transaction is reverted.579 ///580 /// This is the way to burn a nested token as well.581 ///582 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).583 ///584 /// # Permissions:585 /// * Token owner586 ///587 /// # Arguments:588 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.589 /// - `nft_id`: ID of the NFT to be destroyed.590 /// - `max_burns`: Maximum number of tokens to burn, used for nesting. The transaction591 /// is reverted if there are more tokens to burn in the nesting tree than this number.592 #[transactional]593 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]594 pub fn burn_nft(595 origin: OriginFor<T>,596 collection_id: RmrkCollectionId,597 nft_id: RmrkNftId,598 max_burns: u32,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender.clone());602603 let collection = Self::get_typed_nft_collection(604 Self::unique_collection_id(collection_id)?,605 misc::CollectionType::Regular,606 )?;607 collection.check_is_external()?;608609 Self::destroy_nft(610 cross_sender,611 Self::unique_collection_id(collection_id)?,612 nft_id.into(),613 max_burns,614 <Error<T>>::NoPermission,615 )616 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;617618 Self::deposit_event(Event::NFTBurned {619 owner: sender,620 nft_id,621 });622623 Ok(())624 }625626 /// Transfer an NFT from an account/NFT A to another account/NFT B.627 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].628 ///629 /// If the target owner is an NFT owned by another account, then the NFT will enter630 /// the pending state and will have to be accepted by the other account.631 ///632 /// # Permissions:633 /// - Token owner634 ///635 /// # Arguments:636 /// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.637 /// - `nft_id`: ID of the NFT to be transferred.638 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.639 #[transactional]640 #[pallet::weight(<SelfWeightOf<T>>::send())]641 pub fn send(642 origin: OriginFor<T>,643 rmrk_collection_id: RmrkCollectionId,644 rmrk_nft_id: RmrkNftId,645 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,646 ) -> DispatchResult {647 let sender = ensure_signed(origin.clone())?;648 let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651 let nft_id = rmrk_nft_id.into();652653 let collection =654 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655 collection.check_is_external()?;656657 let token_data =658 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;659660 let from = token_data.owner;661662 ensure!(663 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,664 <Error<T>>::NonTransferable665 );666667 ensure!(668 Self::get_nft_property_decoded::<Option<PendingTarget>>(669 collection_id,670 nft_id,671 RmrkProperty::PendingNftAccept672 )?673 .is_none(),674 <Error<T>>::NoPermission675 );676677 let target_owner;678 let approval_required;679680 match new_owner {681 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {682 target_owner = T::CrossAccountId::from_sub(account_id.clone());683 approval_required = false;684 }685 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(686 target_collection_id,687 target_nft_id,688 ) => {689 let target_collection_id = Self::unique_collection_id(target_collection_id)?;690691 let target_nft_budget = budget::Value::new(NESTING_BUDGET);692693 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(694 target_collection_id,695 target_nft_id.into(),696 Some((collection_id, nft_id)),697 &target_nft_budget,698 )699 .map_err(Self::map_unique_err_to_proxy)?;700701 approval_required = cross_sender != target_nft_owner;702703 if approval_required {704 target_owner = target_nft_owner;705706 <PalletNft<T>>::set_scoped_token_property(707 collection.id,708 nft_id,709 RMRK_SCOPE,710 Self::encode_rmrk_property::<Option<PendingTarget>>(711 PendingNftAccept,712 &Some((target_collection_id, target_nft_id.into())),713 )?,714 )?;715716 Self::insert_pending_child(717 (target_collection_id, target_nft_id.into()),718 (rmrk_collection_id, rmrk_nft_id),719 )?;720 } else {721 target_owner = T::CrossTokenAddressMapping::token_to_address(722 target_collection_id,723 target_nft_id.into(),724 );725 }726 }727 }728729 let src_nft_budget = budget::Value::new(NESTING_BUDGET);730731 <PalletNft<T>>::transfer_from(732 &collection,733 &cross_sender,734 &from,735 &target_owner,736 nft_id,737 &src_nft_budget,738 )739 .map_err(Self::map_unique_err_to_proxy)?;740741 Self::deposit_event(Event::NFTSent {742 sender,743 recipient: new_owner,744 collection_id: rmrk_collection_id,745 nft_id: rmrk_nft_id,746 approval_required,747 });748749 Ok(())750 }751752 /// Accept an NFT sent from another account to self or an owned NFT.753 ///754 /// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.755 ///756 /// # Permissions:757 /// - Token-owner-to-be758 ///759 /// # Arguments:760 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.761 /// - `rmrk_nft_id`: ID of the NFT to be accepted.762 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,763 /// whichever the accepted NFT was sent to.764 #[transactional]765 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]766 pub fn accept_nft(767 origin: OriginFor<T>,768 rmrk_collection_id: RmrkCollectionId,769 rmrk_nft_id: RmrkNftId,770 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,771 ) -> DispatchResult {772 let sender = ensure_signed(origin.clone())?;773 let cross_sender = T::CrossAccountId::from_sub(sender.clone());774775 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;776 let nft_id = rmrk_nft_id.into();777778 let collection =779 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;780 collection.check_is_external()?;781782 let new_cross_owner = match new_owner {783 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {784 T::CrossAccountId::from_sub(account_id.clone())785 }786 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(787 target_collection_id,788 target_nft_id,789 ) => {790 let target_collection_id = Self::unique_collection_id(target_collection_id)?;791792 T::CrossTokenAddressMapping::token_to_address(793 target_collection_id,794 TokenId(target_nft_id),795 )796 }797 };798799 let budget = budget::Value::new(NESTING_BUDGET);800801 <PalletNft<T>>::transfer(802 &collection,803 &cross_sender,804 &new_cross_owner,805 nft_id,806 &budget,807 )808 .map_err(|err| {809 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {810 <Error<T>>::CannotAcceptNonOwnedNft.into()811 } else {812 Self::map_unique_err_to_proxy(err)813 }814 })?;815816 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(817 collection_id,818 nft_id,819 RmrkProperty::PendingNftAccept,820 )?;821822 if let Some(pending_target) = pending_target {823 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;824825 <PalletNft<T>>::set_scoped_token_property(826 collection.id,827 nft_id,828 RMRK_SCOPE,829 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,830 )?;831 }832833 Self::deposit_event(Event::NFTAccepted {834 sender,835 recipient: new_owner,836 collection_id: rmrk_collection_id,837 nft_id: rmrk_nft_id,838 });839840 Ok(())841 }842843 /// Reject an NFT sent from another account to self or owned NFT.844 /// The NFT in question will not be sent back and burnt instead.845 ///846 /// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.847 ///848 /// # Permissions:849 /// - Token-owner-to-be-not850 ///851 /// # Arguments:852 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.853 /// - `rmrk_nft_id`: ID of the NFT to be rejected.854 #[transactional]855 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]856 pub fn reject_nft(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 ) -> DispatchResult {861 let sender = ensure_signed(origin)?;862 let cross_sender = T::CrossAccountId::from_sub(sender.clone());863864 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865 let nft_id = rmrk_nft_id.into();866867 let collection =868 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869 collection.check_is_external()?;870871 ensure!(872 <TokenData<T>>::get((collection_id, nft_id)).is_some(),873 <Error<T>>::NoAvailableNftId874 );875876 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(877 collection_id,878 nft_id,879 RmrkProperty::PendingNftAccept,880 )?;881882 match pending_target {883 Some(pending_target) => {884 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?885 }886 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),887 }888889 Self::destroy_nft(890 cross_sender,891 collection_id,892 nft_id,893 NESTING_BUDGET,894 <Error<T>>::CannotRejectNonOwnedNft,895 )896 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;897898 Self::deposit_event(Event::NFTRejected {899 sender,900 collection_id: rmrk_collection_id,901 nft_id: rmrk_nft_id,902 });903904 Ok(())905 }906907 /// Accept the addition of a newly created pending resource to an existing NFT.908 ///909 /// This transaction is needed when a resource is created and assigned to an NFT910 /// by a non-owner, i.e. the collection issuer, with one of the911 /// [`add_...` transactions](crate::pallet::Call::add_basic_resource).912 ///913 /// # Permissions:914 /// - Token owner915 ///916 /// # Arguments:917 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.918 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.919 /// - `resource_id`: ID of the newly created pending resource.920 #[transactional]921 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]922 pub fn accept_resource(923 origin: OriginFor<T>,924 rmrk_collection_id: RmrkCollectionId,925 rmrk_nft_id: RmrkNftId,926 resource_id: RmrkResourceId,927 ) -> DispatchResult {928 let sender = ensure_signed(origin)?;929 let cross_sender = T::CrossAccountId::from_sub(sender);930931 let collection_id = Self::unique_collection_id(rmrk_collection_id)932 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;933 let collection =934 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935 collection.check_is_external()?;936937 let nft_id = rmrk_nft_id.into();938939 let budget = budget::Value::new(NESTING_BUDGET);940941 let nft_owner =942 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)943 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;944945 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {946 ensure!(res.pending, <Error<T>>::ResourceNotPending);947 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);948949 res.pending = false;950951 Ok(())952 })?;953954 Self::deposit_event(Event::<T>::ResourceAccepted {955 nft_id: rmrk_nft_id,956 resource_id,957 });958959 Ok(())960 }961962 /// Accept the removal of a removal-pending resource from an NFT.963 ///964 /// This transaction is needed when a non-owner, i.e. the collection issuer,965 /// requests a [removal](`crate::pallet::Call::remove_resource`) of a resource from an NFT.966 ///967 /// # Permissions:968 /// - Token owner969 ///970 /// # Arguments:971 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.972 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.973 /// - `resource_id`: ID of the removal-pending resource.974 #[transactional]975 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]976 pub fn accept_resource_removal(977 origin: OriginFor<T>,978 rmrk_collection_id: RmrkCollectionId,979 rmrk_nft_id: RmrkNftId,980 resource_id: RmrkResourceId,981 ) -> DispatchResult {982 let sender = ensure_signed(origin)?;983 let cross_sender = T::CrossAccountId::from_sub(sender);984985 let collection_id = Self::unique_collection_id(rmrk_collection_id)986 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;987 let collection =988 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;989 collection.check_is_external()?;990991 let nft_id = rmrk_nft_id.into();992993 let budget = budget::Value::new(NESTING_BUDGET);994995 let nft_owner =996 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)997 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;998999 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10001001 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10021003 let resource_info = <PalletNft<T>>::token_aux_property((1004 collection_id,1005 nft_id,1006 RMRK_SCOPE,1007 resource_id_key.clone(),1008 ))1009 .ok_or(<Error<T>>::ResourceDoesntExist)?;10101011 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10121013 ensure!(1014 resource_info.pending_removal,1015 <Error<T>>::ResourceNotPending1016 );10171018 <PalletNft<T>>::remove_token_aux_property(1019 collection_id,1020 nft_id,1021 RMRK_SCOPE,1022 resource_id_key,1023 );10241025 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1026 let base_id = resource.base;10271028 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1029 }10301031 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1032 nft_id: rmrk_nft_id,1033 resource_id,1034 });10351036 Ok(())1037 }10381039 /// Add or edit a custom user property, a key-value pair, describing the metadata1040 /// of a token or a collection, on either one of these.1041 ///1042 /// Note that in this proxy implementation many details regarding RMRK are stored1043 /// as scoped properties prefixed with "rmrk:", normally inaccessible1044 /// to external transactions and RPCs.1045 ///1046 /// # Permissions:1047 /// - Collection issuer - in case of collection property1048 /// - Token owner - in case of NFT property1049 ///1050 /// # Arguments:1051 /// - `rmrk_collection_id`: RMRK collection ID.1052 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1053 /// - `key`: Key of the custom property to be referenced by.1054 /// - `value`: Value of the custom property to be stored.1055 #[transactional]1056 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1057 pub fn set_property(1058 origin: OriginFor<T>,1059 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1060 maybe_nft_id: Option<RmrkNftId>,1061 key: RmrkKeyString,1062 value: RmrkValueString,1063 ) -> DispatchResult {1064 let sender = ensure_signed(origin)?;1065 let sender = T::CrossAccountId::from_sub(sender);10661067 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1068 let collection =1069 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1070 collection.check_is_external()?;10711072 let budget = budget::Value::new(NESTING_BUDGET);10731074 match maybe_nft_id {1075 Some(nft_id) => {1076 let token_id: TokenId = nft_id.into();10771078 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1079 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;10801081 <PalletNft<T>>::set_scoped_token_property(1082 collection_id,1083 token_id,1084 RMRK_SCOPE,1085 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1086 )?;1087 }1088 None => {1089 let collection = Self::get_typed_nft_collection(1090 collection_id,1091 misc::CollectionType::Regular,1092 )?;10931094 Self::check_collection_owner(&collection, &sender)?;10951096 <PalletCommon<T>>::set_scoped_collection_property(1097 collection_id,1098 RMRK_SCOPE,1099 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1100 )?;1101 }1102 }11031104 Self::deposit_event(Event::PropertySet {1105 collection_id: rmrk_collection_id,1106 maybe_nft_id,1107 key,1108 value,1109 });11101111 Ok(())1112 }11131114 /// Set a different order of resource priorities for an NFT. Priorities can be used,1115 /// for example, for order of rendering.1116 ///1117 /// Note that the priorities are not updated automatically, and are an empty vector1118 /// by default. There is no pre-set definition for the order to be particular,1119 /// it can be interpreted arbitrarily use-case by use-case.1120 ///1121 /// # Permissions:1122 /// - Token owner1123 ///1124 /// # Arguments:1125 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1126 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1127 /// - `priorities`: Ordered vector of resource IDs.1128 #[transactional]1129 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1130 pub fn set_priority(1131 origin: OriginFor<T>,1132 rmrk_collection_id: RmrkCollectionId,1133 rmrk_nft_id: RmrkNftId,1134 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1135 ) -> DispatchResult {1136 let sender = ensure_signed(origin)?;1137 let sender = T::CrossAccountId::from_sub(sender);11381139 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1140 let nft_id = rmrk_nft_id.into();11411142 let collection =1143 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1144 collection.check_is_external()?;11451146 let budget = budget::Value::new(NESTING_BUDGET);11471148 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1149 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11501151 <PalletNft<T>>::set_scoped_token_property(1152 collection_id,1153 nft_id,1154 RMRK_SCOPE,1155 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1156 )?;11571158 Self::deposit_event(Event::<T>::PrioritySet {1159 collection_id: rmrk_collection_id,1160 nft_id: rmrk_nft_id,1161 });11621163 Ok(())1164 }11651166 /// Create and set/propose a basic resource for an NFT.1167 ///1168 /// A resource is considered a part of an NFT, an additional piece of metadata1169 /// usually serving to add a piece of media on top of the root metadata, be it1170 /// a different wing on the root template bird or something entirely unrelated.1171 /// A basic resource is the simplest, lacking a base or composables.1172 ///1173 /// See RMRK docs for more information and examples.1174 ///1175 /// # Permissions:1176 /// - Collection issuer - if not the token owner, adding the resource will warrant1177 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1178 ///1179 /// # Arguments:1180 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1181 /// - `nft_id`: ID of the NFT to assign a resource to.1182 /// - `resource`: Data of the resource to be created.1183 #[transactional]1184 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1185 pub fn add_basic_resource(1186 origin: OriginFor<T>,1187 rmrk_collection_id: RmrkCollectionId,1188 nft_id: RmrkNftId,1189 resource: RmrkBasicResource,1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin.clone())?;11921193 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1194 let collection =1195 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196 collection.check_is_external()?;11971198 let resource_id = Self::resource_add(1199 sender,1200 collection_id,1201 nft_id.into(),1202 RmrkResourceTypes::Basic(resource),1203 )?;12041205 Self::deposit_event(Event::ResourceAdded {1206 nft_id,1207 resource_id,1208 });1209 Ok(())1210 }12111212 /// Create and set/propose a composable resource for an NFT.1213 ///1214 /// A resource is considered a part of an NFT, an additional piece of metadata1215 /// usually serving to add a piece of media on top of the root metadata, be it1216 /// a different wing on the root template bird or something entirely unrelated.1217 /// A composable resource links to a base and has a subset of its parts it is composed of.1218 ///1219 /// See RMRK docs for more information and examples.1220 ///1221 /// # Permissions:1222 /// - Collection issuer - if not the token owner, adding the resource will warrant1223 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1224 ///1225 /// # Arguments:1226 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1227 /// - `nft_id`: ID of the NFT to assign a resource to.1228 /// - `resource`: Data of the resource to be created.1229 #[transactional]1230 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1231 pub fn add_composable_resource(1232 origin: OriginFor<T>,1233 rmrk_collection_id: RmrkCollectionId,1234 nft_id: RmrkNftId,1235 resource: RmrkComposableResource,1236 ) -> DispatchResult {1237 let sender = ensure_signed(origin.clone())?;12381239 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1240 let collection =1241 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1242 collection.check_is_external()?;12431244 let base_id = resource.base;12451246 let resource_id = Self::resource_add(1247 sender,1248 collection_id,1249 nft_id.into(),1250 RmrkResourceTypes::Composable(resource),1251 )?;12521253 <PalletNft<T>>::try_mutate_token_aux_property(1254 collection_id,1255 nft_id.into(),1256 RMRK_SCOPE,1257 Self::get_scoped_property_key(AssociatedBases)?,1258 |value| -> DispatchResult {1259 let mut bases: BasesMap = match value {1260 Some(value) => Self::decode_property_value(value)?,1261 None => BasesMap::new(),1262 };12631264 *bases.entry(base_id).or_insert(0) += 1;12651266 *value = Some(Self::encode_property_value(&bases)?);1267 Ok(())1268 },1269 )?;12701271 Self::deposit_event(Event::ResourceAdded {1272 nft_id,1273 resource_id,1274 });1275 Ok(())1276 }12771278 /// Create and set/propose a slot resource for an NFT.1279 ///1280 /// A resource is considered a part of an NFT, an additional piece of metadata1281 /// usually serving to add a piece of media on top of the root metadata, be it1282 /// a different wing on the root template bird or something entirely unrelated.1283 /// A slot resource links to a base and a slot in it which it now occupies.1284 ///1285 /// See RMRK docs for more information and examples.1286 ///1287 /// # Permissions:1288 /// - Collection issuer - if not the token owner, adding the resource will warrant1289 /// the owner's [acceptance](crate::pallet::Call::accept_resource).1290 ///1291 /// # Arguments:1292 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1293 /// - `nft_id`: ID of the NFT to assign a resource to.1294 /// - `resource`: Data of the resource to be created.1295 #[transactional]1296 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1297 pub fn add_slot_resource(1298 origin: OriginFor<T>,1299 rmrk_collection_id: RmrkCollectionId,1300 nft_id: RmrkNftId,1301 resource: RmrkSlotResource,1302 ) -> DispatchResult {1303 let sender = ensure_signed(origin.clone())?;13041305 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1306 let collection =1307 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1308 collection.check_is_external()?;13091310 let resource_id = Self::resource_add(1311 sender,1312 collection_id,1313 nft_id.into(),1314 RmrkResourceTypes::Slot(resource),1315 )?;13161317 Self::deposit_event(Event::ResourceAdded {1318 nft_id,1319 resource_id,1320 });1321 Ok(())1322 }13231324 /// Remove and erase a resource from an NFT.1325 ///1326 /// If the sender does not own the NFT, then it will be pending confirmation,1327 /// and will have to be [accepted](crate::pallet::Call::accept_resource_removal) by the token owner.1328 ///1329 /// # Permissions1330 /// - Collection issuer1331 ///1332 /// # Arguments1333 /// - `collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1334 /// - `nft_id`: ID of the NFT with a resource to be removed.1335 /// - `resource_id`: ID of the resource to be removed.1336 #[transactional]1337 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1338 pub fn remove_resource(1339 origin: OriginFor<T>,1340 rmrk_collection_id: RmrkCollectionId,1341 nft_id: RmrkNftId,1342 resource_id: RmrkResourceId,1343 ) -> DispatchResult {1344 let sender = ensure_signed(origin.clone())?;13451346 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1347 let collection =1348 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1349 collection.check_is_external()?;13501351 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13521353 Self::deposit_event(Event::ResourceRemoval {1354 nft_id,1355 resource_id,1356 });1357 Ok(())1358 }1359 }1360}13611362impl<T: Config> Pallet<T> {1363 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1364 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1365 let key = rmrk_key.to_key::<T>()?;13661367 let scoped_key = RMRK_SCOPE1368 .apply(key)1369 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13701371 Ok(scoped_key)1372 }13731374 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1375 /// and encoding the value from an arbitrary type into bytes.1376 pub fn encode_rmrk_property<E: Encode>(1377 rmrk_key: RmrkProperty,1378 value: &E,1379 ) -> Result<Property, DispatchError> {1380 let key = rmrk_key.to_key::<T>()?;13811382 let value = Self::encode_property_value(value)?;13831384 let property = Property { key, value };13851386 Ok(property)1387 }13881389 /// Encode property value from an arbitrary type into bytes for storage.1390 pub fn encode_property_value<E: Encode, S: Get<u32>>(1391 value: &E,1392 ) -> Result<BoundedBytes<S>, DispatchError> {1393 let value = value1394 .encode()1395 .try_into()1396 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;13971398 Ok(value)1399 }14001401 /// Decode property value from bytes into an arbitrary type.1402 pub fn decode_property_value<D: Decode, S: Get<u32>>(1403 vec: &BoundedBytes<S>,1404 ) -> Result<D, DispatchError> {1405 vec.decode()1406 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1407 }14081409 /// Change the limit of a property value byte vector.1410 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1411 where1412 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1413 {1414 vec.rebind()1415 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1416 }14171418 /// Initialize a new NFT collection with certain RMRK-scoped properties.1419 ///1420 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1421 fn init_collection(1422 sender: T::CrossAccountId,1423 data: CreateCollectionData<T::AccountId>,1424 properties: impl Iterator<Item = Property>,1425 ) -> Result<CollectionId, DispatchError> {1426 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14271428 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1429 return Err(<Error<T>>::NoAvailableCollectionId.into());1430 }14311432 <PalletCommon<T>>::set_scoped_collection_properties(1433 collection_id?,1434 RMRK_SCOPE,1435 properties,1436 )?;14371438 collection_id1439 }14401441 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1442 ///1443 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1444 pub fn create_nft(1445 sender: &T::CrossAccountId,1446 owner: &T::CrossAccountId,1447 collection: &NonfungibleHandle<T>,1448 properties: impl Iterator<Item = Property>,1449 ) -> Result<TokenId, DispatchError> {1450 let data = CreateNftExData {1451 properties: BoundedVec::default(),1452 owner: owner.clone(),1453 };14541455 let budget = budget::Value::new(NESTING_BUDGET);14561457 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14581459 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14601461 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14621463 Ok(nft_id)1464 }14651466 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1467 ///1468 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1469 fn destroy_nft(1470 sender: T::CrossAccountId,1471 collection_id: CollectionId,1472 token_id: TokenId,1473 max_burns: u32,1474 error_if_not_owned: Error<T>,1475 ) -> DispatchResultWithPostInfo {1476 let collection =1477 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14781479 let token_data =1480 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14811482 let from = token_data.owner;14831484 let owner_check_budget = budget::Value::new(NESTING_BUDGET);14851486 ensure!(1487 <PalletStructure<T>>::check_indirectly_owned(1488 sender.clone(),1489 collection_id,1490 token_id,1491 None,1492 &owner_check_budget1493 )?,1494 error_if_not_owned,1495 );14961497 let burns_budget = budget::Value::new(max_burns);1498 let breadth_budget = budget::Value::new(max_burns);14991500 <PalletNft<T>>::burn_recursively(1501 &collection,1502 &from,1503 token_id,1504 &burns_budget,1505 &breadth_budget,1506 )1507 }15081509 /// Add a sent token pending acceptance to the target owning token as a property.1510 fn insert_pending_child(1511 target: (CollectionId, TokenId),1512 child: (RmrkCollectionId, RmrkNftId),1513 ) -> DispatchResult {1514 Self::mutate_pending_children(target, |pending_children| {1515 pending_children.insert(child);1516 })1517 }15181519 /// Remove a sent token pending acceptance from the target token's properties.1520 fn remove_pending_child(1521 target: (CollectionId, TokenId),1522 child: (RmrkCollectionId, RmrkNftId),1523 ) -> DispatchResult {1524 Self::mutate_pending_children(target, |pending_children| {1525 pending_children.remove(&child);1526 })1527 }15281529 /// Apply a mutation to the property of a token containing sent tokens1530 /// that are currently pending acceptance.1531 fn mutate_pending_children(1532 (target_collection_id, target_nft_id): (CollectionId, TokenId),1533 f: impl FnOnce(&mut PendingChildrenSet),1534 ) -> DispatchResult {1535 <PalletNft<T>>::try_mutate_token_aux_property(1536 target_collection_id,1537 target_nft_id,1538 RMRK_SCOPE,1539 Self::get_scoped_property_key(PendingChildren)?,1540 |pending_children| -> DispatchResult {1541 let mut map = match pending_children {1542 Some(map) => Self::decode_property_value(map)?,1543 None => PendingChildrenSet::new(),1544 };15451546 f(&mut map);15471548 *pending_children = Some(Self::encode_property_value(&map)?);15491550 Ok(())1551 },1552 )1553 }15541555 /// Get an iterator from a token's property containing tokens sent to it1556 /// that are currently pending acceptance.1557 fn iterate_pending_children(1558 collection_id: CollectionId,1559 nft_id: TokenId,1560 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1561 let property = <PalletNft<T>>::token_aux_property((1562 collection_id,1563 nft_id,1564 RMRK_SCOPE,1565 Self::get_scoped_property_key(PendingChildren)?,1566 ));15671568 let pending_children = match property {1569 Some(map) => Self::decode_property_value(&map)?,1570 None => PendingChildrenSet::new(),1571 };15721573 Ok(pending_children.into_iter())1574 }15751576 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1577 /// Thus, the returned resource ID should be used.1578 fn acquire_next_resource_id(1579 collection_id: CollectionId,1580 nft_id: TokenId,1581 ) -> Result<RmrkResourceId, DispatchError> {1582 let resource_id: RmrkResourceId =1583 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15841585 let next_id = resource_id1586 .checked_add(1)1587 .ok_or(<Error<T>>::NoAvailableResourceId)?;15881589 <PalletNft<T>>::set_scoped_token_property(1590 collection_id,1591 nft_id,1592 RMRK_SCOPE,1593 Self::encode_rmrk_property(NextResourceId, &next_id)?,1594 )?;15951596 Ok(resource_id)1597 }15981599 /// Create and add a resource for a regular NFT, mark it as pending if the sender1600 /// is not the token owner. The sender must be the collection owner.1601 fn resource_add(1602 sender: T::AccountId,1603 collection_id: CollectionId,1604 nft_id: TokenId,1605 resource: RmrkResourceTypes,1606 ) -> Result<RmrkResourceId, DispatchError> {1607 let collection =1608 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1609 ensure!(collection.owner == sender, Error::<T>::NoPermission);16101611 let sender = T::CrossAccountId::from_sub(sender);1612 let budget = budget::Value::new(NESTING_BUDGET);16131614 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1615 .map_err(Self::map_unique_err_to_proxy)?;16161617 let pending = sender != nft_owner;16181619 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16201621 let resource_info = RmrkResourceInfo {1622 id,1623 resource,1624 pending,1625 pending_removal: false,1626 };16271628 <PalletNft<T>>::try_mutate_token_aux_property(1629 collection_id,1630 nft_id,1631 RMRK_SCOPE,1632 Self::get_scoped_property_key(ResourceId(id))?,1633 |value| -> DispatchResult {1634 *value = Some(Self::encode_property_value(&resource_info)?);16351636 Ok(())1637 },1638 )?;16391640 Ok(id)1641 }16421643 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1644 /// The sender must be the collection owner.1645 fn resource_remove(1646 sender: T::AccountId,1647 collection_id: CollectionId,1648 nft_id: TokenId,1649 resource_id: RmrkResourceId,1650 ) -> DispatchResult {1651 let collection =1652 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1653 ensure!(collection.owner == sender, Error::<T>::NoPermission);16541655 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16561657 let resource = <PalletNft<T>>::token_aux_property((1658 collection_id,1659 nft_id,1660 RMRK_SCOPE,1661 resource_id_key.clone(),1662 ))1663 .ok_or(<Error<T>>::ResourceDoesntExist)?;16641665 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16661667 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1668 let topmost_owner =1669 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16701671 let sender = T::CrossAccountId::from_sub(sender);1672 if topmost_owner == sender {1673 <PalletNft<T>>::remove_token_aux_property(1674 collection_id,1675 nft_id,1676 RMRK_SCOPE,1677 Self::get_scoped_property_key(ResourceId(resource_id))?,1678 );16791680 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1681 let base_id = resource.base;16821683 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1684 }1685 } else {1686 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1687 res.pending_removal = true;16881689 Ok(())1690 })?;1691 }16921693 Ok(())1694 }16951696 /// Remove one usage of a base from an NFT's property of associated bases. The base will stay, however,1697 /// if the count of resources using the base is still non-zero.1698 fn remove_associated_base_id(1699 collection_id: CollectionId,1700 nft_id: TokenId,1701 base_id: RmrkBaseId,1702 ) -> DispatchResult {1703 <PalletNft<T>>::try_mutate_token_aux_property(1704 collection_id,1705 nft_id,1706 RMRK_SCOPE,1707 Self::get_scoped_property_key(AssociatedBases)?,1708 |value| -> DispatchResult {1709 let mut bases: BasesMap = match value {1710 Some(value) => Self::decode_property_value(value)?,1711 None => BasesMap::new(),1712 };17131714 let remaining = bases.get(&base_id);17151716 if let Some(remaining) = remaining {1717 if let Some(0) | None = remaining.checked_sub(1) {1718 bases.remove(&base_id);1719 }1720 }17211722 *value = Some(Self::encode_property_value(&bases)?);1723 Ok(())1724 },1725 )1726 }17271728 /// Apply a mutation to a resource stored in the token properties of an NFT.1729 fn try_mutate_resource_info(1730 collection_id: CollectionId,1731 nft_id: TokenId,1732 resource_id: RmrkResourceId,1733 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1734 ) -> DispatchResult {1735 <PalletNft<T>>::try_mutate_token_aux_property(1736 collection_id,1737 nft_id,1738 RMRK_SCOPE,1739 Self::get_scoped_property_key(ResourceId(resource_id))?,1740 |value| match value {1741 Some(value) => {1742 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17431744 f(&mut resource_info)?;17451746 *value = Self::encode_property_value(&resource_info)?;17471748 Ok(())1749 }1750 None => Err(<Error<T>>::ResourceDoesntExist.into()),1751 },1752 )1753 }17541755 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1756 fn change_collection_owner(1757 collection_id: CollectionId,1758 collection_type: misc::CollectionType,1759 sender: T::AccountId,1760 new_owner: T::AccountId,1761 ) -> DispatchResult {1762 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1763 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17641765 let mut collection = collection.into_inner();17661767 collection.owner = new_owner;1768 collection.save()1769 }17701771 /// Ensure that an account is the collection owner/issuer, return an error if not.1772 pub fn check_collection_owner(1773 collection: &NonfungibleHandle<T>,1774 account: &T::CrossAccountId,1775 ) -> DispatchResult {1776 collection1777 .check_is_owner(account)1778 .map_err(Self::map_unique_err_to_proxy)1779 }17801781 /// Get the latest yet-unused RMRK collection index from the storage.1782 pub fn last_collection_idx() -> RmrkCollectionId {1783 <CollectionIndex<T>>::get()1784 }17851786 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1787 pub fn unique_collection_id(1788 rmrk_collection_id: RmrkCollectionId,1789 ) -> Result<CollectionId, DispatchError> {1790 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1791 .map_err(|_| <Error<T>>::CollectionUnknown.into())1792 }17931794 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1795 pub fn rmrk_collection_id(1796 unique_collection_id: CollectionId,1797 ) -> Result<RmrkCollectionId, DispatchError> {1798 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1799 }18001801 /// Fetch a Unique NFT collection.1802 pub fn get_nft_collection(1803 collection_id: CollectionId,1804 ) -> Result<NonfungibleHandle<T>, DispatchError> {1805 let collection = <CollectionHandle<T>>::try_get(collection_id)1806 .map_err(|_| <Error<T>>::CollectionUnknown)?;18071808 match collection.mode {1809 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1810 _ => Err(<Error<T>>::CollectionUnknown.into()),1811 }1812 }18131814 /// Check if an NFT collection with such an ID exists.1815 pub fn collection_exists(collection_id: CollectionId) -> bool {1816 <CollectionHandle<T>>::try_get(collection_id).is_ok()1817 }18181819 /// Fetch and decode a RMRK-scoped collection property value in bytes.1820 pub fn get_collection_property(1821 collection_id: CollectionId,1822 key: RmrkProperty,1823 ) -> Result<PropertyValue, DispatchError> {1824 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1825 .get(&Self::get_scoped_property_key(key)?)1826 .ok_or(<Error<T>>::CollectionUnknown)?1827 .clone();18281829 Ok(collection_property)1830 }18311832 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1833 pub fn get_collection_property_decoded<V: Decode>(1834 collection_id: CollectionId,1835 key: RmrkProperty,1836 ) -> Result<V, DispatchError> {1837 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1838 }18391840 /// Get the type of a collection stored in it as a scoped property.1841 ///1842 /// RMRK Core proxy differentiates between regular collections as well as RMRK bases as collections.1843 pub fn get_collection_type(1844 collection_id: CollectionId,1845 ) -> Result<misc::CollectionType, DispatchError> {1846 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1847 if err != <Error<T>>::CollectionUnknown.into() {1848 <Error<T>>::CorruptedCollectionType.into()1849 } else {1850 err1851 }1852 })1853 }18541855 /// Ensure that the type of the collection equals the provided type,1856 /// otherwise return an error.1857 pub fn ensure_collection_type(1858 collection_id: CollectionId,1859 collection_type: misc::CollectionType,1860 ) -> DispatchResult {1861 let actual_type = Self::get_collection_type(collection_id)?;1862 ensure!(1863 actual_type == collection_type,1864 <CommonError<T>>::NoPermission1865 );18661867 Ok(())1868 }18691870 /// Fetch an NFT collection, but make sure it has the appropriate type.1871 pub fn get_typed_nft_collection(1872 collection_id: CollectionId,1873 collection_type: misc::CollectionType,1874 ) -> Result<NonfungibleHandle<T>, DispatchError> {1875 Self::ensure_collection_type(collection_id, collection_type)?;18761877 Self::get_nft_collection(collection_id)1878 }18791880 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1881 /// but also return the Unique collection ID.1882 pub fn get_typed_nft_collection_mapped(1883 rmrk_collection_id: RmrkCollectionId,1884 collection_type: misc::CollectionType,1885 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1886 let unique_collection_id = match collection_type {1887 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1888 _ => rmrk_collection_id.into(),1889 };18901891 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18921893 Ok((collection, unique_collection_id))1894 }18951896 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1897 pub fn get_nft_property(1898 collection_id: CollectionId,1899 nft_id: TokenId,1900 key: RmrkProperty,1901 ) -> Result<PropertyValue, DispatchError> {1902 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1903 .get(&Self::get_scoped_property_key(key)?)1904 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1905 .clone();19061907 Ok(nft_property)1908 }19091910 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1911 pub fn get_nft_property_decoded<V: Decode>(1912 collection_id: CollectionId,1913 nft_id: TokenId,1914 key: RmrkProperty,1915 ) -> Result<V, DispatchError> {1916 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1917 }19181919 /// Check that an NFT exists.1920 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1921 <TokenData<T>>::contains_key((collection_id, nft_id))1922 }19231924 /// Get the type of an NFT stored in it as a scoped property.1925 ///1926 /// RMRK Core proxy differentiates between regular NFTs, and RMRK parts and themes.1927 pub fn get_nft_type(1928 collection_id: CollectionId,1929 token_id: TokenId,1930 ) -> Result<NftType, DispatchError> {1931 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1932 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1933 }19341935 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1936 pub fn ensure_nft_type(1937 collection_id: CollectionId,1938 token_id: TokenId,1939 nft_type: NftType,1940 ) -> DispatchResult {1941 let actual_type = Self::get_nft_type(collection_id, token_id)?;1942 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19431944 Ok(())1945 }19461947 /// Ensure that an account is the owner of the token, either directly1948 /// or at the top of the nesting hierarchy; return an error if it is not.1949 pub fn ensure_nft_owner(1950 collection_id: CollectionId,1951 token_id: TokenId,1952 possible_owner: &T::CrossAccountId,1953 nesting_budget: &dyn budget::Budget,1954 ) -> DispatchResult {1955 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1956 possible_owner.clone(),1957 collection_id,1958 token_id,1959 None,1960 nesting_budget,1961 )1962 .map_err(Self::map_unique_err_to_proxy)?;19631964 ensure!(is_owned, <Error<T>>::NoPermission);19651966 Ok(())1967 }19681969 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,1970 /// or, if None are provided, return all non-scoped properties.1971 pub fn filter_user_properties<Key, Value, R, Mapper>(1972 collection_id: CollectionId,1973 token_id: Option<TokenId>,1974 filter_keys: Option<Vec<RmrkPropertyKey>>,1975 mapper: Mapper,1976 ) -> Result<Vec<R>, DispatchError>1977 where1978 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1979 Value: Decode + Default,1980 Mapper: Fn(Key, Value) -> R,1981 {1982 filter_keys1983 .map(|keys| {1984 let properties = keys1985 .into_iter()1986 .filter_map(|key| {1987 let key: Key = key.try_into().ok()?;19881989 let value = match token_id {1990 Some(token_id) => Self::get_nft_property_decoded(1991 collection_id,1992 token_id,1993 UserProperty(key.as_ref()),1994 ),1995 None => Self::get_collection_property_decoded(1996 collection_id,1997 UserProperty(key.as_ref()),1998 ),1999 }2000 .ok()?;20012002 Some(mapper(key, value))2003 })2004 .collect();20052006 Ok(properties)2007 })2008 .unwrap_or_else(|| {2009 let properties =2010 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20112012 Ok(properties)2013 })2014 }20152016 /// Get all non-scoped properties from a collection or a token, and apply some transformation2017 /// to each key-value pair.2018 pub fn iterate_user_properties<Key, Value, R, Mapper>(2019 collection_id: CollectionId,2020 token_id: Option<TokenId>,2021 mapper: Mapper,2022 ) -> Result<impl Iterator<Item = R>, DispatchError>2023 where2024 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2025 Value: Decode + Default,2026 Mapper: Fn(Key, Value) -> R,2027 {2028 let properties = match token_id {2029 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2030 None => <PalletCommon<T>>::collection_properties(collection_id),2031 };20322033 let properties = properties.into_iter().filter_map(move |(key, value)| {2034 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20352036 let key: Key = key.to_vec().try_into().ok()?;2037 let value: Value = value.decode().ok()?;20382039 Some(mapper(key, value))2040 });20412042 Ok(properties)2043 }20442045 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2046 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2047 map_unique_err_to_proxy! {2048 match err {2049 CommonError::NoPermission => NoPermission,2050 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2051 CommonError::PublicMintingNotAllowed => NoPermission,2052 CommonError::TokenNotFound => NoAvailableNftId,2053 CommonError::ApprovedValueTooLow => NoPermission,2054 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2055 StructureError::TokenNotFound => NoAvailableNftId,2056 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2057 }2058 }2059 }2060}pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -19,7 +19,7 @@
use super::*;
use codec::{Encode, Decode, Error};
-/// Match errors from one type to another and return an error
+/// Match errors from one type to another and return an error
/// if a match is successful.
#[macro_export]
macro_rules! map_unique_err_to_proxy {
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -24,7 +24,7 @@
pub const RESOURCE_ID_PREFIX: &str = "rsid-";
/// Property prefix for storing custom user-defined properties.
pub const USER_PROPERTY_PREFIX: &str = "userprop-";
-/// Property scope for RMRK, used to signify that this property
+/// Property scope for RMRK, used to signify that this property
/// was created and is used by RMRK.
pub const RMRK_SCOPE: PropertyScope = PropertyScope::Rmrk;
pallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -104,7 +104,6 @@
}))
}
-
/// Get tokens owned by an account in a collection.
pub fn account_tokens<T: Config>(
account_id: T::AccountId,
pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -15,83 +15,83 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
//! # RMRK Core Proxy Pallet
-//!
+//!
//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).
-//!
+//!
//! - [`Config`]
//! - [`Call`]
//! - [`Pallet`]
-//!
+//!
//! ## Overview
-//!
-//! The RMRK Equip Proxy pallet mirrors the functionality of RMRK Equip,
+//!
+//! The RMRK Equip Proxy pallet mirrors the functionality of RMRK Equip,
//! binding its externalities to Unique's own underlying structure.
//! It is purposed to mimic RMRK Equip exactly, allowing seamless integrations
//! of solutions based on RMRK.
-//!
+//!
//! RMRK Equip itself contains functionality to equip NFTs, and work with Bases,
//! Parts, and Themes.
-//!
-//! Equip Proxy is responsible for a more specific area of RMRK, and heavily relies on the Core.
+//!
+//! Equip Proxy is responsible for a more specific area of RMRK, and heavily relies on the Core.
//! For a more foundational description of proxy implementation, please refer to [`pallet_rmrk_core`].
-//!
+//!
//! *Note*, that while RMRK itself is subject to active development and restructuring,
//! the proxy may be caught temporarily out of date.
-//!
+//!
//! ### What is RMRK?
-//!
-//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.
+//!
+//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.
//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.
-//!
+//!
//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,
-//! make use of specific changeable and partially shared metadata in the form of resources,
+//! make use of specific changeable and partially shared metadata in the form of resources,
//! and more.
-//!
+//!
//! Visit RMRK documentation and repositories to learn more:
//! - Docs: <https://docs.rmrk.app/getting-started/>
//! - FAQ: <https://coda.io/@rmrk/faq>
//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>
//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>
-//!
+//!
//! ## Proxy Implementation
-//!
+//!
//! An external user is supposed to be able to utilize this proxy as they would
//! utilize RMRK, and get exactly the same results. Normally, Unique transactions
//! are off-limits to RMRK collections and tokens, and vice versa. However,
//! the information stored on chain can be freely interpreted by storage reads and RPCs.
-//!
+//!
//! ### ID Mapping
-//!
+//!
//! RMRK's collections' IDs are counted independently of Unique's and start at 0.
//! Note that tokens' IDs still start at 1.
//! The collections themselves, as well as tokens, are stored as Unique collections,
//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).
-//!
+//!
//! ### External/Internal Collection Insulation
-//!
+//!
//! A Unique transaction cannot target collections purposed for RMRK,
-//! and they are flagged as `external` to specify that. On the other hand,
+//! and they are flagged as `external` to specify that. On the other hand,
//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.
-//!
+//!
//! ### Native Properties
-//!
-//! Many of RMRK's native parameters are stored as scoped properties of a collection
+//!
+//! Many of RMRK's native parameters are stored as scoped properties of a collection
//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`
//! is an unacceptable symbol in user-defined proeprties, which, along with other safeguards,
//! makes them impossible to tamper with.
-//!
+//!
//! ### Collection and NFT Types
-//!
-//! RMRK introduces the concept of a Base, which is a catalgoue of Parts,
+//!
+//! RMRK introduces the concept of a Base, which is a catalgoue of Parts,
//! possible components of an NFT. Due to its similarity with the functionality
//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes
-//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and
+//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and
//! [`NftType`](pallet_rmrk_core::misc::NftType).
-//!
+//!
//! ## Interface
-//!
+//!
//! ### Dispatchables
-//!
+//!
//! - `create_base` - Create a new Base.
//! - `theme_add` - Add a Theme to a Base.
//! - `equippable` - Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
@@ -184,16 +184,16 @@
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Create a new Base.
- ///
+ ///
/// Modeled after the [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
- ///
+ ///
/// # Permissions
/// - Anyone - will be assigned as the issuer of the base.
///
/// # Arguments:
/// - `base_type`: Arbitrary media type, e.g. "svg".
/// - `symbol`: Arbitrary client-chosen symbol.
- /// - `parts`: Array of Fixed and Slot parts composing the base,
+ /// - `parts`: Array of Fixed and Slot parts composing the base,
/// confined in length by [`RmrkPartsLimit`](up_data_structs::RmrkPartsLimit).
#[transactional]
#[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]
@@ -228,7 +228,10 @@
collection_id,
PropertyScope::Rmrk,
[
- <PalletCore<T>>::encode_rmrk_property(CollectionType, &misc::CollectionType::Base)?,
+ <PalletCore<T>>::encode_rmrk_property(
+ CollectionType,
+ &misc::CollectionType::Base,
+ )?,
<PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,
]
.into_iter(),
@@ -250,12 +253,12 @@
/// Add a Theme to a Base.
/// A Theme named "default" is required prior to adding other Themes.
- ///
+ ///
/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md).
///
/// # Permissions:
/// - Base issuer
- ///
+ ///
/// # Arguments:
/// - `base_id`: Base ID containing the Theme to be updated.
/// - `theme`: Theme to add to the Base. A Theme has a name and properties, which are an
@@ -314,12 +317,12 @@
}
/// Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
- ///
+ ///
/// Modeled after [equippable interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/equippable.md).
///
/// # Permissions:
/// - Base issuer
- ///
+ ///
/// # Arguments:
/// - `base_id`: Base containing the Slot Part to be updated.
/// - `part_id`: Slot Part whose Equippable List is being updated.