--- a/pallets/proxy-rmrk-core/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see .
+
use sp_std::vec;
use frame_benchmarking::{benchmarks, account};
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -14,6 +14,135 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see .
+//! # RMRK Core Proxy Pallet
+//!
+//! A pallet used as proxy for RMRK Core ().
+//!
+//! - [`Config`]
+//! - [`Call`]
+//! - [`Pallet`]
+//!
+//! ## Overview
+//!
+//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,
+//! binding its externalities to Unique's own underlying structure.
+//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations
+//! of solutions based on RMRK.
+//!
+//! RMRK Core itself contains essential functionality for RMRK's nested and
+//! multi-resourced NFTs.
+//!
+//! *Note*, that while RMRK itself is subject to active development and restructuring,
+//! the proxy may be caught temporarily out of date.
+//!
+//! ### What is RMRK?
+//!
+//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.
+//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.
+//!
+//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,
+//! make use of specific changeable and partially shared metadata in the form of resources,
+//! and more.
+//!
+//! Visit RMRK documentation and repositories to learn more:
+//! - Docs:
+//! - FAQ:
+//! - Substrate code repository:
+//! - RMRK specification repository:
+//!
+//! ## Terminology
+//!
+//! For more information on RMRK, see RMRK's own documentation.
+//!
+//! ### Intro to RMRK
+//!
+//! - **Resource:** Additional piece of metadata of an NFT usually serving to add
+//! a piece of media on top of the root metadata (NFT's own), be it a different wing
+//! on the root template bird or something entirely unrelated.
+//!
+//! - **Base:** A list of possible "components" - Parts, a combination of which can
+//! be appended/equipped to/on an NFT.
+//!
+//! - **Part:** Something that, together with other Parts, can constitute an NFT.
+//! Parts are defined in the Base to which they belong. Parts can be either
+//! of the `slot` type or `fixed` type. Slots are intended for equippables.
+//! Note that "part of something" and "Part of a Base" can be easily confused,
+//! and so in this documentation these words are distinguished by the capital letter.
+//!
+//! - **Theme:** Named objects of variable => value pairs which get interpolated into
+//! the Base's `themable` Parts. Themes can hold any value, but are often represented
+//! in RMRK's examples as colors applied to visible Parts.
+//!
+//! ### Peculiarities in Unique
+//!
+//! - **Scoped properties:** Properties that are normally obscured from users.
+//! Their purpose is to contain structured metadata that was not included in the Unique standard
+//! for collections and tokens, meant to be operated on by proxies and other outliers.
+//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is
+//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined
+//! properties, which, along with other safeguards, makes scoped ones impossible to tamper with.
+//!
+//! - **Auxiliary properties:** A slightly different structure of properties,
+//! trading universality of use for more convenient storage, writes and access.
+//! Meant to be inaccessible to end users.
+//!
+//! ## Proxy Implementation
+//!
+//! An external user is supposed to be able to utilize this proxy as they would
+//! utilize RMRK, and get exactly the same results. Normally, Unique transactions
+//! are off-limits to RMRK collections and tokens, and vice versa. However,
+//! the information stored on chain can be freely interpreted by storage reads and Unique RPCs.
+//!
+//! ### ID Mapping
+//!
+//! RMRK's collections' IDs are counted independently of Unique's and start at 0.
+//! Note that tokens' IDs still start at 1.
+//! The collections themselves, as well as tokens, are stored as Unique collections,
+//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).
+//!
+//! ### External/Internal Collection Insulation
+//!
+//! A Unique transaction cannot target collections purposed for RMRK,
+//! and they are flagged as `external` to specify that. On the other hand,
+//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.
+//!
+//! ### Native Properties
+//!
+//! Many of RMRK's native parameters are stored as scoped properties of a collection
+//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`
+//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,
+//! makes them impossible to tamper with.
+//!
+//! ### Collection and NFT Types, or Base, Parts and Themes Handling
+//!
+//! RMRK introduces the concept of a Base, which is a catalogue of Parts,
+//! possible components of an NFT. Due to its similarity with the functionality
+//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes
+//! are this collection's NFTs. See [`CollectionType`] and [`NftType`].
+//!
+//! ## Interface
+//!
+//! ### Dispatchables
+//!
+//! - `create_collection` - Create a new collection of NFTs.
+//! - `destroy_collection` - Destroy a collection.
+//! - `change_collection_issuer` - Change the issuer of a collection.
+//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).
+//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**
+//! - `mint_nft` - Mint an NFT in a specified collection.
+//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.
+//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.
+//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.
+//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.
+//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.
+//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.
+//! - `set_property` - Add or edit a custom user property of a token or a collection.
+//! - `set_priority` - Set a different order of resource priorities for an NFT.
+//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.
+//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.
+//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.
+//! - `remove_resource` - Remove and erase a resource from an NFT.
+
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};
@@ -49,6 +178,7 @@
use RmrkProperty::*;
+/// Maximum number of levels of depth in the token nesting tree.
pub const NESTING_BUDGET: u32 = 5;
type PendingTarget = (CollectionId, TokenId);
@@ -66,14 +196,19 @@
pub trait Config:
frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config
{
+ /// Overarching event type.
type Event: From> + IsType<::Event>;
+
+ /// The weight information of this pallet.
type WeightInfo: WeightInfo;
}
+ /// Latest yet-unused collection ID.
#[pallet::storage]
#[pallet::getter(fn collection_index)]
pub type CollectionIndex = StorageValue<_, RmrkCollectionId, ValueQuery>;
+ /// Mapping from RMRK collection ID to Unique's.
#[pallet::storage]
pub type UniqueCollectionId =
StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;
@@ -159,34 +294,66 @@
#[pallet::error]
pub enum Error {
- /* Unique-specific events */
+ /* Unique proxy-specific events */
+ /// Property of the type of RMRK collection could not be read successfully.
CorruptedCollectionType,
- NftTypeEncodeError,
+ // NftTypeEncodeError,
+ /// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).
RmrkPropertyKeyIsTooLong,
+ /// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).
RmrkPropertyValueIsTooLong,
+ /// Could not find a property by the supplied key.
RmrkPropertyIsNotFound,
+ /// Something went wrong when decoding encoded data from the storage.
+ /// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.
UnableToDecodeRmrkData,
/* RMRK compatible events */
+ /// Only destroying collections without tokens is allowed.
CollectionNotEmpty,
+ /// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.
NoAvailableCollectionId,
+ /// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.
NoAvailableNftId,
+ /// Collection does not exist, has a wrong type, or does not map to a Unique ID.
CollectionUnknown,
+ /// No permission to perform action.
NoPermission,
+ /// Token is marked as non-transferable, and thus cannot be transferred.
NonTransferable,
+ /// Too many tokens created in the collection, no new ones are allowed.
CollectionFullOrLocked,
+ /// No such resource found.
ResourceDoesntExist,
+ /// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.
+ /// Sending to self is redundant.
CannotSendToDescendentOrSelf,
+ /// Not the target owner of the sent NFT.
CannotAcceptNonOwnedNft,
+ /// Not the target owner of the sent NFT.
CannotRejectNonOwnedNft,
+ /// NFT was not sent and is not pending.
CannotRejectNonPendingNft,
+ /// Resource is not pending for the operation.
ResourceNotPending,
+ /// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.
NoAvailableResourceId,
}
#[pallet::call]
impl Pallet {
- /// Create a collection
+ // todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?
+
+ /// Create a new collection of NFTs.
+ ///
+ /// # Permissions:
+ /// * Anyone - will be assigned as the issuer of the collection.
+ ///
+ /// # Arguments:
+ /// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.
+ /// - `max`: Optional maximum number of tokens.
+ /// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.
+ /// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.
#[transactional]
#[pallet::weight(>::create_collection())]
pub fn create_collection(
@@ -226,8 +393,8 @@
T::CrossAccountId::from_sub(sender.clone()),
data,
[
- Self::rmrk_property(Metadata, &metadata)?,
- Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,
+ Self::encode_rmrk_property(Metadata, &metadata)?,
+ Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,
]
.into_iter(),
)?;
@@ -237,8 +404,8 @@
>::set_scoped_collection_property(
unique_collection_id,
- PropertyScope::Rmrk,
- Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,
+ RMRK_SCOPE,
+ Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,
)?;
>::mutate(|n| *n += 1);
@@ -251,7 +418,15 @@
Ok(())
}
- /// destroy collection
+ /// Destroy a collection.
+ ///
+ /// Only empty collections can be destroyed. If it has any tokens, they must be burned first.
+ ///
+ /// # Permissions:
+ /// * Collection issuer
+ ///
+ /// # Arguments:
+ /// - `collection_id`: RMRK ID of the collection to destroy.
#[transactional]
#[pallet::weight(>::destroy_collection())]
pub fn destroy_collection(
@@ -278,12 +453,14 @@
Ok(())
}
- /// Change the issuer of a collection
+ /// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).
+ ///
+ /// # Permissions:
+ /// * Collection issuer
///
- /// Parameters:
- /// - `origin`: sender of the transaction
- /// - `collection_id`: collection id of the nft to change issuer of
- /// - `new_issuer`: Collection's new issuer
+ /// # Arguments:
+ /// - `collection_id`: RMRK collection ID to change the issuer of.
+ /// - `new_issuer`: Collection's new issuer.
#[transactional]
#[pallet::weight(>::change_collection_issuer())]
pub fn change_collection_issuer(
@@ -314,7 +491,13 @@
Ok(())
}
- /// lock collection
+ /// "Lock" the collection and prevent new token creation. Cannot be undone.
+ ///
+ /// # Permissions:
+ /// * Collection issuer
+ ///
+ /// # Arguments:
+ /// - `collection_id`: RMRK ID of the collection to lock.
#[transactional]
#[pallet::weight(>::lock_collection())]
pub fn lock_collection(
@@ -346,16 +529,19 @@
Ok(())
}
- /// Mints an NFT in the specified collection
- /// Sets metadata and the royalty attribute
+ /// Mint an NFT in a specified collection.
///
- /// Parameters:
- /// - `collection_id`: The class of the asset to be minted.
- /// - `nft_id`: The nft value of the asset to be minted.
- /// - `recipient`: Receiver of the royalty
- /// - `royalty`: Permillage reward from each trade for the Recipient
- /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash
- /// - `transferable`: Ability to transfer this NFT
+ /// # Permissions:
+ /// * Collection issuer
+ ///
+ /// # Arguments:
+ /// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).
+ /// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.
+ /// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.
+ /// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.
+ /// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.
+ /// - `transferable`: Can this NFT be transferred? Cannot be changed.
+ /// - `resources`: Resource data to be added to the NFT immediately after minting.
#[transactional]
#[pallet::weight(>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]
pub fn mint_nft(
@@ -390,16 +576,16 @@
&cross_owner,
&collection,
[
- Self::rmrk_property(TokenType, &NftType::Regular)?,
- Self::rmrk_property(Transferable, &transferable)?,
- Self::rmrk_property(PendingNftAccept, &None::)?,
- Self::rmrk_property(RoyaltyInfo, &royalty_info)?,
- Self::rmrk_property(Metadata, &metadata)?,
- Self::rmrk_property(Equipped, &false)?,
- Self::rmrk_property(ResourcePriorities, &>::new())?,
- Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,
- Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,
- Self::rmrk_property(AssociatedBases, &BasesMap::new())?,
+ Self::encode_rmrk_property(TokenType, &NftType::Regular)?,
+ Self::encode_rmrk_property(Transferable, &transferable)?,
+ Self::encode_rmrk_property(PendingNftAccept, &None::)?,
+ Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,
+ Self::encode_rmrk_property(Metadata, &metadata)?,
+ Self::encode_rmrk_property(Equipped, &false)?,
+ Self::encode_rmrk_property(ResourcePriorities, &>::new())?,
+ Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,
+ Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,
+ Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,
]
.into_iter(),
)
@@ -423,7 +609,22 @@
Ok(())
}
- /// burn nft
+ /// Burn an NFT, destroying it and its nested tokens up to the specified limit.
+ /// If the burning budget is exceeded, the transaction is reverted.
+ ///
+ /// This is the way to burn a nested token as well.
+ ///
+ /// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).
+ ///
+ /// # Permissions:
+ /// * Token owner
+ ///
+ /// # Arguments:
+ /// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.
+ /// - `nft_id`: ID of the NFT to be destroyed.
+ /// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction
+ /// is reverted if there are more tokens to burn in the nesting tree than this number.
+ /// This is primarily a mechanism of transaction weight control.
#[transactional]
#[pallet::weight(>::burn_nft(*max_burns))]
pub fn burn_nft(
@@ -458,13 +659,19 @@
Ok(())
}
- /// Transfers a NFT from an Account or NFT A to another Account or NFT B
+ /// Transfer an NFT from an account/NFT A to another account/NFT B.
+ /// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].
///
- /// Parameters:
- /// - `origin`: sender of the transaction
- /// - `rmrk_collection_id`: collection id of the nft to be transferred
- /// - `rmrk_nft_id`: nft id of the nft to be transferred
- /// - `new_owner`: new owner of the nft which can be either an account or a NFT
+ /// If the target owner is an NFT owned by another account, then the NFT will enter
+ /// the pending state and will have to be accepted by the other account.
+ ///
+ /// # Permissions:
+ /// - Token owner
+ ///
+ /// # Arguments:
+ /// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.
+ /// - `nft_id`: ID of the NFT to be transferred.
+ /// - `new_owner`: New owner of the nft which can be either an account or a NFT.
#[transactional]
#[pallet::weight(>::send())]
pub fn send(
@@ -535,8 +742,8 @@
>::set_scoped_token_property(
collection.id,
nft_id,
- PropertyScope::Rmrk,
- Self::rmrk_property::