difftreelog
fix forward collection flags
in: master
10 files changed
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -9,7 +9,7 @@
traits::Get,
};
use sp_runtime::DispatchError;
-use up_data_structs::{CollectionId, CreateCollectionData};
+use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags};
use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};
@@ -80,6 +80,7 @@
sender: T::CrossAccountId,
payer: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError>;
/// Delete the collection.
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -212,8 +212,9 @@
owner: T::CrossAccountId,
payer: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, payer, data, CollectionFlags::default())
+ <PalletCommon<T>>::init_collection(owner, payer, data, flags)
}
/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -55,7 +55,7 @@
owner,
CollectionMode::NFT,
|owner: T::CrossAccountId, data| {
- <Pallet<T>>::init_collection(owner.clone(), owner, data, true)
+ <Pallet<T>>::init_collection(owner.clone(), owner, data, Default::default())
},
NonfungibleHandle::cast,
)
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -408,17 +408,9 @@
owner: T::CrossAccountId,
payer: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
- is_external: bool,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(
- owner,
- payer,
- data,
- CollectionFlags {
- external: is_external,
- ..Default::default()
- },
- )
+ <PalletCommon<T>>::init_collection(owner, payer, data, flags)
}
/// Destroy NFT collection
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK specification repository: <https://github.com/rmrk-team/rmrk-spec>52//!53//! ## Terminology54//!55//! For more information on RMRK, see RMRK's own documentation.56//!57//! ### Intro to RMRK58//!59//! - **Resource:** Additional piece of metadata of an NFT usually serving to add60//! a piece of media on top of the root metadata (NFT's own), be it a different wing61//! on the root template bird or something entirely unrelated.62//!63//! - **Base:** A list of possible "components" - Parts, a combination of which can64//! be appended/equipped to/on an NFT.65//!66//! - **Part:** Something that, together with other Parts, can constitute an NFT.67//! Parts are defined in the Base to which they belong. Parts can be either68//! of the `slot` type or `fixed` type. Slots are intended for equippables.69//! Note that "part of something" and "Part of a Base" can be easily confused,70//! and so in this documentation these words are distinguished by the capital letter.71//!72//! - **Theme:** Named objects of variable => value pairs which get interpolated into73//! the Base's `themable` Parts. Themes can hold any value, but are often represented74//! in RMRK's examples as colors applied to visible Parts.75//!76//! ### Peculiarities in Unique77//!78//! - **Scoped properties:** Properties that are normally obscured from users.79//! Their purpose is to contain structured metadata that was not included in the Unique standard80//! for collections and tokens, meant to be operated on by proxies and other outliers.81//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is82//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined83//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.84//!85//! - **Auxiliary properties:** A slightly different structure of properties,86//! trading universality of use for more convenient storage, writes and access.87//! Meant to be inaccessible to end users.88//!89//! ## Proxy Implementation90//!91//! An external user is supposed to be able to utilize this proxy as they would92//! utilize RMRK, and get exactly the same results. Normally, Unique transactions93//! are off-limits to RMRK collections and tokens, and vice versa. However,94//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.95//!96//! ### ID Mapping97//!98//! RMRK's collections' IDs are counted independently of Unique's and start at 0.99//! Note that tokens' IDs still start at 1.100//! The collections themselves, as well as tokens, are stored as Unique collections,101//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).102//!103//! ### External/Internal Collection Insulation104//!105//! A Unique transaction cannot target collections purposed for RMRK,106//! and they are flagged as `external` to specify that. On the other hand,107//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.108//!109//! ### Native Properties110//!111//! Many of RMRK's native parameters are stored as scoped properties of a collection112//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`113//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,114//! makes them impossible to tamper with.115//!116//! ### Collection and NFT Types, or Base, Parts and Themes Handling117//!118//! RMRK introduces the concept of a Base, which is a catalogue of Parts,119//! possible components of an NFT. Due to its similarity with the functionality120//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes121//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].122//!123//! ## Interface124//!125//! ### Dispatchables126//!127//! - `create_collection` - Create a new collection of NFTs.128//! - `destroy_collection` - Destroy a collection.129//! - `change_collection_issuer` - Change the issuer of a collection.130//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).131//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**132//! - `mint_nft` - Mint an NFT in a specified collection.133//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.134//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.135//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.136//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.137//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.138//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.139//! - `set_property` - Add or edit a custom user property of a token or a collection.140//! - `set_priority` - Set a different order of resource priorities for an NFT.141//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.142//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.143//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.144//! - `remove_resource` - Remove and erase a resource from an NFT.145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, BoundedVec, dispatch::DispatchResult};149use frame_system::{pallet_prelude::*, ensure_signed};150use sp_runtime::{DispatchError, Permill, traits::StaticLookup};151use sp_std::{152 vec::Vec,153 collections::{btree_set::BTreeSet, btree_map::BTreeMap},154};155use up_data_structs::{*, mapping::TokenAddressMapping};156use pallet_common::{157 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,158};159use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};160use pallet_structure::{Pallet as PalletStructure, Error as StructureError};161use pallet_evm::account::CrossAccountId;162use core::convert::AsRef;163164pub use pallet::*;165166#[cfg(feature = "runtime-benchmarks")]167pub mod benchmarking;168pub mod misc;169pub mod property;170pub mod rpc;171pub mod weights;172173pub type SelfWeightOf<T> = <T as Config>::WeightInfo;174175use weights::WeightInfo;176use misc::*;177pub use property::*;178179use RmrkProperty::*;180181/// Maximum number of levels of depth in the token nesting tree.182pub const NESTING_BUDGET: u32 = 5;183184type PendingTarget = (CollectionId, TokenId);185type PendingChild = (RmrkCollectionId, RmrkNftId);186type PendingChildrenSet = BTreeSet<PendingChild>;187188type BasesMap = BTreeMap<RmrkBaseId, u32>;189190#[frame_support::pallet]191pub mod pallet {192 use super::*;193 use pallet_evm::account;194195 #[pallet::config]196 pub trait Config:197 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config198 {199 /// Overarching event type.200 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;201202 /// The weight information of this pallet.203 type WeightInfo: WeightInfo;204 }205206 /// Latest yet-unused collection ID.207 #[pallet::storage]208 #[pallet::getter(fn collection_index)]209 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;210211 /// Mapping from RMRK collection ID to Unique's.212 #[pallet::storage]213 pub type UniqueCollectionId<T: Config> =214 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;215216 #[pallet::pallet]217 #[pallet::generate_store(pub(super) trait Store)]218 pub struct Pallet<T>(_);219220 #[pallet::event]221 #[pallet::generate_deposit(pub(super) fn deposit_event)]222 pub enum Event<T: Config> {223 CollectionCreated {224 issuer: T::AccountId,225 collection_id: RmrkCollectionId,226 },227 CollectionDestroyed {228 issuer: T::AccountId,229 collection_id: RmrkCollectionId,230 },231 IssuerChanged {232 old_issuer: T::AccountId,233 new_issuer: T::AccountId,234 collection_id: RmrkCollectionId,235 },236 CollectionLocked {237 issuer: T::AccountId,238 collection_id: RmrkCollectionId,239 },240 NftMinted {241 owner: T::AccountId,242 collection_id: RmrkCollectionId,243 nft_id: RmrkNftId,244 },245 NFTBurned {246 owner: T::AccountId,247 nft_id: RmrkNftId,248 },249 NFTSent {250 sender: T::AccountId,251 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,252 collection_id: RmrkCollectionId,253 nft_id: RmrkNftId,254 approval_required: bool,255 },256 NFTAccepted {257 sender: T::AccountId,258 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,259 collection_id: RmrkCollectionId,260 nft_id: RmrkNftId,261 },262 NFTRejected {263 sender: T::AccountId,264 collection_id: RmrkCollectionId,265 nft_id: RmrkNftId,266 },267 PropertySet {268 collection_id: RmrkCollectionId,269 maybe_nft_id: Option<RmrkNftId>,270 key: RmrkKeyString,271 value: RmrkValueString,272 },273 ResourceAdded {274 nft_id: RmrkNftId,275 resource_id: RmrkResourceId,276 },277 ResourceRemoval {278 nft_id: RmrkNftId,279 resource_id: RmrkResourceId,280 },281 ResourceAccepted {282 nft_id: RmrkNftId,283 resource_id: RmrkResourceId,284 },285 ResourceRemovalAccepted {286 nft_id: RmrkNftId,287 resource_id: RmrkResourceId,288 },289 PrioritySet {290 collection_id: RmrkCollectionId,291 nft_id: RmrkNftId,292 },293 }294295 #[pallet::error]296 pub enum Error<T> {297 /* Unique proxy-specific events */298 /// Property of the type of RMRK collection could not be read successfully.299 CorruptedCollectionType,300 // NftTypeEncodeError,301 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).302 RmrkPropertyKeyIsTooLong,303 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).304 RmrkPropertyValueIsTooLong,305 /// Could not find a property by the supplied key.306 RmrkPropertyIsNotFound,307 /// Something went wrong when decoding encoded data from the storage.308 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.309 UnableToDecodeRmrkData,310311 /* RMRK compatible events */312 /// Only destroying collections without tokens is allowed.313 CollectionNotEmpty,314 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.315 NoAvailableCollectionId,316 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.317 NoAvailableNftId,318 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.319 CollectionUnknown,320 /// No permission to perform action.321 NoPermission,322 /// Token is marked as non-transferable, and thus cannot be transferred.323 NonTransferable,324 /// Too many tokens created in the collection, no new ones are allowed.325 CollectionFullOrLocked,326 /// No such resource found.327 ResourceDoesntExist,328 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.329 /// Sending to self is redundant.330 CannotSendToDescendentOrSelf,331 /// Not the target owner of the sent NFT.332 CannotAcceptNonOwnedNft,333 /// Not the target owner of the sent NFT.334 CannotRejectNonOwnedNft,335 /// NFT was not sent and is not pending.336 CannotRejectNonPendingNft,337 /// Resource is not pending for the operation.338 ResourceNotPending,339 /// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.340 NoAvailableResourceId,341 }342343 #[pallet::call]344 impl<T: Config> Pallet<T> {345 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?346347 /// Create a new collection of NFTs.348 ///349 /// # Permissions:350 /// * Anyone - will be assigned as the issuer of the collection.351 ///352 /// # Arguments:353 /// - `origin`: sender of the transaction354 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.355 /// - `max`: Optional maximum number of tokens.356 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.357 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.358 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]359 pub fn create_collection(360 origin: OriginFor<T>,361 metadata: RmrkString,362 max: Option<u32>,363 symbol: RmrkCollectionSymbol,364 ) -> DispatchResult {365 let sender = ensure_signed(origin)?;366367 let limits = CollectionLimits {368 owner_can_transfer: Some(false),369 token_limit: max,370 ..Default::default()371 };372373 let data = CreateCollectionData {374 limits: Some(limits),375 token_prefix: symbol376 .into_inner()377 .try_into()378 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,379 permissions: Some(CollectionPermissions {380 nesting: Some(NestingPermissions {381 token_owner: true,382 collection_admin: false,383 restricted: None,384 #[cfg(feature = "runtime-benchmarks")]385 permissive: false,386 }),387 ..Default::default()388 }),389 ..Default::default()390 };391392 let unique_collection_id = Self::init_collection(393 T::CrossAccountId::from_sub(sender.clone()),394 data,395 [396 Self::encode_rmrk_property(Metadata, &metadata)?,397 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,398 ]399 .into_iter(),400 )?;401 let rmrk_collection_id = <CollectionIndex<T>>::get();402403 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);404405 <PalletCommon<T>>::set_scoped_collection_property(406 unique_collection_id,407 RMRK_SCOPE,408 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,409 )?;410411 <CollectionIndex<T>>::mutate(|n| *n += 1);412413 Self::deposit_event(Event::CollectionCreated {414 issuer: sender,415 collection_id: rmrk_collection_id,416 });417418 Ok(())419 }420421 /// Destroy a collection.422 ///423 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.424 ///425 /// # Permissions:426 /// * Collection issuer427 ///428 /// # Arguments:429 /// - `origin`: sender of the transaction430 /// - `collection_id`: RMRK ID of the collection to destroy.431 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]432 pub fn destroy_collection(433 origin: OriginFor<T>,434 collection_id: RmrkCollectionId,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437 let cross_sender = T::CrossAccountId::from_sub(sender.clone());438439 let collection = Self::get_typed_nft_collection(440 Self::unique_collection_id(collection_id)?,441 misc::CollectionType::Regular,442 )?;443 collection.check_is_external()?;444445 <PalletNft<T>>::destroy_collection(collection, &cross_sender)446 .map_err(Self::map_unique_err_to_proxy)?;447448 Self::deposit_event(Event::CollectionDestroyed {449 issuer: sender,450 collection_id,451 });452453 Ok(())454 }455456 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).457 ///458 /// # Permissions:459 /// * Collection issuer460 ///461 /// # Arguments:462 /// - `origin`: sender of the transaction463 /// - `collection_id`: RMRK collection ID to change the issuer of.464 /// - `new_issuer`: Collection's new issuer.465 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]466 pub fn change_collection_issuer(467 origin: OriginFor<T>,468 collection_id: RmrkCollectionId,469 new_issuer: <T::Lookup as StaticLookup>::Source,470 ) -> DispatchResult {471 let sender = ensure_signed(origin)?;472473 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;474 collection.check_is_external()?;475476 let new_issuer = T::Lookup::lookup(new_issuer)?;477478 Self::change_collection_owner(479 Self::unique_collection_id(collection_id)?,480 misc::CollectionType::Regular,481 sender.clone(),482 new_issuer.clone(),483 )?;484485 Self::deposit_event(Event::IssuerChanged {486 old_issuer: sender,487 new_issuer,488 collection_id,489 });490491 Ok(())492 }493494 /// "Lock" the collection and prevent new token creation. Cannot be undone.495 ///496 /// # Permissions:497 /// * Collection issuer498 ///499 /// # Arguments:500 /// - `origin`: sender of the transaction501 /// - `collection_id`: RMRK ID of the collection to lock.502 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]503 pub fn lock_collection(504 origin: OriginFor<T>,505 collection_id: RmrkCollectionId,506 ) -> DispatchResult {507 let sender = ensure_signed(origin)?;508 let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510 let collection = Self::get_typed_nft_collection(511 Self::unique_collection_id(collection_id)?,512 misc::CollectionType::Regular,513 )?;514 collection.check_is_external()?;515516 Self::check_collection_owner(&collection, &cross_sender)?;517518 let token_count = collection.total_supply();519520 let mut collection = collection.into_inner();521 collection.limits.token_limit = Some(token_count);522 collection.save()?;523524 Self::deposit_event(Event::CollectionLocked {525 issuer: sender,526 collection_id,527 });528529 Ok(())530 }531532 /// Mint an NFT in a specified collection.533 ///534 /// # Permissions:535 /// * Collection issuer536 ///537 /// # Arguments:538 /// - `origin`: sender of the transaction539 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).540 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.541 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.542 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.543 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.544 /// - `transferable`: Can this NFT be transferred? Cannot be changed.545 /// - `resources`: Resource data to be added to the NFT immediately after minting.546 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]547 pub fn mint_nft(548 origin: OriginFor<T>,549 owner: Option<T::AccountId>,550 collection_id: RmrkCollectionId,551 recipient: Option<T::AccountId>,552 royalty_amount: Option<Permill>,553 metadata: RmrkString,554 transferable: bool,555 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,556 ) -> DispatchResult {557 let sender = ensure_signed(origin)?;558 let cross_sender = T::CrossAccountId::from_sub(sender.clone());559560 let owner = owner.unwrap_or(sender.clone());561 let cross_owner = T::CrossAccountId::from_sub(owner.clone());562563 let collection = Self::get_typed_nft_collection(564 Self::unique_collection_id(collection_id)?,565 misc::CollectionType::Regular,566 )?;567 collection.check_is_external()?;568569 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {570 recipient: recipient.unwrap_or_else(|| owner.clone()),571 amount,572 });573574 let nft_id = Self::create_nft(575 &cross_sender,576 &cross_owner,577 &collection,578 [579 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,580 Self::encode_rmrk_property(Transferable, &transferable)?,581 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,582 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,583 Self::encode_rmrk_property(Metadata, &metadata)?,584 Self::encode_rmrk_property(Equipped, &false)?,585 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,586 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,587 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,588 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,589 ]590 .into_iter(),591 )592 .map_err(|err| match err {593 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),594 err => Self::map_unique_err_to_proxy(err),595 })?;596597 if let Some(resources) = resources {598 for resource in resources {599 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;600 }601 }602603 Self::deposit_event(Event::NftMinted {604 owner,605 collection_id,606 nft_id: nft_id.0,607 });608609 Ok(())610 }611612 /// Burn an NFT, destroying it and its nested tokens up to the specified limit.613 /// If the burning budget is exceeded, the transaction is reverted.614 ///615 /// This is the way to burn a nested token as well.616 ///617 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).618 ///619 /// # Permissions:620 /// * Token owner621 ///622 /// # Arguments:623 /// - `origin`: sender of the transaction624 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.625 /// - `nft_id`: ID of the NFT to be destroyed.626 /// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction627 /// is reverted if there are more tokens to burn in the nesting tree than this number.628 /// This is primarily a mechanism of transaction weight control.629 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]630 pub fn burn_nft(631 origin: OriginFor<T>,632 collection_id: RmrkCollectionId,633 nft_id: RmrkNftId,634 max_burns: u32,635 ) -> DispatchResult {636 let sender = ensure_signed(origin)?;637 let cross_sender = T::CrossAccountId::from_sub(sender.clone());638639 let collection = Self::get_typed_nft_collection(640 Self::unique_collection_id(collection_id)?,641 misc::CollectionType::Regular,642 )?;643 collection.check_is_external()?;644645 Self::destroy_nft(646 cross_sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 max_burns,650 <Error<T>>::NoPermission,651 )652 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;653654 Self::deposit_event(Event::NFTBurned {655 owner: sender,656 nft_id,657 });658659 Ok(())660 }661662 /// Transfer an NFT from an account/NFT A to another account/NFT B.663 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].664 ///665 /// If the target owner is an NFT owned by another account, then the NFT will enter666 /// the pending state and will have to be accepted by the other account.667 ///668 /// # Permissions:669 /// - Token owner670 ///671 /// # Arguments:672 /// - `origin`: sender of the transaction673 /// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.674 /// - `rmrk_nft_id`: ID of the NFT to be transferred.675 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.676 #[pallet::weight(<SelfWeightOf<T>>::send())]677 pub fn send(678 origin: OriginFor<T>,679 rmrk_collection_id: RmrkCollectionId,680 rmrk_nft_id: RmrkNftId,681 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,682 ) -> DispatchResult {683 let sender = ensure_signed(origin.clone())?;684 let cross_sender = T::CrossAccountId::from_sub(sender.clone());685686 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;687 let nft_id = rmrk_nft_id.into();688689 let collection =690 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691 collection.check_is_external()?;692693 let token_data =694 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;695696 let from = token_data.owner;697698 ensure!(699 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,700 <Error<T>>::NonTransferable701 );702703 ensure!(704 Self::get_nft_property_decoded::<Option<PendingTarget>>(705 collection_id,706 nft_id,707 RmrkProperty::PendingNftAccept708 )?709 .is_none(),710 <Error<T>>::NoPermission711 );712713 let target_owner;714 let approval_required;715716 match new_owner {717 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {718 target_owner = T::CrossAccountId::from_sub(account_id.clone());719 approval_required = false;720 }721 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(722 target_collection_id,723 target_nft_id,724 ) => {725 let target_collection_id = Self::unique_collection_id(target_collection_id)?;726727 let target_nft_budget = budget::Value::new(NESTING_BUDGET);728729 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(730 target_collection_id,731 target_nft_id.into(),732 Some((collection_id, nft_id)),733 &target_nft_budget,734 )735 .map_err(Self::map_unique_err_to_proxy)?;736737 approval_required = cross_sender != target_nft_owner;738739 if approval_required {740 target_owner = target_nft_owner;741742 <PalletNft<T>>::set_scoped_token_property(743 collection.id,744 nft_id,745 RMRK_SCOPE,746 Self::encode_rmrk_property::<Option<PendingTarget>>(747 PendingNftAccept,748 &Some((target_collection_id, target_nft_id.into())),749 )?,750 )?;751752 Self::insert_pending_child(753 (target_collection_id, target_nft_id.into()),754 (rmrk_collection_id, rmrk_nft_id),755 )?;756 } else {757 target_owner = T::CrossTokenAddressMapping::token_to_address(758 target_collection_id,759 target_nft_id.into(),760 );761 }762 }763 }764765 let src_nft_budget = budget::Value::new(NESTING_BUDGET);766767 <PalletNft<T>>::transfer_from(768 &collection,769 &cross_sender,770 &from,771 &target_owner,772 nft_id,773 &src_nft_budget,774 )775 .map_err(Self::map_unique_err_to_proxy)?;776777 Self::deposit_event(Event::NFTSent {778 sender,779 recipient: new_owner,780 collection_id: rmrk_collection_id,781 nft_id: rmrk_nft_id,782 approval_required,783 });784785 Ok(())786 }787788 /// Accept an NFT sent from another account to self or an owned NFT.789 ///790 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.791 ///792 /// # Permissions:793 /// - Token-owner-to-be794 ///795 /// # Arguments:796 /// - `origin`: sender of the transaction797 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.798 /// - `rmrk_nft_id`: ID of the NFT to be accepted.799 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,800 /// whichever the accepted NFT was sent to.801 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]802 pub fn accept_nft(803 origin: OriginFor<T>,804 rmrk_collection_id: RmrkCollectionId,805 rmrk_nft_id: RmrkNftId,806 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,807 ) -> DispatchResult {808 let sender = ensure_signed(origin.clone())?;809 let cross_sender = T::CrossAccountId::from_sub(sender.clone());810811 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812 let nft_id = rmrk_nft_id.into();813814 let collection =815 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816 collection.check_is_external()?;817818 let new_cross_owner = match new_owner {819 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {820 T::CrossAccountId::from_sub(account_id.clone())821 }822 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(823 target_collection_id,824 target_nft_id,825 ) => {826 let target_collection_id = Self::unique_collection_id(target_collection_id)?;827828 T::CrossTokenAddressMapping::token_to_address(829 target_collection_id,830 TokenId(target_nft_id),831 )832 }833 };834835 let budget = budget::Value::new(NESTING_BUDGET);836837 <PalletNft<T>>::transfer(838 &collection,839 &cross_sender,840 &new_cross_owner,841 nft_id,842 &budget,843 )844 .map_err(|err| {845 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {846 <Error<T>>::CannotAcceptNonOwnedNft.into()847 } else {848 Self::map_unique_err_to_proxy(err)849 }850 })?;851852 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(853 collection_id,854 nft_id,855 RmrkProperty::PendingNftAccept,856 )?;857858 if let Some(pending_target) = pending_target {859 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;860861 <PalletNft<T>>::set_scoped_token_property(862 collection.id,863 nft_id,864 RMRK_SCOPE,865 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,866 )?;867 }868869 Self::deposit_event(Event::NFTAccepted {870 sender,871 recipient: new_owner,872 collection_id: rmrk_collection_id,873 nft_id: rmrk_nft_id,874 });875876 Ok(())877 }878879 /// Reject an NFT sent from another account to self or owned NFT.880 /// The NFT in question will not be sent back and burnt instead.881 ///882 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.883 ///884 /// # Permissions:885 /// - Token-owner-to-be-not886 ///887 /// # Arguments:888 /// - `origin`: sender of the transaction889 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.890 /// - `rmrk_nft_id`: ID of the NFT to be rejected.891 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]892 pub fn reject_nft(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 rmrk_nft_id: RmrkNftId,896 ) -> DispatchResult {897 let sender = ensure_signed(origin)?;898 let cross_sender = T::CrossAccountId::from_sub(sender.clone());899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let nft_id = rmrk_nft_id.into();902903 let collection =904 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905 collection.check_is_external()?;906907 ensure!(908 <TokenData<T>>::get((collection_id, nft_id)).is_some(),909 <Error<T>>::NoAvailableNftId910 );911912 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(913 collection_id,914 nft_id,915 RmrkProperty::PendingNftAccept,916 )?;917918 match pending_target {919 Some(pending_target) => {920 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?921 }922 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),923 }924925 Self::destroy_nft(926 cross_sender,927 collection_id,928 nft_id,929 NESTING_BUDGET,930 <Error<T>>::CannotRejectNonOwnedNft,931 )932 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;933934 Self::deposit_event(Event::NFTRejected {935 sender,936 collection_id: rmrk_collection_id,937 nft_id: rmrk_nft_id,938 });939940 Ok(())941 }942943 /// Accept the addition of a newly created pending resource to an existing NFT.944 ///945 /// This transaction is needed when a resource is created and assigned to an NFT946 /// by a non-owner, i.e. the collection issuer, with one of the947 /// [`add_...` transactions](Pallet::add_basic_resource).948 ///949 /// # Permissions:950 /// - Token owner951 ///952 /// # Arguments:953 /// - `origin`: sender of the transaction954 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.955 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.956 /// - `resource_id`: ID of the newly created pending resource.957 /// accept the addition of a new resource to an existing NFT958 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]959 pub fn accept_resource(960 origin: OriginFor<T>,961 rmrk_collection_id: RmrkCollectionId,962 rmrk_nft_id: RmrkNftId,963 resource_id: RmrkResourceId,964 ) -> DispatchResult {965 let sender = ensure_signed(origin)?;966 let cross_sender = T::CrossAccountId::from_sub(sender);967968 let collection_id = Self::unique_collection_id(rmrk_collection_id)969 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;970 let collection =971 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;972 collection.check_is_external()?;973974 let nft_id = rmrk_nft_id.into();975976 let budget = budget::Value::new(NESTING_BUDGET);977978 let nft_owner =979 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)980 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;981982 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {983 ensure!(res.pending, <Error<T>>::ResourceNotPending);984 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);985986 res.pending = false;987988 Ok(())989 })?;990991 Self::deposit_event(Event::<T>::ResourceAccepted {992 nft_id: rmrk_nft_id,993 resource_id,994 });995996 Ok(())997 }998999 /// Accept the removal of a removal-pending resource from an NFT.1000 ///1001 /// This transaction is needed when a non-owner, i.e. the collection issuer,1002 /// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1003 ///1004 /// # Permissions:1005 /// - Token owner1006 ///1007 /// # Arguments:1008 /// - `origin`: sender of the transaction1009 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1010 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1011 /// - `resource_id`: ID of the removal-pending resource.1012 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1013 pub fn accept_resource_removal(1014 origin: OriginFor<T>,1015 rmrk_collection_id: RmrkCollectionId,1016 rmrk_nft_id: RmrkNftId,1017 resource_id: RmrkResourceId,1018 ) -> DispatchResult {1019 let sender = ensure_signed(origin)?;1020 let cross_sender = T::CrossAccountId::from_sub(sender);10211022 let collection_id = Self::unique_collection_id(rmrk_collection_id)1023 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1024 let collection =1025 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1026 collection.check_is_external()?;10271028 let nft_id = rmrk_nft_id.into();10291030 let budget = budget::Value::new(NESTING_BUDGET);10311032 let nft_owner =1033 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1034 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10351036 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10371038 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10391040 let resource_info = <PalletNft<T>>::token_aux_property((1041 collection_id,1042 nft_id,1043 RMRK_SCOPE,1044 resource_id_key.clone(),1045 ))1046 .ok_or(<Error<T>>::ResourceDoesntExist)?;10471048 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10491050 ensure!(1051 resource_info.pending_removal,1052 <Error<T>>::ResourceNotPending1053 );10541055 <PalletNft<T>>::remove_token_aux_property(1056 collection_id,1057 nft_id,1058 RMRK_SCOPE,1059 resource_id_key,1060 );10611062 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1063 let base_id = resource.base;10641065 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1066 }10671068 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1069 nft_id: rmrk_nft_id,1070 resource_id,1071 });10721073 Ok(())1074 }10751076 /// Add or edit a custom user property, a key-value pair, describing the metadata1077 /// of a token or a collection, on either one of these.1078 ///1079 /// Note that in this proxy implementation many details regarding RMRK are stored1080 /// as scoped properties prefixed with "rmrk:", normally inaccessible1081 /// to external transactions and RPCs.1082 ///1083 /// # Permissions:1084 /// - Collection issuer - in case of collection property1085 /// - Token owner - in case of NFT property1086 ///1087 /// # Arguments:1088 /// - `origin`: sender of the transaction1089 /// - `rmrk_collection_id`: RMRK collection ID.1090 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1091 /// - `key`: Key of the custom property to be referenced by.1092 /// - `value`: Value of the custom property to be stored.1093 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1094 pub fn set_property(1095 origin: OriginFor<T>,1096 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1097 maybe_nft_id: Option<RmrkNftId>,1098 key: RmrkKeyString,1099 value: RmrkValueString,1100 ) -> DispatchResult {1101 let sender = ensure_signed(origin)?;1102 let sender = T::CrossAccountId::from_sub(sender);11031104 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1105 let collection =1106 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1107 collection.check_is_external()?;11081109 let budget = budget::Value::new(NESTING_BUDGET);11101111 match maybe_nft_id {1112 Some(nft_id) => {1113 let token_id: TokenId = nft_id.into();11141115 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1116 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11171118 <PalletNft<T>>::set_scoped_token_property(1119 collection_id,1120 token_id,1121 RMRK_SCOPE,1122 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1123 )?;1124 }1125 None => {1126 let collection = Self::get_typed_nft_collection(1127 collection_id,1128 misc::CollectionType::Regular,1129 )?;11301131 Self::check_collection_owner(&collection, &sender)?;11321133 <PalletCommon<T>>::set_scoped_collection_property(1134 collection_id,1135 RMRK_SCOPE,1136 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137 )?;1138 }1139 }11401141 Self::deposit_event(Event::PropertySet {1142 collection_id: rmrk_collection_id,1143 maybe_nft_id,1144 key,1145 value,1146 });11471148 Ok(())1149 }11501151 /// Set a different order of resource priorities for an NFT. Priorities can be used,1152 /// for example, for order of rendering.1153 ///1154 /// Note that the priorities are not updated automatically, and are an empty vector1155 /// by default. There is no pre-set definition for the order to be particular,1156 /// it can be interpreted arbitrarily use-case by use-case.1157 ///1158 /// # Permissions:1159 /// - Token owner1160 ///1161 /// # Arguments:1162 /// - `origin`: sender of the transaction1163 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1164 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1165 /// - `priorities`: Ordered vector of resource IDs.1166 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1167 pub fn set_priority(1168 origin: OriginFor<T>,1169 rmrk_collection_id: RmrkCollectionId,1170 rmrk_nft_id: RmrkNftId,1171 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1172 ) -> DispatchResult {1173 let sender = ensure_signed(origin)?;1174 let sender = T::CrossAccountId::from_sub(sender);11751176 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1177 let nft_id = rmrk_nft_id.into();11781179 let collection =1180 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1181 collection.check_is_external()?;11821183 let budget = budget::Value::new(NESTING_BUDGET);11841185 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1186 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11871188 <PalletNft<T>>::set_scoped_token_property(1189 collection_id,1190 nft_id,1191 RMRK_SCOPE,1192 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1193 )?;11941195 Self::deposit_event(Event::<T>::PrioritySet {1196 collection_id: rmrk_collection_id,1197 nft_id: rmrk_nft_id,1198 });11991200 Ok(())1201 }12021203 /// Create and set/propose a basic resource for an NFT.1204 ///1205 /// A basic resource is the simplest, lacking a Base and anything that comes with it.1206 /// See RMRK docs for more information and examples.1207 ///1208 /// # Permissions:1209 /// - Collection issuer - if not the token owner, adding the resource will warrant1210 /// the owner's [acceptance](Pallet::accept_resource).1211 ///1212 /// # Arguments:1213 /// - `origin`: sender of the transaction1214 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1215 /// - `nft_id`: ID of the NFT to assign a resource to.1216 /// - `resource`: Data of the resource to be created.1217 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1218 pub fn add_basic_resource(1219 origin: OriginFor<T>,1220 rmrk_collection_id: RmrkCollectionId,1221 nft_id: RmrkNftId,1222 resource: RmrkBasicResource,1223 ) -> DispatchResult {1224 let sender = ensure_signed(origin.clone())?;12251226 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1227 let collection =1228 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1229 collection.check_is_external()?;12301231 let resource_id = Self::resource_add(1232 sender,1233 collection_id,1234 nft_id.into(),1235 RmrkResourceTypes::Basic(resource),1236 )?;12371238 Self::deposit_event(Event::ResourceAdded {1239 nft_id,1240 resource_id,1241 });1242 Ok(())1243 }12441245 /// Create and set/propose a composable resource for an NFT.1246 ///1247 /// A composable resource links to a Base and has a subset of its Parts it is composed of.1248 /// See RMRK docs for more information and examples.1249 ///1250 /// # Permissions:1251 /// - Collection issuer - if not the token owner, adding the resource will warrant1252 /// the owner's [acceptance](Pallet::accept_resource).1253 ///1254 /// # Arguments:1255 /// - `origin`: sender of the transaction1256 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1257 /// - `nft_id`: ID of the NFT to assign a resource to.1258 /// - `resource`: Data of the resource to be created.1259 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1260 pub fn add_composable_resource(1261 origin: OriginFor<T>,1262 rmrk_collection_id: RmrkCollectionId,1263 nft_id: RmrkNftId,1264 resource: RmrkComposableResource,1265 ) -> DispatchResult {1266 let sender = ensure_signed(origin.clone())?;12671268 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1269 let collection =1270 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1271 collection.check_is_external()?;12721273 let base_id = resource.base;12741275 let resource_id = Self::resource_add(1276 sender,1277 collection_id,1278 nft_id.into(),1279 RmrkResourceTypes::Composable(resource),1280 )?;12811282 <PalletNft<T>>::try_mutate_token_aux_property(1283 collection_id,1284 nft_id.into(),1285 RMRK_SCOPE,1286 Self::get_scoped_property_key(AssociatedBases)?,1287 |value| -> DispatchResult {1288 let mut bases: BasesMap = match value {1289 Some(value) => Self::decode_property_value(value)?,1290 None => BasesMap::new(),1291 };12921293 *bases.entry(base_id).or_insert(0) += 1;12941295 *value = Some(Self::encode_property_value(&bases)?);1296 Ok(())1297 },1298 )?;12991300 Self::deposit_event(Event::ResourceAdded {1301 nft_id,1302 resource_id,1303 });1304 Ok(())1305 }13061307 /// Create and set/propose a slot resource for an NFT.1308 ///1309 /// A slot resource links to a Base and a slot ID in it which it can fit into.1310 /// See RMRK docs for more information and examples.1311 ///1312 /// # Permissions:1313 /// - Collection issuer - if not the token owner, adding the resource will warrant1314 /// the owner's [acceptance](Pallet::accept_resource).1315 ///1316 /// # Arguments:1317 /// - `origin`: sender of the transaction1318 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1319 /// - `nft_id`: ID of the NFT to assign a resource to.1320 /// - `resource`: Data of the resource to be created.1321 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1322 pub fn add_slot_resource(1323 origin: OriginFor<T>,1324 rmrk_collection_id: RmrkCollectionId,1325 nft_id: RmrkNftId,1326 resource: RmrkSlotResource,1327 ) -> DispatchResult {1328 let sender = ensure_signed(origin.clone())?;13291330 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1331 let collection =1332 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1333 collection.check_is_external()?;13341335 let resource_id = Self::resource_add(1336 sender,1337 collection_id,1338 nft_id.into(),1339 RmrkResourceTypes::Slot(resource),1340 )?;13411342 Self::deposit_event(Event::ResourceAdded {1343 nft_id,1344 resource_id,1345 });1346 Ok(())1347 }13481349 /// Remove and erase a resource from an NFT.1350 ///1351 /// If the sender does not own the NFT, then it will be pending confirmation,1352 /// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1353 ///1354 /// # Permissions1355 /// - Collection issuer1356 ///1357 /// # Arguments1358 /// - `origin`: sender of the transaction1359 /// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1360 /// - `nft_id`: ID of the NFT with a resource to be removed.1361 /// - `resource_id`: ID of the resource to be removed.1362 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1363 pub fn remove_resource(1364 origin: OriginFor<T>,1365 rmrk_collection_id: RmrkCollectionId,1366 nft_id: RmrkNftId,1367 resource_id: RmrkResourceId,1368 ) -> DispatchResult {1369 let sender = ensure_signed(origin.clone())?;13701371 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1372 let collection =1373 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1374 collection.check_is_external()?;13751376 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13771378 Self::deposit_event(Event::ResourceRemoval {1379 nft_id,1380 resource_id,1381 });1382 Ok(())1383 }1384 }1385}13861387impl<T: Config> Pallet<T> {1388 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1389 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1390 let key = rmrk_key.to_key::<T>()?;13911392 let scoped_key = RMRK_SCOPE1393 .apply(key)1394 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13951396 Ok(scoped_key)1397 }13981399 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1400 /// and encoding the value from an arbitrary type into bytes.1401 pub fn encode_rmrk_property<E: Encode>(1402 rmrk_key: RmrkProperty,1403 value: &E,1404 ) -> Result<Property, DispatchError> {1405 let key = rmrk_key.to_key::<T>()?;14061407 let value = Self::encode_property_value(value)?;14081409 let property = Property { key, value };14101411 Ok(property)1412 }14131414 /// Encode property value from an arbitrary type into bytes for storage.1415 pub fn encode_property_value<E: Encode, S: Get<u32>>(1416 value: &E,1417 ) -> Result<BoundedBytes<S>, DispatchError> {1418 let value = value1419 .encode()1420 .try_into()1421 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14221423 Ok(value)1424 }14251426 /// Decode property value from bytes into an arbitrary type.1427 pub fn decode_property_value<D: Decode, S: Get<u32>>(1428 vec: &BoundedBytes<S>,1429 ) -> Result<D, DispatchError> {1430 vec.decode()1431 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1432 }14331434 /// Change the limit of a property value byte vector.1435 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1436 where1437 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1438 {1439 vec.rebind()1440 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1441 }14421443 /// Initialize a new NFT collection with certain RMRK-scoped properties.1444 ///1445 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1446 fn init_collection(1447 sender: T::CrossAccountId,1448 data: CreateCollectionData<T::AccountId>,1449 properties: impl Iterator<Item = Property>,1450 ) -> Result<CollectionId, DispatchError> {1451 let collection_id = <PalletNft<T>>::init_collection(sender.clone(), sender, data, true);14521453 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1454 return Err(<Error<T>>::NoAvailableCollectionId.into());1455 }14561457 <PalletCommon<T>>::set_scoped_collection_properties(1458 collection_id?,1459 RMRK_SCOPE,1460 properties,1461 )?;14621463 collection_id1464 }14651466 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1467 ///1468 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1469 pub fn create_nft(1470 sender: &T::CrossAccountId,1471 owner: &T::CrossAccountId,1472 collection: &NonfungibleHandle<T>,1473 properties: impl Iterator<Item = Property>,1474 ) -> Result<TokenId, DispatchError> {1475 let data = CreateNftExData {1476 properties: BoundedVec::default(),1477 owner: owner.clone(),1478 };14791480 let budget = budget::Value::new(NESTING_BUDGET);14811482 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14831484 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14851486 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14871488 Ok(nft_id)1489 }14901491 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1492 ///1493 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1494 fn destroy_nft(1495 sender: T::CrossAccountId,1496 collection_id: CollectionId,1497 token_id: TokenId,1498 max_burns: u32,1499 error_if_not_owned: Error<T>,1500 ) -> DispatchResultWithPostInfo {1501 let collection =1502 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15031504 let token_data =1505 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15061507 let from = token_data.owner;15081509 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15101511 ensure!(1512 <PalletStructure<T>>::check_indirectly_owned(1513 sender.clone(),1514 collection_id,1515 token_id,1516 None,1517 &owner_check_budget1518 )?,1519 error_if_not_owned,1520 );15211522 let burns_budget = budget::Value::new(max_burns);1523 let breadth_budget = budget::Value::new(max_burns);15241525 <PalletNft<T>>::burn_recursively(1526 &collection,1527 &from,1528 token_id,1529 &burns_budget,1530 &breadth_budget,1531 )1532 }15331534 /// Add a sent token pending acceptance to the target owning token as a property.1535 fn insert_pending_child(1536 target: (CollectionId, TokenId),1537 child: (RmrkCollectionId, RmrkNftId),1538 ) -> DispatchResult {1539 Self::mutate_pending_children(target, |pending_children| {1540 pending_children.insert(child);1541 })1542 }15431544 /// Remove a sent token pending acceptance from the target token's properties.1545 fn remove_pending_child(1546 target: (CollectionId, TokenId),1547 child: (RmrkCollectionId, RmrkNftId),1548 ) -> DispatchResult {1549 Self::mutate_pending_children(target, |pending_children| {1550 pending_children.remove(&child);1551 })1552 }15531554 /// Apply a mutation to the property of a token containing sent tokens1555 /// that are currently pending acceptance.1556 fn mutate_pending_children(1557 (target_collection_id, target_nft_id): (CollectionId, TokenId),1558 f: impl FnOnce(&mut PendingChildrenSet),1559 ) -> DispatchResult {1560 <PalletNft<T>>::try_mutate_token_aux_property(1561 target_collection_id,1562 target_nft_id,1563 RMRK_SCOPE,1564 Self::get_scoped_property_key(PendingChildren)?,1565 |pending_children| -> DispatchResult {1566 let mut map = match pending_children {1567 Some(map) => Self::decode_property_value(map)?,1568 None => PendingChildrenSet::new(),1569 };15701571 f(&mut map);15721573 *pending_children = Some(Self::encode_property_value(&map)?);15741575 Ok(())1576 },1577 )1578 }15791580 /// Get an iterator from a token's property containing tokens sent to it1581 /// that are currently pending acceptance.1582 fn iterate_pending_children(1583 collection_id: CollectionId,1584 nft_id: TokenId,1585 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1586 let property = <PalletNft<T>>::token_aux_property((1587 collection_id,1588 nft_id,1589 RMRK_SCOPE,1590 Self::get_scoped_property_key(PendingChildren)?,1591 ));15921593 let pending_children = match property {1594 Some(map) => Self::decode_property_value(&map)?,1595 None => PendingChildrenSet::new(),1596 };15971598 Ok(pending_children.into_iter())1599 }16001601 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1602 /// Thus, the returned resource ID should be used.1603 ///1604 /// Resource IDs are unique only across an NFT.1605 fn acquire_next_resource_id(1606 collection_id: CollectionId,1607 nft_id: TokenId,1608 ) -> Result<RmrkResourceId, DispatchError> {1609 let resource_id: RmrkResourceId =1610 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16111612 let next_id = resource_id1613 .checked_add(1)1614 .ok_or(<Error<T>>::NoAvailableResourceId)?;16151616 <PalletNft<T>>::set_scoped_token_property(1617 collection_id,1618 nft_id,1619 RMRK_SCOPE,1620 Self::encode_rmrk_property(NextResourceId, &next_id)?,1621 )?;16221623 Ok(resource_id)1624 }16251626 /// Create and add a resource for a regular NFT, mark it as pending if the sender1627 /// is not the token owner. The sender must be the collection owner.1628 fn resource_add(1629 sender: T::AccountId,1630 collection_id: CollectionId,1631 nft_id: TokenId,1632 resource: RmrkResourceTypes,1633 ) -> Result<RmrkResourceId, DispatchError> {1634 let collection =1635 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1636 ensure!(collection.owner == sender, Error::<T>::NoPermission);16371638 let sender = T::CrossAccountId::from_sub(sender);1639 let budget = budget::Value::new(NESTING_BUDGET);16401641 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1642 .map_err(Self::map_unique_err_to_proxy)?;16431644 let pending = sender != nft_owner;16451646 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16471648 let resource_info = RmrkResourceInfo {1649 id,1650 resource,1651 pending,1652 pending_removal: false,1653 };16541655 <PalletNft<T>>::try_mutate_token_aux_property(1656 collection_id,1657 nft_id,1658 RMRK_SCOPE,1659 Self::get_scoped_property_key(ResourceId(id))?,1660 |value| -> DispatchResult {1661 *value = Some(Self::encode_property_value(&resource_info)?);16621663 Ok(())1664 },1665 )?;16661667 Ok(id)1668 }16691670 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1671 /// The sender must be the collection owner.1672 fn resource_remove(1673 sender: T::AccountId,1674 collection_id: CollectionId,1675 nft_id: TokenId,1676 resource_id: RmrkResourceId,1677 ) -> DispatchResult {1678 let collection =1679 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1680 ensure!(collection.owner == sender, Error::<T>::NoPermission);16811682 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16831684 let resource = <PalletNft<T>>::token_aux_property((1685 collection_id,1686 nft_id,1687 RMRK_SCOPE,1688 resource_id_key.clone(),1689 ))1690 .ok_or(<Error<T>>::ResourceDoesntExist)?;16911692 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16931694 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1695 let topmost_owner =1696 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16971698 let sender = T::CrossAccountId::from_sub(sender);1699 if topmost_owner == sender {1700 <PalletNft<T>>::remove_token_aux_property(1701 collection_id,1702 nft_id,1703 RMRK_SCOPE,1704 Self::get_scoped_property_key(ResourceId(resource_id))?,1705 );17061707 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1708 let base_id = resource.base;17091710 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1711 }1712 } else {1713 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1714 res.pending_removal = true;17151716 Ok(())1717 })?;1718 }17191720 Ok(())1721 }17221723 /// Remove a Base ID from an NFT if they are associated.1724 /// The Base itself is deleted if the number of associated NFTs reaches 0.1725 fn remove_associated_base_id(1726 collection_id: CollectionId,1727 nft_id: TokenId,1728 base_id: RmrkBaseId,1729 ) -> DispatchResult {1730 <PalletNft<T>>::try_mutate_token_aux_property(1731 collection_id,1732 nft_id,1733 RMRK_SCOPE,1734 Self::get_scoped_property_key(AssociatedBases)?,1735 |value| -> DispatchResult {1736 let mut bases: BasesMap = match value {1737 Some(value) => Self::decode_property_value(value)?,1738 None => BasesMap::new(),1739 };17401741 let remaining = bases.get(&base_id);17421743 if let Some(remaining) = remaining {1744 if let Some(0) | None = remaining.checked_sub(1) {1745 bases.remove(&base_id);1746 }1747 }17481749 *value = Some(Self::encode_property_value(&bases)?);1750 Ok(())1751 },1752 )1753 }17541755 /// Apply a mutation to a resource stored in the token properties of an NFT.1756 fn try_mutate_resource_info(1757 collection_id: CollectionId,1758 nft_id: TokenId,1759 resource_id: RmrkResourceId,1760 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1761 ) -> DispatchResult {1762 <PalletNft<T>>::try_mutate_token_aux_property(1763 collection_id,1764 nft_id,1765 RMRK_SCOPE,1766 Self::get_scoped_property_key(ResourceId(resource_id))?,1767 |value| match value {1768 Some(value) => {1769 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17701771 f(&mut resource_info)?;17721773 *value = Self::encode_property_value(&resource_info)?;17741775 Ok(())1776 }1777 None => Err(<Error<T>>::ResourceDoesntExist.into()),1778 },1779 )1780 }17811782 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1783 fn change_collection_owner(1784 collection_id: CollectionId,1785 collection_type: misc::CollectionType,1786 sender: T::AccountId,1787 new_owner: T::AccountId,1788 ) -> DispatchResult {1789 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1790 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17911792 let mut collection = collection.into_inner();17931794 collection.owner = new_owner;1795 collection.save()1796 }17971798 /// Ensure that an account is the collection owner/issuer, return an error if not.1799 pub fn check_collection_owner(1800 collection: &NonfungibleHandle<T>,1801 account: &T::CrossAccountId,1802 ) -> DispatchResult {1803 collection1804 .check_is_owner(account)1805 .map_err(Self::map_unique_err_to_proxy)1806 }18071808 /// Get the latest yet-unused RMRK collection index from the storage.1809 pub fn last_collection_idx() -> RmrkCollectionId {1810 <CollectionIndex<T>>::get()1811 }18121813 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1814 pub fn unique_collection_id(1815 rmrk_collection_id: RmrkCollectionId,1816 ) -> Result<CollectionId, DispatchError> {1817 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1818 .map_err(|_| <Error<T>>::CollectionUnknown.into())1819 }18201821 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1822 pub fn rmrk_collection_id(1823 unique_collection_id: CollectionId,1824 ) -> Result<RmrkCollectionId, DispatchError> {1825 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1826 }18271828 /// Fetch a Unique NFT collection.1829 pub fn get_nft_collection(1830 collection_id: CollectionId,1831 ) -> Result<NonfungibleHandle<T>, DispatchError> {1832 let collection = <CollectionHandle<T>>::try_get(collection_id)1833 .map_err(|_| <Error<T>>::CollectionUnknown)?;18341835 match collection.mode {1836 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1837 _ => Err(<Error<T>>::CollectionUnknown.into()),1838 }1839 }18401841 /// Check if an NFT collection with such an ID exists.1842 pub fn collection_exists(collection_id: CollectionId) -> bool {1843 <CollectionHandle<T>>::try_get(collection_id).is_ok()1844 }18451846 /// Fetch and decode a RMRK-scoped collection property value in bytes.1847 pub fn get_collection_property(1848 collection_id: CollectionId,1849 key: RmrkProperty,1850 ) -> Result<PropertyValue, DispatchError> {1851 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1852 .get(&Self::get_scoped_property_key(key)?)1853 .ok_or(<Error<T>>::CollectionUnknown)?1854 .clone();18551856 Ok(collection_property)1857 }18581859 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1860 pub fn get_collection_property_decoded<V: Decode>(1861 collection_id: CollectionId,1862 key: RmrkProperty,1863 ) -> Result<V, DispatchError> {1864 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1865 }18661867 /// Get the type of a collection stored as a scoped property.1868 ///1869 /// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1870 pub fn get_collection_type(1871 collection_id: CollectionId,1872 ) -> Result<misc::CollectionType, DispatchError> {1873 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1874 if err != <Error<T>>::CollectionUnknown.into() {1875 <Error<T>>::CorruptedCollectionType.into()1876 } else {1877 err1878 }1879 })1880 }18811882 /// Ensure that the type of the collection equals the provided type,1883 /// otherwise return an error.1884 pub fn ensure_collection_type(1885 collection_id: CollectionId,1886 collection_type: misc::CollectionType,1887 ) -> DispatchResult {1888 let actual_type = Self::get_collection_type(collection_id)?;1889 ensure!(1890 actual_type == collection_type,1891 <CommonError<T>>::NoPermission1892 );18931894 Ok(())1895 }18961897 /// Fetch an NFT collection, but make sure it has the appropriate type.1898 pub fn get_typed_nft_collection(1899 collection_id: CollectionId,1900 collection_type: misc::CollectionType,1901 ) -> Result<NonfungibleHandle<T>, DispatchError> {1902 Self::ensure_collection_type(collection_id, collection_type)?;19031904 Self::get_nft_collection(collection_id)1905 }19061907 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1908 /// but also return the Unique collection ID.1909 pub fn get_typed_nft_collection_mapped(1910 rmrk_collection_id: RmrkCollectionId,1911 collection_type: misc::CollectionType,1912 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1913 let unique_collection_id = match collection_type {1914 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1915 _ => rmrk_collection_id.into(),1916 };19171918 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19191920 Ok((collection, unique_collection_id))1921 }19221923 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1924 pub fn get_nft_property(1925 collection_id: CollectionId,1926 nft_id: TokenId,1927 key: RmrkProperty,1928 ) -> Result<PropertyValue, DispatchError> {1929 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1930 .get(&Self::get_scoped_property_key(key)?)1931 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1932 .clone();19331934 Ok(nft_property)1935 }19361937 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1938 pub fn get_nft_property_decoded<V: Decode>(1939 collection_id: CollectionId,1940 nft_id: TokenId,1941 key: RmrkProperty,1942 ) -> Result<V, DispatchError> {1943 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1944 }19451946 /// Check that an NFT exists.1947 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1948 <TokenData<T>>::contains_key((collection_id, nft_id))1949 }19501951 /// Get the type of an NFT stored as a scoped property.1952 ///1953 /// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1954 pub fn get_nft_type(1955 collection_id: CollectionId,1956 token_id: TokenId,1957 ) -> Result<NftType, DispatchError> {1958 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1959 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1960 }19611962 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1963 pub fn ensure_nft_type(1964 collection_id: CollectionId,1965 token_id: TokenId,1966 nft_type: NftType,1967 ) -> DispatchResult {1968 let actual_type = Self::get_nft_type(collection_id, token_id)?;1969 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19701971 Ok(())1972 }19731974 /// Ensure that an account is the owner of the token, either directly1975 /// or at the top of the nesting hierarchy; return an error if it is not.1976 pub fn ensure_nft_owner(1977 collection_id: CollectionId,1978 token_id: TokenId,1979 possible_owner: &T::CrossAccountId,1980 nesting_budget: &dyn budget::Budget,1981 ) -> DispatchResult {1982 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1983 possible_owner.clone(),1984 collection_id,1985 token_id,1986 None,1987 nesting_budget,1988 )1989 .map_err(Self::map_unique_err_to_proxy)?;19901991 ensure!(is_owned, <Error<T>>::NoPermission);19921993 Ok(())1994 }19951996 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,1997 /// or, if None are provided, return all non-scoped properties.1998 pub fn filter_user_properties<Key, Value, R, Mapper>(1999 collection_id: CollectionId,2000 token_id: Option<TokenId>,2001 filter_keys: Option<Vec<RmrkPropertyKey>>,2002 mapper: Mapper,2003 ) -> Result<Vec<R>, DispatchError>2004 where2005 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2006 Value: Decode + Default,2007 Mapper: Fn(Key, Value) -> R,2008 {2009 filter_keys2010 .map(|keys| {2011 let properties = keys2012 .into_iter()2013 .filter_map(|key| {2014 let key: Key = key.try_into().ok()?;20152016 let value = match token_id {2017 Some(token_id) => Self::get_nft_property_decoded(2018 collection_id,2019 token_id,2020 UserProperty(key.as_ref()),2021 ),2022 None => Self::get_collection_property_decoded(2023 collection_id,2024 UserProperty(key.as_ref()),2025 ),2026 }2027 .ok()?;20282029 Some(mapper(key, value))2030 })2031 .collect();20322033 Ok(properties)2034 })2035 .unwrap_or_else(|| {2036 let properties =2037 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20382039 Ok(properties)2040 })2041 }20422043 /// Get all non-scoped properties from a collection or a token, and apply some transformation,2044 /// supplied by `mapper`, to each key-value pair.2045 pub fn iterate_user_properties<Key, Value, R, Mapper>(2046 collection_id: CollectionId,2047 token_id: Option<TokenId>,2048 mapper: Mapper,2049 ) -> Result<impl Iterator<Item = R>, DispatchError>2050 where2051 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2052 Value: Decode + Default,2053 Mapper: Fn(Key, Value) -> R,2054 {2055 let properties = match token_id {2056 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2057 None => <PalletCommon<T>>::collection_properties(collection_id),2058 };20592060 let properties = properties.into_iter().filter_map(move |(key, value)| {2061 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20622063 let key: Key = key.to_vec().try_into().ok()?;2064 let value: Value = value.decode().ok()?;20652066 Some(mapper(key, value))2067 });20682069 Ok(properties)2070 }20712072 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2073 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2074 map_unique_err_to_proxy! {2075 match err {2076 CommonError::NoPermission => NoPermission,2077 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2078 CommonError::PublicMintingNotAllowed => NoPermission,2079 CommonError::TokenNotFound => NoAvailableNftId,2080 CommonError::ApprovedValueTooLow => NoPermission,2081 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2082 StructureError::TokenNotFound => NoAvailableNftId,2083 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2084 }2085 }2086 }2087}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK specification repository: <https://github.com/rmrk-team/rmrk-spec>52//!53//! ## Terminology54//!55//! For more information on RMRK, see RMRK's own documentation.56//!57//! ### Intro to RMRK58//!59//! - **Resource:** Additional piece of metadata of an NFT usually serving to add60//! a piece of media on top of the root metadata (NFT's own), be it a different wing61//! on the root template bird or something entirely unrelated.62//!63//! - **Base:** A list of possible "components" - Parts, a combination of which can64//! be appended/equipped to/on an NFT.65//!66//! - **Part:** Something that, together with other Parts, can constitute an NFT.67//! Parts are defined in the Base to which they belong. Parts can be either68//! of the `slot` type or `fixed` type. Slots are intended for equippables.69//! Note that "part of something" and "Part of a Base" can be easily confused,70//! and so in this documentation these words are distinguished by the capital letter.71//!72//! - **Theme:** Named objects of variable => value pairs which get interpolated into73//! the Base's `themable` Parts. Themes can hold any value, but are often represented74//! in RMRK's examples as colors applied to visible Parts.75//!76//! ### Peculiarities in Unique77//!78//! - **Scoped properties:** Properties that are normally obscured from users.79//! Their purpose is to contain structured metadata that was not included in the Unique standard80//! for collections and tokens, meant to be operated on by proxies and other outliers.81//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is82//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined83//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.84//!85//! - **Auxiliary properties:** A slightly different structure of properties,86//! trading universality of use for more convenient storage, writes and access.87//! Meant to be inaccessible to end users.88//!89//! ## Proxy Implementation90//!91//! An external user is supposed to be able to utilize this proxy as they would92//! utilize RMRK, and get exactly the same results. Normally, Unique transactions93//! are off-limits to RMRK collections and tokens, and vice versa. However,94//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.95//!96//! ### ID Mapping97//!98//! RMRK's collections' IDs are counted independently of Unique's and start at 0.99//! Note that tokens' IDs still start at 1.100//! The collections themselves, as well as tokens, are stored as Unique collections,101//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).102//!103//! ### External/Internal Collection Insulation104//!105//! A Unique transaction cannot target collections purposed for RMRK,106//! and they are flagged as `external` to specify that. On the other hand,107//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.108//!109//! ### Native Properties110//!111//! Many of RMRK's native parameters are stored as scoped properties of a collection112//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`113//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,114//! makes them impossible to tamper with.115//!116//! ### Collection and NFT Types, or Base, Parts and Themes Handling117//!118//! RMRK introduces the concept of a Base, which is a catalogue of Parts,119//! possible components of an NFT. Due to its similarity with the functionality120//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes121//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].122//!123//! ## Interface124//!125//! ### Dispatchables126//!127//! - `create_collection` - Create a new collection of NFTs.128//! - `destroy_collection` - Destroy a collection.129//! - `change_collection_issuer` - Change the issuer of a collection.130//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).131//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**132//! - `mint_nft` - Mint an NFT in a specified collection.133//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.134//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.135//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.136//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.137//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.138//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.139//! - `set_property` - Add or edit a custom user property of a token or a collection.140//! - `set_priority` - Set a different order of resource priorities for an NFT.141//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.142//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.143//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.144//! - `remove_resource` - Remove and erase a resource from an NFT.145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, BoundedVec, dispatch::DispatchResult};149use frame_system::{pallet_prelude::*, ensure_signed};150use sp_runtime::{DispatchError, Permill, traits::StaticLookup};151use sp_std::{152 vec::Vec,153 collections::{btree_set::BTreeSet, btree_map::BTreeMap},154};155use up_data_structs::{*, mapping::TokenAddressMapping};156use pallet_common::{157 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,158};159use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};160use pallet_structure::{Pallet as PalletStructure, Error as StructureError};161use pallet_evm::account::CrossAccountId;162use core::convert::AsRef;163164pub use pallet::*;165166#[cfg(feature = "runtime-benchmarks")]167pub mod benchmarking;168pub mod misc;169pub mod property;170pub mod rpc;171pub mod weights;172173pub type SelfWeightOf<T> = <T as Config>::WeightInfo;174175use weights::WeightInfo;176use misc::*;177pub use property::*;178179use RmrkProperty::*;180181/// Maximum number of levels of depth in the token nesting tree.182pub const NESTING_BUDGET: u32 = 5;183184type PendingTarget = (CollectionId, TokenId);185type PendingChild = (RmrkCollectionId, RmrkNftId);186type PendingChildrenSet = BTreeSet<PendingChild>;187188type BasesMap = BTreeMap<RmrkBaseId, u32>;189190#[frame_support::pallet]191pub mod pallet {192 use super::*;193 use pallet_evm::account;194195 #[pallet::config]196 pub trait Config:197 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config198 {199 /// Overarching event type.200 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;201202 /// The weight information of this pallet.203 type WeightInfo: WeightInfo;204 }205206 /// Latest yet-unused collection ID.207 #[pallet::storage]208 #[pallet::getter(fn collection_index)]209 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;210211 /// Mapping from RMRK collection ID to Unique's.212 #[pallet::storage]213 pub type UniqueCollectionId<T: Config> =214 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;215216 #[pallet::pallet]217 #[pallet::generate_store(pub(super) trait Store)]218 pub struct Pallet<T>(_);219220 #[pallet::event]221 #[pallet::generate_deposit(pub(super) fn deposit_event)]222 pub enum Event<T: Config> {223 CollectionCreated {224 issuer: T::AccountId,225 collection_id: RmrkCollectionId,226 },227 CollectionDestroyed {228 issuer: T::AccountId,229 collection_id: RmrkCollectionId,230 },231 IssuerChanged {232 old_issuer: T::AccountId,233 new_issuer: T::AccountId,234 collection_id: RmrkCollectionId,235 },236 CollectionLocked {237 issuer: T::AccountId,238 collection_id: RmrkCollectionId,239 },240 NftMinted {241 owner: T::AccountId,242 collection_id: RmrkCollectionId,243 nft_id: RmrkNftId,244 },245 NFTBurned {246 owner: T::AccountId,247 nft_id: RmrkNftId,248 },249 NFTSent {250 sender: T::AccountId,251 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,252 collection_id: RmrkCollectionId,253 nft_id: RmrkNftId,254 approval_required: bool,255 },256 NFTAccepted {257 sender: T::AccountId,258 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,259 collection_id: RmrkCollectionId,260 nft_id: RmrkNftId,261 },262 NFTRejected {263 sender: T::AccountId,264 collection_id: RmrkCollectionId,265 nft_id: RmrkNftId,266 },267 PropertySet {268 collection_id: RmrkCollectionId,269 maybe_nft_id: Option<RmrkNftId>,270 key: RmrkKeyString,271 value: RmrkValueString,272 },273 ResourceAdded {274 nft_id: RmrkNftId,275 resource_id: RmrkResourceId,276 },277 ResourceRemoval {278 nft_id: RmrkNftId,279 resource_id: RmrkResourceId,280 },281 ResourceAccepted {282 nft_id: RmrkNftId,283 resource_id: RmrkResourceId,284 },285 ResourceRemovalAccepted {286 nft_id: RmrkNftId,287 resource_id: RmrkResourceId,288 },289 PrioritySet {290 collection_id: RmrkCollectionId,291 nft_id: RmrkNftId,292 },293 }294295 #[pallet::error]296 pub enum Error<T> {297 /* Unique proxy-specific events */298 /// Property of the type of RMRK collection could not be read successfully.299 CorruptedCollectionType,300 // NftTypeEncodeError,301 /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).302 RmrkPropertyKeyIsTooLong,303 /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).304 RmrkPropertyValueIsTooLong,305 /// Could not find a property by the supplied key.306 RmrkPropertyIsNotFound,307 /// Something went wrong when decoding encoded data from the storage.308 /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.309 UnableToDecodeRmrkData,310311 /* RMRK compatible events */312 /// Only destroying collections without tokens is allowed.313 CollectionNotEmpty,314 /// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.315 NoAvailableCollectionId,316 /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.317 NoAvailableNftId,318 /// Collection does not exist, has a wrong type, or does not map to a Unique ID.319 CollectionUnknown,320 /// No permission to perform action.321 NoPermission,322 /// Token is marked as non-transferable, and thus cannot be transferred.323 NonTransferable,324 /// Too many tokens created in the collection, no new ones are allowed.325 CollectionFullOrLocked,326 /// No such resource found.327 ResourceDoesntExist,328 /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.329 /// Sending to self is redundant.330 CannotSendToDescendentOrSelf,331 /// Not the target owner of the sent NFT.332 CannotAcceptNonOwnedNft,333 /// Not the target owner of the sent NFT.334 CannotRejectNonOwnedNft,335 /// NFT was not sent and is not pending.336 CannotRejectNonPendingNft,337 /// Resource is not pending for the operation.338 ResourceNotPending,339 /// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.340 NoAvailableResourceId,341 }342343 #[pallet::call]344 impl<T: Config> Pallet<T> {345 // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?346347 /// Create a new collection of NFTs.348 ///349 /// # Permissions:350 /// * Anyone - will be assigned as the issuer of the collection.351 ///352 /// # Arguments:353 /// - `origin`: sender of the transaction354 /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.355 /// - `max`: Optional maximum number of tokens.356 /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.357 /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.358 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]359 pub fn create_collection(360 origin: OriginFor<T>,361 metadata: RmrkString,362 max: Option<u32>,363 symbol: RmrkCollectionSymbol,364 ) -> DispatchResult {365 let sender = ensure_signed(origin)?;366367 let limits = CollectionLimits {368 owner_can_transfer: Some(false),369 token_limit: max,370 ..Default::default()371 };372373 let data = CreateCollectionData {374 limits: Some(limits),375 token_prefix: symbol376 .into_inner()377 .try_into()378 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,379 permissions: Some(CollectionPermissions {380 nesting: Some(NestingPermissions {381 token_owner: true,382 collection_admin: false,383 restricted: None,384 #[cfg(feature = "runtime-benchmarks")]385 permissive: false,386 }),387 ..Default::default()388 }),389 ..Default::default()390 };391392 let unique_collection_id = Self::init_collection(393 T::CrossAccountId::from_sub(sender.clone()),394 data,395 [396 Self::encode_rmrk_property(Metadata, &metadata)?,397 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,398 ]399 .into_iter(),400 )?;401 let rmrk_collection_id = <CollectionIndex<T>>::get();402403 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);404405 <PalletCommon<T>>::set_scoped_collection_property(406 unique_collection_id,407 RMRK_SCOPE,408 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,409 )?;410411 <CollectionIndex<T>>::mutate(|n| *n += 1);412413 Self::deposit_event(Event::CollectionCreated {414 issuer: sender,415 collection_id: rmrk_collection_id,416 });417418 Ok(())419 }420421 /// Destroy a collection.422 ///423 /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.424 ///425 /// # Permissions:426 /// * Collection issuer427 ///428 /// # Arguments:429 /// - `origin`: sender of the transaction430 /// - `collection_id`: RMRK ID of the collection to destroy.431 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]432 pub fn destroy_collection(433 origin: OriginFor<T>,434 collection_id: RmrkCollectionId,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437 let cross_sender = T::CrossAccountId::from_sub(sender.clone());438439 let collection = Self::get_typed_nft_collection(440 Self::unique_collection_id(collection_id)?,441 misc::CollectionType::Regular,442 )?;443 collection.check_is_external()?;444445 <PalletNft<T>>::destroy_collection(collection, &cross_sender)446 .map_err(Self::map_unique_err_to_proxy)?;447448 Self::deposit_event(Event::CollectionDestroyed {449 issuer: sender,450 collection_id,451 });452453 Ok(())454 }455456 /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).457 ///458 /// # Permissions:459 /// * Collection issuer460 ///461 /// # Arguments:462 /// - `origin`: sender of the transaction463 /// - `collection_id`: RMRK collection ID to change the issuer of.464 /// - `new_issuer`: Collection's new issuer.465 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]466 pub fn change_collection_issuer(467 origin: OriginFor<T>,468 collection_id: RmrkCollectionId,469 new_issuer: <T::Lookup as StaticLookup>::Source,470 ) -> DispatchResult {471 let sender = ensure_signed(origin)?;472473 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;474 collection.check_is_external()?;475476 let new_issuer = T::Lookup::lookup(new_issuer)?;477478 Self::change_collection_owner(479 Self::unique_collection_id(collection_id)?,480 misc::CollectionType::Regular,481 sender.clone(),482 new_issuer.clone(),483 )?;484485 Self::deposit_event(Event::IssuerChanged {486 old_issuer: sender,487 new_issuer,488 collection_id,489 });490491 Ok(())492 }493494 /// "Lock" the collection and prevent new token creation. Cannot be undone.495 ///496 /// # Permissions:497 /// * Collection issuer498 ///499 /// # Arguments:500 /// - `origin`: sender of the transaction501 /// - `collection_id`: RMRK ID of the collection to lock.502 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]503 pub fn lock_collection(504 origin: OriginFor<T>,505 collection_id: RmrkCollectionId,506 ) -> DispatchResult {507 let sender = ensure_signed(origin)?;508 let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510 let collection = Self::get_typed_nft_collection(511 Self::unique_collection_id(collection_id)?,512 misc::CollectionType::Regular,513 )?;514 collection.check_is_external()?;515516 Self::check_collection_owner(&collection, &cross_sender)?;517518 let token_count = collection.total_supply();519520 let mut collection = collection.into_inner();521 collection.limits.token_limit = Some(token_count);522 collection.save()?;523524 Self::deposit_event(Event::CollectionLocked {525 issuer: sender,526 collection_id,527 });528529 Ok(())530 }531532 /// Mint an NFT in a specified collection.533 ///534 /// # Permissions:535 /// * Collection issuer536 ///537 /// # Arguments:538 /// - `origin`: sender of the transaction539 /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).540 /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.541 /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.542 /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.543 /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.544 /// - `transferable`: Can this NFT be transferred? Cannot be changed.545 /// - `resources`: Resource data to be added to the NFT immediately after minting.546 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]547 pub fn mint_nft(548 origin: OriginFor<T>,549 owner: Option<T::AccountId>,550 collection_id: RmrkCollectionId,551 recipient: Option<T::AccountId>,552 royalty_amount: Option<Permill>,553 metadata: RmrkString,554 transferable: bool,555 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,556 ) -> DispatchResult {557 let sender = ensure_signed(origin)?;558 let cross_sender = T::CrossAccountId::from_sub(sender.clone());559560 let owner = owner.unwrap_or(sender.clone());561 let cross_owner = T::CrossAccountId::from_sub(owner.clone());562563 let collection = Self::get_typed_nft_collection(564 Self::unique_collection_id(collection_id)?,565 misc::CollectionType::Regular,566 )?;567 collection.check_is_external()?;568569 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {570 recipient: recipient.unwrap_or_else(|| owner.clone()),571 amount,572 });573574 let nft_id = Self::create_nft(575 &cross_sender,576 &cross_owner,577 &collection,578 [579 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,580 Self::encode_rmrk_property(Transferable, &transferable)?,581 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,582 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,583 Self::encode_rmrk_property(Metadata, &metadata)?,584 Self::encode_rmrk_property(Equipped, &false)?,585 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,586 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,587 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,588 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,589 ]590 .into_iter(),591 )592 .map_err(|err| match err {593 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),594 err => Self::map_unique_err_to_proxy(err),595 })?;596597 if let Some(resources) = resources {598 for resource in resources {599 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;600 }601 }602603 Self::deposit_event(Event::NftMinted {604 owner,605 collection_id,606 nft_id: nft_id.0,607 });608609 Ok(())610 }611612 /// Burn an NFT, destroying it and its nested tokens up to the specified limit.613 /// If the burning budget is exceeded, the transaction is reverted.614 ///615 /// This is the way to burn a nested token as well.616 ///617 /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).618 ///619 /// # Permissions:620 /// * Token owner621 ///622 /// # Arguments:623 /// - `origin`: sender of the transaction624 /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.625 /// - `nft_id`: ID of the NFT to be destroyed.626 /// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction627 /// is reverted if there are more tokens to burn in the nesting tree than this number.628 /// This is primarily a mechanism of transaction weight control.629 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]630 pub fn burn_nft(631 origin: OriginFor<T>,632 collection_id: RmrkCollectionId,633 nft_id: RmrkNftId,634 max_burns: u32,635 ) -> DispatchResult {636 let sender = ensure_signed(origin)?;637 let cross_sender = T::CrossAccountId::from_sub(sender.clone());638639 let collection = Self::get_typed_nft_collection(640 Self::unique_collection_id(collection_id)?,641 misc::CollectionType::Regular,642 )?;643 collection.check_is_external()?;644645 Self::destroy_nft(646 cross_sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 max_burns,650 <Error<T>>::NoPermission,651 )652 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;653654 Self::deposit_event(Event::NFTBurned {655 owner: sender,656 nft_id,657 });658659 Ok(())660 }661662 /// Transfer an NFT from an account/NFT A to another account/NFT B.663 /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].664 ///665 /// If the target owner is an NFT owned by another account, then the NFT will enter666 /// the pending state and will have to be accepted by the other account.667 ///668 /// # Permissions:669 /// - Token owner670 ///671 /// # Arguments:672 /// - `origin`: sender of the transaction673 /// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.674 /// - `rmrk_nft_id`: ID of the NFT to be transferred.675 /// - `new_owner`: New owner of the nft which can be either an account or a NFT.676 #[pallet::weight(<SelfWeightOf<T>>::send())]677 pub fn send(678 origin: OriginFor<T>,679 rmrk_collection_id: RmrkCollectionId,680 rmrk_nft_id: RmrkNftId,681 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,682 ) -> DispatchResult {683 let sender = ensure_signed(origin.clone())?;684 let cross_sender = T::CrossAccountId::from_sub(sender.clone());685686 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;687 let nft_id = rmrk_nft_id.into();688689 let collection =690 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691 collection.check_is_external()?;692693 let token_data =694 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;695696 let from = token_data.owner;697698 ensure!(699 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,700 <Error<T>>::NonTransferable701 );702703 ensure!(704 Self::get_nft_property_decoded::<Option<PendingTarget>>(705 collection_id,706 nft_id,707 RmrkProperty::PendingNftAccept708 )?709 .is_none(),710 <Error<T>>::NoPermission711 );712713 let target_owner;714 let approval_required;715716 match new_owner {717 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {718 target_owner = T::CrossAccountId::from_sub(account_id.clone());719 approval_required = false;720 }721 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(722 target_collection_id,723 target_nft_id,724 ) => {725 let target_collection_id = Self::unique_collection_id(target_collection_id)?;726727 let target_nft_budget = budget::Value::new(NESTING_BUDGET);728729 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(730 target_collection_id,731 target_nft_id.into(),732 Some((collection_id, nft_id)),733 &target_nft_budget,734 )735 .map_err(Self::map_unique_err_to_proxy)?;736737 approval_required = cross_sender != target_nft_owner;738739 if approval_required {740 target_owner = target_nft_owner;741742 <PalletNft<T>>::set_scoped_token_property(743 collection.id,744 nft_id,745 RMRK_SCOPE,746 Self::encode_rmrk_property::<Option<PendingTarget>>(747 PendingNftAccept,748 &Some((target_collection_id, target_nft_id.into())),749 )?,750 )?;751752 Self::insert_pending_child(753 (target_collection_id, target_nft_id.into()),754 (rmrk_collection_id, rmrk_nft_id),755 )?;756 } else {757 target_owner = T::CrossTokenAddressMapping::token_to_address(758 target_collection_id,759 target_nft_id.into(),760 );761 }762 }763 }764765 let src_nft_budget = budget::Value::new(NESTING_BUDGET);766767 <PalletNft<T>>::transfer_from(768 &collection,769 &cross_sender,770 &from,771 &target_owner,772 nft_id,773 &src_nft_budget,774 )775 .map_err(Self::map_unique_err_to_proxy)?;776777 Self::deposit_event(Event::NFTSent {778 sender,779 recipient: new_owner,780 collection_id: rmrk_collection_id,781 nft_id: rmrk_nft_id,782 approval_required,783 });784785 Ok(())786 }787788 /// Accept an NFT sent from another account to self or an owned NFT.789 ///790 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.791 ///792 /// # Permissions:793 /// - Token-owner-to-be794 ///795 /// # Arguments:796 /// - `origin`: sender of the transaction797 /// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.798 /// - `rmrk_nft_id`: ID of the NFT to be accepted.799 /// - `new_owner`: Either the sender's account ID or a sender-owned NFT,800 /// whichever the accepted NFT was sent to.801 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]802 pub fn accept_nft(803 origin: OriginFor<T>,804 rmrk_collection_id: RmrkCollectionId,805 rmrk_nft_id: RmrkNftId,806 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,807 ) -> DispatchResult {808 let sender = ensure_signed(origin.clone())?;809 let cross_sender = T::CrossAccountId::from_sub(sender.clone());810811 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812 let nft_id = rmrk_nft_id.into();813814 let collection =815 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816 collection.check_is_external()?;817818 let new_cross_owner = match new_owner {819 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {820 T::CrossAccountId::from_sub(account_id.clone())821 }822 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(823 target_collection_id,824 target_nft_id,825 ) => {826 let target_collection_id = Self::unique_collection_id(target_collection_id)?;827828 T::CrossTokenAddressMapping::token_to_address(829 target_collection_id,830 TokenId(target_nft_id),831 )832 }833 };834835 let budget = budget::Value::new(NESTING_BUDGET);836837 <PalletNft<T>>::transfer(838 &collection,839 &cross_sender,840 &new_cross_owner,841 nft_id,842 &budget,843 )844 .map_err(|err| {845 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {846 <Error<T>>::CannotAcceptNonOwnedNft.into()847 } else {848 Self::map_unique_err_to_proxy(err)849 }850 })?;851852 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(853 collection_id,854 nft_id,855 RmrkProperty::PendingNftAccept,856 )?;857858 if let Some(pending_target) = pending_target {859 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;860861 <PalletNft<T>>::set_scoped_token_property(862 collection.id,863 nft_id,864 RMRK_SCOPE,865 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,866 )?;867 }868869 Self::deposit_event(Event::NFTAccepted {870 sender,871 recipient: new_owner,872 collection_id: rmrk_collection_id,873 nft_id: rmrk_nft_id,874 });875876 Ok(())877 }878879 /// Reject an NFT sent from another account to self or owned NFT.880 /// The NFT in question will not be sent back and burnt instead.881 ///882 /// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.883 ///884 /// # Permissions:885 /// - Token-owner-to-be-not886 ///887 /// # Arguments:888 /// - `origin`: sender of the transaction889 /// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.890 /// - `rmrk_nft_id`: ID of the NFT to be rejected.891 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]892 pub fn reject_nft(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 rmrk_nft_id: RmrkNftId,896 ) -> DispatchResult {897 let sender = ensure_signed(origin)?;898 let cross_sender = T::CrossAccountId::from_sub(sender.clone());899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let nft_id = rmrk_nft_id.into();902903 let collection =904 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905 collection.check_is_external()?;906907 ensure!(908 <TokenData<T>>::get((collection_id, nft_id)).is_some(),909 <Error<T>>::NoAvailableNftId910 );911912 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(913 collection_id,914 nft_id,915 RmrkProperty::PendingNftAccept,916 )?;917918 match pending_target {919 Some(pending_target) => {920 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?921 }922 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),923 }924925 Self::destroy_nft(926 cross_sender,927 collection_id,928 nft_id,929 NESTING_BUDGET,930 <Error<T>>::CannotRejectNonOwnedNft,931 )932 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;933934 Self::deposit_event(Event::NFTRejected {935 sender,936 collection_id: rmrk_collection_id,937 nft_id: rmrk_nft_id,938 });939940 Ok(())941 }942943 /// Accept the addition of a newly created pending resource to an existing NFT.944 ///945 /// This transaction is needed when a resource is created and assigned to an NFT946 /// by a non-owner, i.e. the collection issuer, with one of the947 /// [`add_...` transactions](Pallet::add_basic_resource).948 ///949 /// # Permissions:950 /// - Token owner951 ///952 /// # Arguments:953 /// - `origin`: sender of the transaction954 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.955 /// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.956 /// - `resource_id`: ID of the newly created pending resource.957 /// accept the addition of a new resource to an existing NFT958 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]959 pub fn accept_resource(960 origin: OriginFor<T>,961 rmrk_collection_id: RmrkCollectionId,962 rmrk_nft_id: RmrkNftId,963 resource_id: RmrkResourceId,964 ) -> DispatchResult {965 let sender = ensure_signed(origin)?;966 let cross_sender = T::CrossAccountId::from_sub(sender);967968 let collection_id = Self::unique_collection_id(rmrk_collection_id)969 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;970 let collection =971 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;972 collection.check_is_external()?;973974 let nft_id = rmrk_nft_id.into();975976 let budget = budget::Value::new(NESTING_BUDGET);977978 let nft_owner =979 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)980 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;981982 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {983 ensure!(res.pending, <Error<T>>::ResourceNotPending);984 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);985986 res.pending = false;987988 Ok(())989 })?;990991 Self::deposit_event(Event::<T>::ResourceAccepted {992 nft_id: rmrk_nft_id,993 resource_id,994 });995996 Ok(())997 }998999 /// Accept the removal of a removal-pending resource from an NFT.1000 ///1001 /// This transaction is needed when a non-owner, i.e. the collection issuer,1002 /// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1003 ///1004 /// # Permissions:1005 /// - Token owner1006 ///1007 /// # Arguments:1008 /// - `origin`: sender of the transaction1009 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1010 /// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1011 /// - `resource_id`: ID of the removal-pending resource.1012 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1013 pub fn accept_resource_removal(1014 origin: OriginFor<T>,1015 rmrk_collection_id: RmrkCollectionId,1016 rmrk_nft_id: RmrkNftId,1017 resource_id: RmrkResourceId,1018 ) -> DispatchResult {1019 let sender = ensure_signed(origin)?;1020 let cross_sender = T::CrossAccountId::from_sub(sender);10211022 let collection_id = Self::unique_collection_id(rmrk_collection_id)1023 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1024 let collection =1025 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1026 collection.check_is_external()?;10271028 let nft_id = rmrk_nft_id.into();10291030 let budget = budget::Value::new(NESTING_BUDGET);10311032 let nft_owner =1033 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1034 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10351036 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10371038 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10391040 let resource_info = <PalletNft<T>>::token_aux_property((1041 collection_id,1042 nft_id,1043 RMRK_SCOPE,1044 resource_id_key.clone(),1045 ))1046 .ok_or(<Error<T>>::ResourceDoesntExist)?;10471048 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10491050 ensure!(1051 resource_info.pending_removal,1052 <Error<T>>::ResourceNotPending1053 );10541055 <PalletNft<T>>::remove_token_aux_property(1056 collection_id,1057 nft_id,1058 RMRK_SCOPE,1059 resource_id_key,1060 );10611062 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1063 let base_id = resource.base;10641065 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1066 }10671068 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1069 nft_id: rmrk_nft_id,1070 resource_id,1071 });10721073 Ok(())1074 }10751076 /// Add or edit a custom user property, a key-value pair, describing the metadata1077 /// of a token or a collection, on either one of these.1078 ///1079 /// Note that in this proxy implementation many details regarding RMRK are stored1080 /// as scoped properties prefixed with "rmrk:", normally inaccessible1081 /// to external transactions and RPCs.1082 ///1083 /// # Permissions:1084 /// - Collection issuer - in case of collection property1085 /// - Token owner - in case of NFT property1086 ///1087 /// # Arguments:1088 /// - `origin`: sender of the transaction1089 /// - `rmrk_collection_id`: RMRK collection ID.1090 /// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1091 /// - `key`: Key of the custom property to be referenced by.1092 /// - `value`: Value of the custom property to be stored.1093 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1094 pub fn set_property(1095 origin: OriginFor<T>,1096 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1097 maybe_nft_id: Option<RmrkNftId>,1098 key: RmrkKeyString,1099 value: RmrkValueString,1100 ) -> DispatchResult {1101 let sender = ensure_signed(origin)?;1102 let sender = T::CrossAccountId::from_sub(sender);11031104 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1105 let collection =1106 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1107 collection.check_is_external()?;11081109 let budget = budget::Value::new(NESTING_BUDGET);11101111 match maybe_nft_id {1112 Some(nft_id) => {1113 let token_id: TokenId = nft_id.into();11141115 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1116 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11171118 <PalletNft<T>>::set_scoped_token_property(1119 collection_id,1120 token_id,1121 RMRK_SCOPE,1122 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1123 )?;1124 }1125 None => {1126 let collection = Self::get_typed_nft_collection(1127 collection_id,1128 misc::CollectionType::Regular,1129 )?;11301131 Self::check_collection_owner(&collection, &sender)?;11321133 <PalletCommon<T>>::set_scoped_collection_property(1134 collection_id,1135 RMRK_SCOPE,1136 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137 )?;1138 }1139 }11401141 Self::deposit_event(Event::PropertySet {1142 collection_id: rmrk_collection_id,1143 maybe_nft_id,1144 key,1145 value,1146 });11471148 Ok(())1149 }11501151 /// Set a different order of resource priorities for an NFT. Priorities can be used,1152 /// for example, for order of rendering.1153 ///1154 /// Note that the priorities are not updated automatically, and are an empty vector1155 /// by default. There is no pre-set definition for the order to be particular,1156 /// it can be interpreted arbitrarily use-case by use-case.1157 ///1158 /// # Permissions:1159 /// - Token owner1160 ///1161 /// # Arguments:1162 /// - `origin`: sender of the transaction1163 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1164 /// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1165 /// - `priorities`: Ordered vector of resource IDs.1166 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1167 pub fn set_priority(1168 origin: OriginFor<T>,1169 rmrk_collection_id: RmrkCollectionId,1170 rmrk_nft_id: RmrkNftId,1171 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1172 ) -> DispatchResult {1173 let sender = ensure_signed(origin)?;1174 let sender = T::CrossAccountId::from_sub(sender);11751176 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1177 let nft_id = rmrk_nft_id.into();11781179 let collection =1180 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1181 collection.check_is_external()?;11821183 let budget = budget::Value::new(NESTING_BUDGET);11841185 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1186 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11871188 <PalletNft<T>>::set_scoped_token_property(1189 collection_id,1190 nft_id,1191 RMRK_SCOPE,1192 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1193 )?;11941195 Self::deposit_event(Event::<T>::PrioritySet {1196 collection_id: rmrk_collection_id,1197 nft_id: rmrk_nft_id,1198 });11991200 Ok(())1201 }12021203 /// Create and set/propose a basic resource for an NFT.1204 ///1205 /// A basic resource is the simplest, lacking a Base and anything that comes with it.1206 /// See RMRK docs for more information and examples.1207 ///1208 /// # Permissions:1209 /// - Collection issuer - if not the token owner, adding the resource will warrant1210 /// the owner's [acceptance](Pallet::accept_resource).1211 ///1212 /// # Arguments:1213 /// - `origin`: sender of the transaction1214 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1215 /// - `nft_id`: ID of the NFT to assign a resource to.1216 /// - `resource`: Data of the resource to be created.1217 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1218 pub fn add_basic_resource(1219 origin: OriginFor<T>,1220 rmrk_collection_id: RmrkCollectionId,1221 nft_id: RmrkNftId,1222 resource: RmrkBasicResource,1223 ) -> DispatchResult {1224 let sender = ensure_signed(origin.clone())?;12251226 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1227 let collection =1228 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1229 collection.check_is_external()?;12301231 let resource_id = Self::resource_add(1232 sender,1233 collection_id,1234 nft_id.into(),1235 RmrkResourceTypes::Basic(resource),1236 )?;12371238 Self::deposit_event(Event::ResourceAdded {1239 nft_id,1240 resource_id,1241 });1242 Ok(())1243 }12441245 /// Create and set/propose a composable resource for an NFT.1246 ///1247 /// A composable resource links to a Base and has a subset of its Parts it is composed of.1248 /// See RMRK docs for more information and examples.1249 ///1250 /// # Permissions:1251 /// - Collection issuer - if not the token owner, adding the resource will warrant1252 /// the owner's [acceptance](Pallet::accept_resource).1253 ///1254 /// # Arguments:1255 /// - `origin`: sender of the transaction1256 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1257 /// - `nft_id`: ID of the NFT to assign a resource to.1258 /// - `resource`: Data of the resource to be created.1259 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1260 pub fn add_composable_resource(1261 origin: OriginFor<T>,1262 rmrk_collection_id: RmrkCollectionId,1263 nft_id: RmrkNftId,1264 resource: RmrkComposableResource,1265 ) -> DispatchResult {1266 let sender = ensure_signed(origin.clone())?;12671268 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1269 let collection =1270 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1271 collection.check_is_external()?;12721273 let base_id = resource.base;12741275 let resource_id = Self::resource_add(1276 sender,1277 collection_id,1278 nft_id.into(),1279 RmrkResourceTypes::Composable(resource),1280 )?;12811282 <PalletNft<T>>::try_mutate_token_aux_property(1283 collection_id,1284 nft_id.into(),1285 RMRK_SCOPE,1286 Self::get_scoped_property_key(AssociatedBases)?,1287 |value| -> DispatchResult {1288 let mut bases: BasesMap = match value {1289 Some(value) => Self::decode_property_value(value)?,1290 None => BasesMap::new(),1291 };12921293 *bases.entry(base_id).or_insert(0) += 1;12941295 *value = Some(Self::encode_property_value(&bases)?);1296 Ok(())1297 },1298 )?;12991300 Self::deposit_event(Event::ResourceAdded {1301 nft_id,1302 resource_id,1303 });1304 Ok(())1305 }13061307 /// Create and set/propose a slot resource for an NFT.1308 ///1309 /// A slot resource links to a Base and a slot ID in it which it can fit into.1310 /// See RMRK docs for more information and examples.1311 ///1312 /// # Permissions:1313 /// - Collection issuer - if not the token owner, adding the resource will warrant1314 /// the owner's [acceptance](Pallet::accept_resource).1315 ///1316 /// # Arguments:1317 /// - `origin`: sender of the transaction1318 /// - `rmrk_collection_id`: RMRK collection ID of the NFT.1319 /// - `nft_id`: ID of the NFT to assign a resource to.1320 /// - `resource`: Data of the resource to be created.1321 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1322 pub fn add_slot_resource(1323 origin: OriginFor<T>,1324 rmrk_collection_id: RmrkCollectionId,1325 nft_id: RmrkNftId,1326 resource: RmrkSlotResource,1327 ) -> DispatchResult {1328 let sender = ensure_signed(origin.clone())?;13291330 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1331 let collection =1332 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1333 collection.check_is_external()?;13341335 let resource_id = Self::resource_add(1336 sender,1337 collection_id,1338 nft_id.into(),1339 RmrkResourceTypes::Slot(resource),1340 )?;13411342 Self::deposit_event(Event::ResourceAdded {1343 nft_id,1344 resource_id,1345 });1346 Ok(())1347 }13481349 /// Remove and erase a resource from an NFT.1350 ///1351 /// If the sender does not own the NFT, then it will be pending confirmation,1352 /// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1353 ///1354 /// # Permissions1355 /// - Collection issuer1356 ///1357 /// # Arguments1358 /// - `origin`: sender of the transaction1359 /// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1360 /// - `nft_id`: ID of the NFT with a resource to be removed.1361 /// - `resource_id`: ID of the resource to be removed.1362 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1363 pub fn remove_resource(1364 origin: OriginFor<T>,1365 rmrk_collection_id: RmrkCollectionId,1366 nft_id: RmrkNftId,1367 resource_id: RmrkResourceId,1368 ) -> DispatchResult {1369 let sender = ensure_signed(origin.clone())?;13701371 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1372 let collection =1373 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1374 collection.check_is_external()?;13751376 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13771378 Self::deposit_event(Event::ResourceRemoval {1379 nft_id,1380 resource_id,1381 });1382 Ok(())1383 }1384 }1385}13861387impl<T: Config> Pallet<T> {1388 /// Transform one of possible RMRK keys into a byte key with a RMRK scope.1389 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1390 let key = rmrk_key.to_key::<T>()?;13911392 let scoped_key = RMRK_SCOPE1393 .apply(key)1394 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13951396 Ok(scoped_key)1397 }13981399 /// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1400 /// and encoding the value from an arbitrary type into bytes.1401 pub fn encode_rmrk_property<E: Encode>(1402 rmrk_key: RmrkProperty,1403 value: &E,1404 ) -> Result<Property, DispatchError> {1405 let key = rmrk_key.to_key::<T>()?;14061407 let value = Self::encode_property_value(value)?;14081409 let property = Property { key, value };14101411 Ok(property)1412 }14131414 /// Encode property value from an arbitrary type into bytes for storage.1415 pub fn encode_property_value<E: Encode, S: Get<u32>>(1416 value: &E,1417 ) -> Result<BoundedBytes<S>, DispatchError> {1418 let value = value1419 .encode()1420 .try_into()1421 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14221423 Ok(value)1424 }14251426 /// Decode property value from bytes into an arbitrary type.1427 pub fn decode_property_value<D: Decode, S: Get<u32>>(1428 vec: &BoundedBytes<S>,1429 ) -> Result<D, DispatchError> {1430 vec.decode()1431 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1432 }14331434 /// Change the limit of a property value byte vector.1435 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1436 where1437 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1438 {1439 vec.rebind()1440 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1441 }14421443 /// Initialize a new NFT collection with certain RMRK-scoped properties.1444 ///1445 /// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1446 fn init_collection(1447 sender: T::CrossAccountId,1448 data: CreateCollectionData<T::AccountId>,1449 properties: impl Iterator<Item = Property>,1450 ) -> Result<CollectionId, DispatchError> {1451 let collection_id = <PalletNft<T>>::init_collection(1452 sender.clone(),1453 sender,1454 data,1455 up_data_structs::CollectionFlags {1456 external: true,1457 ..Default::default()1458 },1459 );14601461 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1462 return Err(<Error<T>>::NoAvailableCollectionId.into());1463 }14641465 <PalletCommon<T>>::set_scoped_collection_properties(1466 collection_id?,1467 RMRK_SCOPE,1468 properties,1469 )?;14701471 collection_id1472 }14731474 /// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1475 ///1476 /// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1477 pub fn create_nft(1478 sender: &T::CrossAccountId,1479 owner: &T::CrossAccountId,1480 collection: &NonfungibleHandle<T>,1481 properties: impl Iterator<Item = Property>,1482 ) -> Result<TokenId, DispatchError> {1483 let data = CreateNftExData {1484 properties: BoundedVec::default(),1485 owner: owner.clone(),1486 };14871488 let budget = budget::Value::new(NESTING_BUDGET);14891490 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14911492 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14931494 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14951496 Ok(nft_id)1497 }14981499 /// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1500 ///1501 /// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1502 fn destroy_nft(1503 sender: T::CrossAccountId,1504 collection_id: CollectionId,1505 token_id: TokenId,1506 max_burns: u32,1507 error_if_not_owned: Error<T>,1508 ) -> DispatchResultWithPostInfo {1509 let collection =1510 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15111512 let token_data =1513 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15141515 let from = token_data.owner;15161517 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15181519 ensure!(1520 <PalletStructure<T>>::check_indirectly_owned(1521 sender.clone(),1522 collection_id,1523 token_id,1524 None,1525 &owner_check_budget1526 )?,1527 error_if_not_owned,1528 );15291530 let burns_budget = budget::Value::new(max_burns);1531 let breadth_budget = budget::Value::new(max_burns);15321533 <PalletNft<T>>::burn_recursively(1534 &collection,1535 &from,1536 token_id,1537 &burns_budget,1538 &breadth_budget,1539 )1540 }15411542 /// Add a sent token pending acceptance to the target owning token as a property.1543 fn insert_pending_child(1544 target: (CollectionId, TokenId),1545 child: (RmrkCollectionId, RmrkNftId),1546 ) -> DispatchResult {1547 Self::mutate_pending_children(target, |pending_children| {1548 pending_children.insert(child);1549 })1550 }15511552 /// Remove a sent token pending acceptance from the target token's properties.1553 fn remove_pending_child(1554 target: (CollectionId, TokenId),1555 child: (RmrkCollectionId, RmrkNftId),1556 ) -> DispatchResult {1557 Self::mutate_pending_children(target, |pending_children| {1558 pending_children.remove(&child);1559 })1560 }15611562 /// Apply a mutation to the property of a token containing sent tokens1563 /// that are currently pending acceptance.1564 fn mutate_pending_children(1565 (target_collection_id, target_nft_id): (CollectionId, TokenId),1566 f: impl FnOnce(&mut PendingChildrenSet),1567 ) -> DispatchResult {1568 <PalletNft<T>>::try_mutate_token_aux_property(1569 target_collection_id,1570 target_nft_id,1571 RMRK_SCOPE,1572 Self::get_scoped_property_key(PendingChildren)?,1573 |pending_children| -> DispatchResult {1574 let mut map = match pending_children {1575 Some(map) => Self::decode_property_value(map)?,1576 None => PendingChildrenSet::new(),1577 };15781579 f(&mut map);15801581 *pending_children = Some(Self::encode_property_value(&map)?);15821583 Ok(())1584 },1585 )1586 }15871588 /// Get an iterator from a token's property containing tokens sent to it1589 /// that are currently pending acceptance.1590 fn iterate_pending_children(1591 collection_id: CollectionId,1592 nft_id: TokenId,1593 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1594 let property = <PalletNft<T>>::token_aux_property((1595 collection_id,1596 nft_id,1597 RMRK_SCOPE,1598 Self::get_scoped_property_key(PendingChildren)?,1599 ));16001601 let pending_children = match property {1602 Some(map) => Self::decode_property_value(&map)?,1603 None => PendingChildrenSet::new(),1604 };16051606 Ok(pending_children.into_iter())1607 }16081609 /// Get incremented resource ID from within an NFT's properties and store the new latest ID.1610 /// Thus, the returned resource ID should be used.1611 ///1612 /// Resource IDs are unique only across an NFT.1613 fn acquire_next_resource_id(1614 collection_id: CollectionId,1615 nft_id: TokenId,1616 ) -> Result<RmrkResourceId, DispatchError> {1617 let resource_id: RmrkResourceId =1618 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16191620 let next_id = resource_id1621 .checked_add(1)1622 .ok_or(<Error<T>>::NoAvailableResourceId)?;16231624 <PalletNft<T>>::set_scoped_token_property(1625 collection_id,1626 nft_id,1627 RMRK_SCOPE,1628 Self::encode_rmrk_property(NextResourceId, &next_id)?,1629 )?;16301631 Ok(resource_id)1632 }16331634 /// Create and add a resource for a regular NFT, mark it as pending if the sender1635 /// is not the token owner. The sender must be the collection owner.1636 fn resource_add(1637 sender: T::AccountId,1638 collection_id: CollectionId,1639 nft_id: TokenId,1640 resource: RmrkResourceTypes,1641 ) -> Result<RmrkResourceId, DispatchError> {1642 let collection =1643 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1644 ensure!(collection.owner == sender, Error::<T>::NoPermission);16451646 let sender = T::CrossAccountId::from_sub(sender);1647 let budget = budget::Value::new(NESTING_BUDGET);16481649 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1650 .map_err(Self::map_unique_err_to_proxy)?;16511652 let pending = sender != nft_owner;16531654 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16551656 let resource_info = RmrkResourceInfo {1657 id,1658 resource,1659 pending,1660 pending_removal: false,1661 };16621663 <PalletNft<T>>::try_mutate_token_aux_property(1664 collection_id,1665 nft_id,1666 RMRK_SCOPE,1667 Self::get_scoped_property_key(ResourceId(id))?,1668 |value| -> DispatchResult {1669 *value = Some(Self::encode_property_value(&resource_info)?);16701671 Ok(())1672 },1673 )?;16741675 Ok(id)1676 }16771678 /// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1679 /// The sender must be the collection owner.1680 fn resource_remove(1681 sender: T::AccountId,1682 collection_id: CollectionId,1683 nft_id: TokenId,1684 resource_id: RmrkResourceId,1685 ) -> DispatchResult {1686 let collection =1687 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1688 ensure!(collection.owner == sender, Error::<T>::NoPermission);16891690 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16911692 let resource = <PalletNft<T>>::token_aux_property((1693 collection_id,1694 nft_id,1695 RMRK_SCOPE,1696 resource_id_key.clone(),1697 ))1698 .ok_or(<Error<T>>::ResourceDoesntExist)?;16991700 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17011702 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1703 let topmost_owner =1704 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17051706 let sender = T::CrossAccountId::from_sub(sender);1707 if topmost_owner == sender {1708 <PalletNft<T>>::remove_token_aux_property(1709 collection_id,1710 nft_id,1711 RMRK_SCOPE,1712 Self::get_scoped_property_key(ResourceId(resource_id))?,1713 );17141715 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1716 let base_id = resource.base;17171718 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1719 }1720 } else {1721 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1722 res.pending_removal = true;17231724 Ok(())1725 })?;1726 }17271728 Ok(())1729 }17301731 /// Remove a Base ID from an NFT if they are associated.1732 /// The Base itself is deleted if the number of associated NFTs reaches 0.1733 fn remove_associated_base_id(1734 collection_id: CollectionId,1735 nft_id: TokenId,1736 base_id: RmrkBaseId,1737 ) -> DispatchResult {1738 <PalletNft<T>>::try_mutate_token_aux_property(1739 collection_id,1740 nft_id,1741 RMRK_SCOPE,1742 Self::get_scoped_property_key(AssociatedBases)?,1743 |value| -> DispatchResult {1744 let mut bases: BasesMap = match value {1745 Some(value) => Self::decode_property_value(value)?,1746 None => BasesMap::new(),1747 };17481749 let remaining = bases.get(&base_id);17501751 if let Some(remaining) = remaining {1752 if let Some(0) | None = remaining.checked_sub(1) {1753 bases.remove(&base_id);1754 }1755 }17561757 *value = Some(Self::encode_property_value(&bases)?);1758 Ok(())1759 },1760 )1761 }17621763 /// Apply a mutation to a resource stored in the token properties of an NFT.1764 fn try_mutate_resource_info(1765 collection_id: CollectionId,1766 nft_id: TokenId,1767 resource_id: RmrkResourceId,1768 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1769 ) -> DispatchResult {1770 <PalletNft<T>>::try_mutate_token_aux_property(1771 collection_id,1772 nft_id,1773 RMRK_SCOPE,1774 Self::get_scoped_property_key(ResourceId(resource_id))?,1775 |value| match value {1776 Some(value) => {1777 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17781779 f(&mut resource_info)?;17801781 *value = Self::encode_property_value(&resource_info)?;17821783 Ok(())1784 }1785 None => Err(<Error<T>>::ResourceDoesntExist.into()),1786 },1787 )1788 }17891790 /// Change the owner of an NFT collection, ensuring that the sender is the current owner.1791 fn change_collection_owner(1792 collection_id: CollectionId,1793 collection_type: misc::CollectionType,1794 sender: T::AccountId,1795 new_owner: T::AccountId,1796 ) -> DispatchResult {1797 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1798 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17991800 let mut collection = collection.into_inner();18011802 collection.owner = new_owner;1803 collection.save()1804 }18051806 /// Ensure that an account is the collection owner/issuer, return an error if not.1807 pub fn check_collection_owner(1808 collection: &NonfungibleHandle<T>,1809 account: &T::CrossAccountId,1810 ) -> DispatchResult {1811 collection1812 .check_is_owner(account)1813 .map_err(Self::map_unique_err_to_proxy)1814 }18151816 /// Get the latest yet-unused RMRK collection index from the storage.1817 pub fn last_collection_idx() -> RmrkCollectionId {1818 <CollectionIndex<T>>::get()1819 }18201821 /// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1822 pub fn unique_collection_id(1823 rmrk_collection_id: RmrkCollectionId,1824 ) -> Result<CollectionId, DispatchError> {1825 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1826 .map_err(|_| <Error<T>>::CollectionUnknown.into())1827 }18281829 /// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1830 pub fn rmrk_collection_id(1831 unique_collection_id: CollectionId,1832 ) -> Result<RmrkCollectionId, DispatchError> {1833 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1834 }18351836 /// Fetch a Unique NFT collection.1837 pub fn get_nft_collection(1838 collection_id: CollectionId,1839 ) -> Result<NonfungibleHandle<T>, DispatchError> {1840 let collection = <CollectionHandle<T>>::try_get(collection_id)1841 .map_err(|_| <Error<T>>::CollectionUnknown)?;18421843 match collection.mode {1844 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1845 _ => Err(<Error<T>>::CollectionUnknown.into()),1846 }1847 }18481849 /// Check if an NFT collection with such an ID exists.1850 pub fn collection_exists(collection_id: CollectionId) -> bool {1851 <CollectionHandle<T>>::try_get(collection_id).is_ok()1852 }18531854 /// Fetch and decode a RMRK-scoped collection property value in bytes.1855 pub fn get_collection_property(1856 collection_id: CollectionId,1857 key: RmrkProperty,1858 ) -> Result<PropertyValue, DispatchError> {1859 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1860 .get(&Self::get_scoped_property_key(key)?)1861 .ok_or(<Error<T>>::CollectionUnknown)?1862 .clone();18631864 Ok(collection_property)1865 }18661867 /// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1868 pub fn get_collection_property_decoded<V: Decode>(1869 collection_id: CollectionId,1870 key: RmrkProperty,1871 ) -> Result<V, DispatchError> {1872 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1873 }18741875 /// Get the type of a collection stored as a scoped property.1876 ///1877 /// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1878 pub fn get_collection_type(1879 collection_id: CollectionId,1880 ) -> Result<misc::CollectionType, DispatchError> {1881 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1882 if err != <Error<T>>::CollectionUnknown.into() {1883 <Error<T>>::CorruptedCollectionType.into()1884 } else {1885 err1886 }1887 })1888 }18891890 /// Ensure that the type of the collection equals the provided type,1891 /// otherwise return an error.1892 pub fn ensure_collection_type(1893 collection_id: CollectionId,1894 collection_type: misc::CollectionType,1895 ) -> DispatchResult {1896 let actual_type = Self::get_collection_type(collection_id)?;1897 ensure!(1898 actual_type == collection_type,1899 <CommonError<T>>::NoPermission1900 );19011902 Ok(())1903 }19041905 /// Fetch an NFT collection, but make sure it has the appropriate type.1906 pub fn get_typed_nft_collection(1907 collection_id: CollectionId,1908 collection_type: misc::CollectionType,1909 ) -> Result<NonfungibleHandle<T>, DispatchError> {1910 Self::ensure_collection_type(collection_id, collection_type)?;19111912 Self::get_nft_collection(collection_id)1913 }19141915 /// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1916 /// but also return the Unique collection ID.1917 pub fn get_typed_nft_collection_mapped(1918 rmrk_collection_id: RmrkCollectionId,1919 collection_type: misc::CollectionType,1920 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1921 let unique_collection_id = match collection_type {1922 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1923 _ => rmrk_collection_id.into(),1924 };19251926 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19271928 Ok((collection, unique_collection_id))1929 }19301931 /// Fetch and decode a RMRK-scoped NFT property value in bytes.1932 pub fn get_nft_property(1933 collection_id: CollectionId,1934 nft_id: TokenId,1935 key: RmrkProperty,1936 ) -> Result<PropertyValue, DispatchError> {1937 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1938 .get(&Self::get_scoped_property_key(key)?)1939 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1940 .clone();19411942 Ok(nft_property)1943 }19441945 /// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1946 pub fn get_nft_property_decoded<V: Decode>(1947 collection_id: CollectionId,1948 nft_id: TokenId,1949 key: RmrkProperty,1950 ) -> Result<V, DispatchError> {1951 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1952 }19531954 /// Check that an NFT exists.1955 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1956 <TokenData<T>>::contains_key((collection_id, nft_id))1957 }19581959 /// Get the type of an NFT stored as a scoped property.1960 ///1961 /// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1962 pub fn get_nft_type(1963 collection_id: CollectionId,1964 token_id: TokenId,1965 ) -> Result<NftType, DispatchError> {1966 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1967 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1968 }19691970 /// Ensure that the type of the NFT equals the provided type, otherwise return an error.1971 pub fn ensure_nft_type(1972 collection_id: CollectionId,1973 token_id: TokenId,1974 nft_type: NftType,1975 ) -> DispatchResult {1976 let actual_type = Self::get_nft_type(collection_id, token_id)?;1977 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19781979 Ok(())1980 }19811982 /// Ensure that an account is the owner of the token, either directly1983 /// or at the top of the nesting hierarchy; return an error if it is not.1984 pub fn ensure_nft_owner(1985 collection_id: CollectionId,1986 token_id: TokenId,1987 possible_owner: &T::CrossAccountId,1988 nesting_budget: &dyn budget::Budget,1989 ) -> DispatchResult {1990 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1991 possible_owner.clone(),1992 collection_id,1993 token_id,1994 None,1995 nesting_budget,1996 )1997 .map_err(Self::map_unique_err_to_proxy)?;19981999 ensure!(is_owned, <Error<T>>::NoPermission);20002001 Ok(())2002 }20032004 /// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2005 /// or, if None are provided, return all non-scoped properties.2006 pub fn filter_user_properties<Key, Value, R, Mapper>(2007 collection_id: CollectionId,2008 token_id: Option<TokenId>,2009 filter_keys: Option<Vec<RmrkPropertyKey>>,2010 mapper: Mapper,2011 ) -> Result<Vec<R>, DispatchError>2012 where2013 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2014 Value: Decode + Default,2015 Mapper: Fn(Key, Value) -> R,2016 {2017 filter_keys2018 .map(|keys| {2019 let properties = keys2020 .into_iter()2021 .filter_map(|key| {2022 let key: Key = key.try_into().ok()?;20232024 let value = match token_id {2025 Some(token_id) => Self::get_nft_property_decoded(2026 collection_id,2027 token_id,2028 UserProperty(key.as_ref()),2029 ),2030 None => Self::get_collection_property_decoded(2031 collection_id,2032 UserProperty(key.as_ref()),2033 ),2034 }2035 .ok()?;20362037 Some(mapper(key, value))2038 })2039 .collect();20402041 Ok(properties)2042 })2043 .unwrap_or_else(|| {2044 let properties =2045 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20462047 Ok(properties)2048 })2049 }20502051 /// Get all non-scoped properties from a collection or a token, and apply some transformation,2052 /// supplied by `mapper`, to each key-value pair.2053 pub fn iterate_user_properties<Key, Value, R, Mapper>(2054 collection_id: CollectionId,2055 token_id: Option<TokenId>,2056 mapper: Mapper,2057 ) -> Result<impl Iterator<Item = R>, DispatchError>2058 where2059 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2060 Value: Decode + Default,2061 Mapper: Fn(Key, Value) -> R,2062 {2063 let properties = match token_id {2064 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2065 None => <PalletCommon<T>>::collection_properties(collection_id),2066 };20672068 let properties = properties.into_iter().filter_map(move |(key, value)| {2069 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20702071 let key: Key = key.to_vec().try_into().ok()?;2072 let value: Value = value.decode().ok()?;20732074 Some(mapper(key, value))2075 });20762077 Ok(properties)2078 }20792080 /// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2081 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2082 map_unique_err_to_proxy! {2083 match err {2084 CommonError::NoPermission => NoPermission,2085 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2086 CommonError::PublicMintingNotAllowed => NoPermission,2087 CommonError::TokenNotFound => NoAvailableNftId,2088 CommonError::ApprovedValueTooLow => NoPermission,2089 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2090 StructureError::TokenNotFound => NoAvailableNftId,2091 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2092 }2093 }2094 }2095}pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -254,7 +254,10 @@
cross_sender.clone(),
cross_sender.clone(),
data,
- true,
+ up_data_structs::CollectionFlags {
+ external: true,
+ ..Default::default()
+ },
);
if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -371,8 +371,9 @@
owner: T::CrossAccountId,
payer: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, payer, data, CollectionFlags::default())
+ <PalletCommon<T>>::init_collection(owner, payer, data, flags)
}
/// Destroy RFT collection
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -34,7 +34,7 @@
use sp_std::vec;
use up_data_structs::{
CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,
- CollectionMode, PropertyValue,
+ CollectionMode, PropertyValue, CollectionFlags,
};
use crate::{Config, SelfWeightOf, weights::WeightInfo};
@@ -186,9 +186,16 @@
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id =
- T::CollectionDispatch::create(caller.clone(), collection_helpers_address, data)
- .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller.clone(),
+ collection_helpers_address,
+ data,
+ CollectionFlags {
+ erc721metadata: add_properties,
+ ..Default::default()
+ },
+ )
+ .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
}
@@ -243,8 +250,13 @@
check_sent_amount_equals_collection_creation_price::<T>(value)?;
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
- .map_err(dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller,
+ collection_helpers_address,
+ data,
+ Default::default(),
+ )
+ .map_err(dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
@@ -291,8 +303,16 @@
check_sent_amount_equals_collection_creation_price::<T>(value)?;
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
- .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller,
+ collection_helpers_address,
+ data,
+ CollectionFlags {
+ erc721metadata: true,
+ ..Default::default()
+ },
+ )
+ .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -345,7 +345,7 @@
// =========
let sender = T::CrossAccountId::from_sub(sender);
- let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;
+ let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;
Ok(())
}
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -31,7 +31,7 @@
};
use up_data_structs::{
CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
- CollectionId,
+ CollectionId, CollectionFlags,
};
#[cfg(not(feature = "refungible"))]
@@ -57,10 +57,11 @@
sender: T::CrossAccountId,
payer: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError> {
let id = match data.mode {
CollectionMode::NFT => {
- <PalletNonfungible<T>>::init_collection(sender, payer, data, false)?
+ <PalletNonfungible<T>>::init_collection(sender, payer, data, flags)?
}
CollectionMode::Fungible(decimal_points) => {
// check params
@@ -68,11 +69,13 @@
decimal_points <= MAX_DECIMAL_POINTS,
pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
);
- <PalletFungible<T>>::init_collection(sender, payer, data)?
+ <PalletFungible<T>>::init_collection(sender, payer, data, flags)?
}
#[cfg(feature = "refungible")]
- CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, payer, data)?,
+ CollectionMode::ReFungible => {
+ <PalletRefungible<T>>::init_collection(sender, payer, data, flags)?
+ }
#[cfg(not(feature = "refungible"))]
CollectionMode::ReFungible => return unsupported!(T),