From 9f5f42dac2e7c11fc6475bd1171f84592d61fd5d Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 21 Jul 2022 08:25:17 +0000 Subject: [PATCH] Merge pull request #440 from UniqueNetwork/doc/pallet_common docs(pallet_common): Add general documentation. --- --- a/pallets/common/src/dispatch.rs +++ b/pallets/common/src/dispatch.rs @@ -1,3 +1,5 @@ +//! Module with interfaces for dispatching collections. + use frame_support::{ dispatch::{ DispatchResultWithPostInfo, PostDispatchInfo, Weight, DispatchErrorWithPostInfo, @@ -20,7 +22,10 @@ // submit_logs is measured as part of collection pallets } -/// Helper function to implement substrate calls for common collection methods +/// Helper function to implement substrate calls for common collection methods. +/// +/// * `collection` - The collection on which to call the method. +/// * `call` - The function in which to call the corresponding method from [`CommonCollectionOperations`]. pub fn dispatch_tx< T: Config, C: FnOnce(&dyn CommonCollectionOperations) -> DispatchResultWithPostInfo, @@ -64,15 +69,31 @@ result } +/// Interface for working with different collections through the dispatcher. pub trait CollectionDispatch { + /// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode). + /// + /// * `sender` - The user who will become the owner of the collection. + /// * `data` - Description of the created collection. fn create( sender: T::CrossAccountId, data: CreateCollectionData, ) -> DispatchResult; + + /// Delete the collection. + /// + /// * `sender` - The owner of the collection. + /// * `handle` - Collection handle. fn destroy(sender: T::CrossAccountId, handle: CollectionHandle) -> DispatchResult; + /// Get a specialized collection from the handle. + /// + /// * `handle` - Collection handle. fn dispatch(handle: CollectionHandle) -> Self; + + /// Get the collection handle for the corresponding implementation. fn into_inner(self) -> CollectionHandle; + /// Get the implementation of [`CommonCollectionOperations`]. fn as_dyn(&self) -> &dyn CommonCollectionOperations; } --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +//! This module contains the implementation of pallet methods for evm. + use evm_coder::{ solidity_interface, solidity, ToLog, types::*, @@ -29,29 +31,40 @@ use crate::{Pallet, CollectionHandle, Config, CollectionProperties}; +/// Events for ethereum collection helper. #[derive(ToLog)] pub enum CollectionHelpersEvents { + /// The collection has been created. CollectionCreated { + /// Collection owner. #[indexed] owner: address, + + /// Collection ID. #[indexed] collection_id: address, }, } /// Does not always represent a full collection, for RFT it is either -/// collection (Implementing ERC721), or specific collection token (Implementing ERC20) +/// collection (Implementing ERC721), or specific collection token (Implementing ERC20). pub trait CommonEvmHandler { const CODE: &'static [u8]; + /// Call precompiled handle. fn call(self, handle: &mut impl PrecompileHandle) -> Option; } +/// @title A contract that allows you to work with collections. #[solidity_interface(name = "Collection")] impl CollectionHandle where T::AccountId: From<[u8; 32]>, { + /// Set collection property. + /// + /// @param key Property key. + /// @param value Propery value. fn set_collection_property( &mut self, caller: caller, @@ -68,6 +81,9 @@ .map_err(dispatch_to_evm::) } + /// Delete collection property. + /// + /// @param key Property key. fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> { let caller = T::CrossAccountId::from_eth(caller); let key = >::from(key) @@ -77,7 +93,12 @@ >::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::) } - /// Throws error if key not found + /// Get collection property. + /// + /// @dev Throws error if key not found. + /// + /// @param key Property key. + /// @return bytes The property corresponding to the key. fn collection_property(&self, key: string) -> Result { let key = >::from(key) .try_into() @@ -89,6 +110,11 @@ Ok(prop.to_vec()) } + /// Set the sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract. fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result { check_is_owner_or_admin(caller, self)?; @@ -98,6 +124,9 @@ save(self) } + /// Collection sponsorship confirmation. + /// + /// @dev After setting the sponsor for the collection, it must be confirmed with this function. fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result { let caller = T::CrossAccountId::from_eth(caller); if !self @@ -109,6 +138,16 @@ save(self) } + /// Set limits for the collection. + /// @dev Throws error if limit not found. + /// @param limit Name of the limit. Valid names: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// @param value Value of the limit. #[solidity(rename_selector = "setCollectionLimit")] fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result { check_is_owner_or_admin(caller, self)?; @@ -145,6 +184,13 @@ save(self) } + /// Set limits for the collection. + /// @dev Throws error if limit not found. + /// @param limit Name of the limit. Valid names: + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// @param value Value of the limit. #[solidity(rename_selector = "setCollectionLimit")] fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result { check_is_owner_or_admin(caller, self)?; @@ -172,10 +218,13 @@ save(self) } + /// Get contract address. fn contract_address(&self, _caller: caller) -> Result
{ Ok(crate::eth::collection_id_to_address(self.id)) } + /// Add collection admin by substrate address. + /// @param new_admin Substrate administrator address. fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result { let caller = T::CrossAccountId::from_eth(caller); let mut new_admin_arr: [u8; 32] = Default::default(); @@ -186,21 +235,20 @@ Ok(()) } - fn remove_collection_admin_substrate( - &self, - caller: caller, - new_admin: uint256, - ) -> Result { + /// Remove collection admin by substrate address. + /// @param admin Substrate administrator address. + fn remove_collection_admin_substrate(&self, caller: caller, admin: uint256) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let mut new_admin_arr: [u8; 32] = Default::default(); - new_admin.to_big_endian(&mut new_admin_arr); - let account_id = T::AccountId::from(new_admin_arr); - let new_admin = T::CrossAccountId::from_sub(account_id); - >::toggle_admin(self, &caller, &new_admin, false) - .map_err(dispatch_to_evm::)?; + let mut admin_arr: [u8; 32] = Default::default(); + admin.to_big_endian(&mut admin_arr); + let account_id = T::AccountId::from(admin_arr); + let admin = T::CrossAccountId::from_sub(account_id); + >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; Ok(()) } + /// Add collection admin. + /// @param new_admin Address of the added administrator. fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result { let caller = T::CrossAccountId::from_eth(caller); let new_admin = T::CrossAccountId::from_eth(new_admin); @@ -208,6 +256,9 @@ Ok(()) } + /// Remove collection admin. + /// + /// @param new_admin Address of the removed administrator. fn remove_collection_admin(&self, caller: caller, admin: address) -> Result { let caller = T::CrossAccountId::from_eth(caller); let admin = T::CrossAccountId::from_eth(admin); @@ -215,6 +266,9 @@ Ok(()) } + /// Toggle accessibility of collection nesting. + /// + /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' #[solidity(rename_selector = "setCollectionNesting")] fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result { check_is_owner_or_admin(caller, self)?; @@ -235,6 +289,10 @@ save(self) } + /// Toggle accessibility of collection nesting. + /// + /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + /// @param collections Addresses of collections that will be available for nesting. #[solidity(rename_selector = "setCollectionNesting")] fn set_nesting( &mut self, @@ -280,6 +338,10 @@ save(self) } + /// Set the collection access method. + /// @param mode Access mode + /// 0 for Normal + /// 1 for AllowList fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result { check_is_owner_or_admin(caller, self)?; let permissions = CollectionPermissions { @@ -300,6 +362,9 @@ save(self) } + /// Add the user to the allowed list. + /// + /// @param user Address of a trusted user. fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result { let caller = T::CrossAccountId::from_eth(caller); let user = T::CrossAccountId::from_eth(user); @@ -307,6 +372,9 @@ Ok(()) } + /// Remove the user from the allowed list. + /// + /// @param user Address of a removed user. fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result { let caller = T::CrossAccountId::from_eth(caller); let user = T::CrossAccountId::from_eth(user); @@ -314,6 +382,9 @@ Ok(()) } + /// Switch permission for minting. + /// + /// @param mode Enable if "true". fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result { check_is_owner_or_admin(caller, self)?; let permissions = CollectionPermissions { @@ -351,6 +422,7 @@ Ok(()) } +/// Get the "tokenURI" key as [PropertyKey](up_data_structs::PropertyKey). pub fn token_uri_key() -> up_data_structs::PropertyKey { b"tokenURI" .to_vec() --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +//! The module contains a number of functions for converting and checking ethereum identifiers. + use up_data_structs::CollectionId; use sp_core::H160; @@ -23,6 +25,7 @@ 0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e, ]; +/// Maps the ethereum address of the collection in substrate. pub fn map_eth_to_id(eth: &H160) -> Option { if eth[0..16] != ETH_COLLECTION_PREFIX { return None; @@ -31,6 +34,8 @@ id_bytes.copy_from_slice(ð[16..20]); Some(CollectionId(u32::from_be_bytes(id_bytes))) } + +/// Maps the substrate collection id in ethereum. pub fn collection_id_to_address(id: CollectionId) -> H160 { let mut out = [0; 20]; out[0..16].copy_from_slice(Ð_COLLECTION_PREFIX); @@ -38,6 +43,7 @@ H160(out) } +/// Check if the ethereum address is a collection. pub fn is_collection(address: &H160) -> bool { address[0..16] == ETH_COLLECTION_PREFIX } --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -14,8 +14,43 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -#![cfg_attr(not(feature = "std"), no_std)] +//! # Common pallet +//! +//! The Common pallet provides functionality for handling collections. +//! +//! ## Overview +//! +//! The Common pallet provides an interface for common collection operations for different collection types +//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module. +//! It also provides this functionality to EVM, see [erc] and [eth] modules. +//! +//! The Common pallet provides functions for: +//! +//! - Setting and approving collection sponsor. +//! - Get\set\delete allow list. +//! - Get\set\delete collection properties. +//! - Get\set\delete collection property permissions. +//! - Get\set\delete token property permissions. +//! - Get\set\delete collection administrators. +//! - Checking access permissions. +//! +//! ### Terminology +//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will +//! be possible to mint tokens. +//! +//! **Allow list** - List of users who have the right to minting tokens. +//! +//! **Collection properties** - Collection properties are simply key-value stores where various +//! metadata can be placed. +//! +//! **Permissions on token properties** - For each property in the token can be set permission +//! to change, see [`PropertyPermission`]. +//! +//! **Collection administrator** - For a collection, you can set administrators who have the right +//! to most actions on the collection. +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; use core::ops::{Deref, DerefMut}; @@ -90,14 +125,24 @@ pub mod eth; pub mod weights; +/// Weight info. pub type SelfWeightOf = ::WeightInfo; +/// Collection handle contains information about collection data and id. +/// Also provides functionality to count consumed gas. +/// +/// CollectionHandle is used as a generic wrapper for collections of all types. +/// It allows to perform common operations and queries on any collection type, +/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`]. #[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"] pub struct CollectionHandle { + /// Collection id pub id: CollectionId, collection: Collection, + /// Substrate recorder for counting consumed gas pub recorder: SubstrateRecorder, } + impl WithRecorder for CollectionHandle { fn recorder(&self) -> &SubstrateRecorder { &self.recorder @@ -106,7 +151,9 @@ self.recorder } } + impl CollectionHandle { + /// Same as [CollectionHandle::new] but with an explicit gas limit. pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option { >::get(id).map(|collection| Self { id, @@ -115,6 +162,7 @@ }) } + /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`]. pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder) -> Option { >::get(id).map(|collection| Self { id, @@ -123,14 +171,18 @@ }) } + /// Retrives collection data from storage and creates collection handle with default parameters. + /// If collection not found return `None` pub fn new(id: CollectionId) -> Option { Self::new_with_gas_limit(id, u64::MAX) } + /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned. pub fn try_get(id: CollectionId) -> Result { Ok(Self::new(id).ok_or(>::CollectionNotFound)?) } + /// Consume gas for reading. pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> { self.recorder .consume_gas(T::GasWeightMapping::weight_to_gas( @@ -140,6 +192,7 @@ )) } + /// Consume gas for writing. pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> { self.recorder .consume_gas(T::GasWeightMapping::weight_to_gas( @@ -148,16 +201,27 @@ .saturating_mul(writes), )) } + + /// Save collection to storage. pub fn save(self) -> DispatchResult { >::insert(self.id, self.collection); Ok(()) } + /// Set collection sponsor. + /// + /// Unique collections allows sponsoring for certain actions. + /// This method allows you to set the sponsor of the collection. + /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`]. pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult { self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor); Ok(()) } + /// Confirm sponsorship + /// + /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation. + /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`]. pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result { if self.collection.sponsorship.pending_sponsor() != Some(sender) { return Ok(false); @@ -168,7 +232,7 @@ } /// Checks that the collection was created with, and must be operated upon through **Unique API**. - /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error. + /// Now check only the `external_collection` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error. pub fn check_is_internal(&self) -> DispatchResult { if self.external_collection { return Err(>::CollectionIsExternal)?; @@ -178,7 +242,7 @@ } /// Checks that the collection was created with, and must be operated upon through an **assimilated API**. - /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error. + /// Now check only the `external_collection` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error. pub fn check_is_external(&self) -> DispatchResult { if !self.external_collection { return Err(>::CollectionIsInternal)?; @@ -203,23 +267,34 @@ } impl CollectionHandle { - pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult { - ensure!(*subject.as_sub() == self.owner, >::NoPermission); + /// Checks if the `user` is the owner of the collection. + pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult { + ensure!(*user.as_sub() == self.owner, >::NoPermission); Ok(()) } - pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool { - *subject.as_sub() == self.owner || >::get((self.id, subject)) + + /// Returns **true** if the `user` is the owner or administrator of the collection. + pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool { + *user.as_sub() == self.owner || >::get((self.id, user)) } - pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult { - ensure!(self.is_owner_or_admin(subject), >::NoPermission); + + /// Checks if the `user` is the owner or administrator of the collection. + pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult { + ensure!(self.is_owner_or_admin(user), >::NoPermission); Ok(()) } + + /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions. pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool { self.limits.owner_can_transfer() && self.is_owner_or_admin(user) } + + /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions. pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool { self.limits.owner_can_transfer() && self.is_owner_or_admin(user) } + + /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns. pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult { ensure!( >::get((self.id, user)), @@ -249,21 +324,34 @@ + TypeInfo + account::Config { + /// Weight information for functions of this pallet. type WeightInfo: WeightInfo; + + /// Events compatible with [`frame_system::Config::Event`]. type Event: IsType<::Event> + From>; + /// Handler of accounts and payment. type Currency: Currency; + /// Set price to create a collection. #[pallet::constant] type CollectionCreationPrice: Get< <::Currency as Currency>::Balance, >; + + /// Dispatcher of operations on collections. type CollectionDispatch: CollectionDispatch; + /// Account which holds the chain's treasury. type TreasuryAccountId: Get; + + /// Address under which the CollectionHelper contract would be available. type ContractAddress: Get; + /// Mapper for token addresses to Ethereum addresses. type EvmTokenAddressMapping: TokenAddressMapping; + + /// Mapper for token addresses to [`CrossAccountId`]. type CrossTokenAddressMapping: TokenAddressMapping; } @@ -276,6 +364,7 @@ #[pallet::extra_constants] impl Pallet { + /// Maximum admins per collection. pub fn collection_admins_limit() -> u32 { COLLECTION_ADMINS_LIMIT } @@ -285,94 +374,116 @@ #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { /// New collection was created - /// - /// # Arguments - /// - /// * collection_id: Globally unique identifier of newly created collection. - /// - /// * mode: [CollectionMode] converted into u8. - /// - /// * account_id: Collection owner. - CollectionCreated(CollectionId, u8, T::AccountId), + CollectionCreated( + /// Globally unique identifier of newly created collection. + CollectionId, + /// [`CollectionMode`] converted into _u8_. + u8, + /// Collection owner. + T::AccountId, + ), /// New collection was destroyed - /// - /// # Arguments - /// - /// * collection_id: Globally unique identifier of collection. - CollectionDestroyed(CollectionId), + CollectionDestroyed( + /// Globally unique identifier of collection. + CollectionId, + ), /// New item was created. - /// - /// # Arguments - /// - /// * collection_id: Id of the collection where item was created. - /// - /// * item_id: Id of an item. Unique within the collection. - /// - /// * recipient: Owner of newly created item - /// - /// * amount: Always 1 for NFT - ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128), + ItemCreated( + /// Id of the collection where item was created. + CollectionId, + /// Id of an item. Unique within the collection. + TokenId, + /// Owner of newly created item + T::CrossAccountId, + /// Always 1 for NFT + u128, + ), /// Collection item was burned. - /// - /// # Arguments - /// - /// * collection_id. - /// - /// * item_id: Identifier of burned NFT. - /// - /// * owner: which user has destroyed its tokens - /// - /// * amount: Always 1 for NFT - ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128), + ItemDestroyed( + /// Id of the collection where item was destroyed. + CollectionId, + /// Identifier of burned NFT. + TokenId, + /// Which user has destroyed its tokens. + T::CrossAccountId, + /// Amount of token pieces destroed. Always 1 for NFT. + u128, + ), /// Item was transferred - /// - /// * collection_id: Id of collection to which item is belong - /// - /// * item_id: Id of an item - /// - /// * sender: Original owner of item - /// - /// * recipient: New owner of item - /// - /// * amount: Always 1 for NFT Transfer( + /// Id of collection to which item is belong. CollectionId, + /// Id of an item. TokenId, + /// Original owner of item. T::CrossAccountId, + /// New owner of item. T::CrossAccountId, + /// Amount of token pieces transfered. Always 1 for NFT. u128, ), - /// * collection_id - /// - /// * item_id - /// - /// * sender - /// - /// * spender - /// - /// * amount + /// Amount pieces of token owned by `sender` was approved for `spender`. Approved( + /// Id of collection to which item is belong. CollectionId, + /// Id of an item. TokenId, + /// Original owner of item. T::CrossAccountId, + /// Id for which the approval was granted. T::CrossAccountId, + /// Amount of token pieces transfered. Always 1 for NFT. u128, ), - CollectionPropertySet(CollectionId, PropertyKey), + /// The colletion property has been set. + CollectionPropertySet( + /// Id of collection to which property has been set. + CollectionId, + /// The property that was set. + PropertyKey, + ), - CollectionPropertyDeleted(CollectionId, PropertyKey), + /// The property has been deleted. + CollectionPropertyDeleted( + /// Id of collection to which property has been deleted. + CollectionId, + /// The property that was deleted. + PropertyKey, + ), - TokenPropertySet(CollectionId, TokenId, PropertyKey), + /// The token property has been set. + TokenPropertySet( + /// Identifier of the collection whose token has the property set. + CollectionId, + /// The token for which the property was set. + TokenId, + /// The property that was set. + PropertyKey, + ), - TokenPropertyDeleted(CollectionId, TokenId, PropertyKey), + /// The token property has been deleted. + TokenPropertyDeleted( + /// Identifier of the collection whose token has the property deleted. + CollectionId, + /// The token for which the property was deleted. + TokenId, + /// The property that was deleted. + PropertyKey, + ), - PropertyPermissionSet(CollectionId, PropertyKey), + /// The colletion property permission has been set. + PropertyPermissionSet( + /// Id of collection to which property permission has been set. + CollectionId, + /// The property permission that was set. + PropertyKey, + ), } #[pallet::error] @@ -460,13 +571,16 @@ CollectionIsInternal, } + /// Storage of the count of created collections. #[pallet::storage] pub type CreatedCollectionCount = StorageValue; + + /// Storage of the count of deleted collections. #[pallet::storage] pub type DestroyedCollectionCount = StorageValue; - /// Collection info + /// Storage of collection info. #[pallet::storage] pub type CollectionById = StorageMap< Hasher = Blake2_128Concat, @@ -475,7 +589,7 @@ QueryKind = OptionQuery, >; - /// Collection properties + /// Storage of collection properties. #[pallet::storage] #[pallet::getter(fn collection_properties)] pub type CollectionProperties = StorageMap< @@ -486,6 +600,7 @@ OnEmpty = up_data_structs::CollectionProperties, >; + /// Storage of collection properties permissions. #[pallet::storage] #[pallet::getter(fn property_permissions)] pub type CollectionPropertyPermissions = StorageMap< @@ -495,6 +610,7 @@ QueryKind = ValueQuery, >; + /// Storage of collection admins count. #[pallet::storage] pub type AdminAmount = StorageMap< Hasher = Blake2_128Concat, @@ -525,7 +641,7 @@ QueryKind = ValueQuery, >; - /// Not used by code, exists only to provide some types to metadata + /// Not used by code, exists only to provide some types to metadata. #[pallet::storage] pub type DummyStorageValue = StorageValue< Value = ( @@ -619,7 +735,9 @@ } impl Pallet { - /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens + /// Enshure that receiver address is correct. + /// + /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens. pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult { ensure!( &T::CrossAccountId::from_eth(H160([0; 20])) != receiver, @@ -627,19 +745,27 @@ ); Ok(()) } + + /// Get a vector of collection admins. pub fn adminlist(collection: CollectionId) -> Vec { >::iter_prefix((collection,)) .map(|(a, _)| a) .collect() } + + /// Get a vector of users allowed to mint tokens. pub fn allowlist(collection: CollectionId) -> Vec { >::iter_prefix((collection,)) .map(|(a, _)| a) .collect() } + + /// Is `user` allowed to mint token in `collection`. pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool { >::get((collection, user)) } + + /// Get statistics of collections. pub fn collection_stats() -> CollectionStats { let created = >::get(); let destroyed = >::get(); @@ -650,6 +776,7 @@ } } + /// Get the effective limits for the collection. pub fn effective_collection_limits(collection: CollectionId) -> Option { let collection = >::get(collection); if collection.is_none() { @@ -683,6 +810,7 @@ Some(effective_limits) } + /// Returns information about the `collection` adapted for rpc. pub fn rpc_collection(collection: CollectionId) -> Option> { let Collection { name, @@ -758,6 +886,11 @@ } impl Pallet { + /// Create new collection. + /// + /// * `owner` - The owner of the collection. + /// * `data` - Description of the created collection. + /// * `is_external` - Marks that collection managet by not "Unique network". pub fn init_collection( owner: T::CrossAccountId, data: CreateCollectionData, @@ -858,6 +991,10 @@ Ok(id) } + /// Destroy collection. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. pub fn destroy_collection( collection: CollectionHandle, sender: &T::CrossAccountId, @@ -886,6 +1023,11 @@ Ok(()) } + /// Set collection property. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `property` - The property to set. pub fn set_collection_property( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -904,6 +1046,11 @@ Ok(()) } + /// Set scouped collection property. + /// + /// * `collection_id` - ID of the collection for which the property is being set. + /// * `scope` - Property scope. + /// * `property` - The property to set. pub fn set_scoped_collection_property( collection_id: CollectionId, scope: PropertyScope, @@ -917,6 +1064,11 @@ Ok(()) } + /// Set scouped collection properties. + /// + /// * `collection_id` - ID of the collection for which the properties is being set. + /// * `scope` - Property scope. + /// * `properties` - The properties to set. pub fn set_scoped_collection_properties( collection_id: CollectionId, scope: PropertyScope, @@ -930,6 +1082,11 @@ Ok(()) } + /// Set collection properties. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `properties` - The properties to set. #[transactional] pub fn set_collection_properties( collection: &CollectionHandle, @@ -943,6 +1100,11 @@ Ok(()) } + /// Delete collection property. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `property` - The property to delete. pub fn delete_collection_property( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -963,6 +1125,11 @@ Ok(()) } + /// Delete collection properties. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `properties` - The properties to delete. #[transactional] pub fn delete_collection_properties( collection: &CollectionHandle, @@ -976,7 +1143,12 @@ Ok(()) } - // For migrations + /// Set collection propetry permission without any checks. + /// + /// Used for migrations. + /// + /// * `collection` - Collection handler. + /// * `property_permissions` - Property permissions. pub fn set_property_permission_unchecked( collection: CollectionId, property_permission: PropertyKeyPermission, @@ -988,6 +1160,11 @@ Ok(()) } + /// Set collection property permission. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `property_permission` - Property permission. pub fn set_property_permission( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1018,6 +1195,11 @@ Ok(()) } + /// Set token property permission. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `property_permissions` - Property permissions. #[transactional] pub fn set_token_property_permissions( collection: &CollectionHandle, @@ -1031,6 +1213,7 @@ Ok(()) } + /// Get collection property. pub fn get_collection_property( collection_id: CollectionId, key: &PropertyKey, @@ -1038,6 +1221,7 @@ Self::collection_properties(collection_id).get(key).cloned() } + /// Convert byte vector to property key vector. pub fn bytes_keys_to_property_keys( keys: Vec>, ) -> Result, DispatchError> { @@ -1049,6 +1233,7 @@ .collect::, DispatchError>>() } + /// Get properties according to given keys. pub fn filter_collection_properties( collection_id: CollectionId, keys: Option>, @@ -1076,6 +1261,7 @@ Ok(properties) } + /// Get property permissions according to given keys. pub fn filter_property_permissions( collection_id: CollectionId, keys: Option>, @@ -1105,6 +1291,7 @@ Ok(key_permissions) } + /// Toggle `user` participation in the `collection`'s allow list. pub fn toggle_allowlist( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1124,6 +1311,7 @@ Ok(()) } + /// Toggle `user` participation in the `collection`'s admin list. pub fn toggle_admin( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1159,6 +1347,7 @@ Ok(()) } + /// Merge set fields from `new_limit` to `old_limit`. pub fn clamp_limits( mode: CollectionMode, old_limit: &CollectionLimits, @@ -1204,20 +1393,22 @@ Ok(new_limit) } + /// Merge set fields from `new_permission` to `old_permission`. pub fn clamp_permissions( _mode: CollectionMode, - old_limit: &CollectionPermissions, - mut new_limit: CollectionPermissions, + old_permission: &CollectionPermissions, + mut new_permission: CollectionPermissions, ) -> Result { - limit_default_clone!(old_limit, new_limit, + limit_default_clone!(old_permission, new_permission, access => {}, mint_mode => {}, nesting => { /* todo check for permissive, if only it gets out of benchmarks */ }, ); - Ok(new_limit) + Ok(new_permission) } } +/// Indicates unsupported methods by returning [Error::UnsupportedOperation]. #[macro_export] macro_rules! unsupported { () => { @@ -1225,32 +1416,72 @@ }; } -/// Worst cases +/// Return weights for various worst-case operations. pub trait CommonWeightInfo { + /// Weight of item creation. fn create_item() -> Weight; + + /// Weight of items creation. fn create_multiple_items(amount: &[CreateItemData]) -> Weight; + + /// Weight of items creation. fn create_multiple_items_ex(cost: &CreateItemExData) -> Weight; + + /// The weight of the burning item. fn burn_item() -> Weight; + + /// Property setting weight. + /// + /// * `amount`- The number of properties to set. fn set_collection_properties(amount: u32) -> Weight; + + /// Collection property deletion weight. + /// + /// * `amount`- The number of properties to set. fn delete_collection_properties(amount: u32) -> Weight; + + /// Token property setting weight. + /// + /// * `amount`- The number of properties to set. fn set_token_properties(amount: u32) -> Weight; + + /// Token property deletion weight. + /// + /// * `amount`- The number of properties to delete. fn delete_token_properties(amount: u32) -> Weight; + + /// Token property permissions set weight. + /// + /// * `amount`- The number of property permissions to set. fn set_token_property_permissions(amount: u32) -> Weight; + + /// Transfer price of the token or its parts. fn transfer() -> Weight; + + /// The price of setting the permission of the operation from another user. fn approve() -> Weight; + + /// Transfer price from another user. fn transfer_from() -> Weight; + + /// The price of burning a token from another user. fn burn_from() -> Weight; /// Differs from burn_item in case of Fungible and Refungible, as it should burn - /// whole users's balance + /// whole users's balance. /// - /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead + /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead fn burn_recursively_self_raw() -> Weight; - /// Cost of iterating over `amount` children while burning, without counting child burning itself + + /// Cost of iterating over `amount` children while burning, without counting child burning itself. /// - /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead + /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead fn burn_recursively_breadth_raw(amount: u32) -> Weight; + /// The price of recursive burning a token. + /// + /// `max_selfs` - The maximum burning weight of the token itself. + /// `max_breadth` - The maximum number of nested tokens to burn. fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight { Self::burn_recursively_self_raw() .saturating_mul(max_selfs.max(1) as u64) @@ -1258,11 +1489,23 @@ } } +/// Weight info extension trait for refungible pallet. pub trait RefungibleExtensionsWeightInfo { + /// Weight of token repartition. fn repartition() -> Weight; } +/// Common collection operations. +/// +/// It wraps methods in Fungible, Nonfungible and Refungible pallets +/// and adds weight info. pub trait CommonCollectionOperations { + /// Create token. + /// + /// * `sender` - The user who mint the token and pays for the transaction. + /// * `to` - The user who will own the token. + /// * `data` - Token data. + /// * `nesting_budget` - A budget that can be spent on nesting tokens. fn create_item( &self, sender: T::CrossAccountId, @@ -1270,6 +1513,13 @@ data: CreateItemData, nesting_budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Create multiple tokens. + /// + /// * `sender` - The user who mint the token and pays for the transaction. + /// * `to` - The user who will own the token. + /// * `data` - Token data. + /// * `nesting_budget` - A budget that can be spent on nesting tokens. fn create_multiple_items( &self, sender: T::CrossAccountId, @@ -1277,18 +1527,38 @@ data: Vec, nesting_budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Create multiple tokens. + /// + /// * `sender` - The user who mint the token and pays for the transaction. + /// * `to` - The user who will own the token. + /// * `data` - Token data. + /// * `nesting_budget` - A budget that can be spent on nesting tokens. fn create_multiple_items_ex( &self, sender: T::CrossAccountId, data: CreateItemExData, nesting_budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Burn token. + /// + /// * `sender` - The user who owns the token. + /// * `token` - Token id that will burned. + /// * `amount` - The number of parts of the token that will be burned. fn burn_item( &self, sender: T::CrossAccountId, token: TokenId, amount: u128, ) -> DispatchResultWithPostInfo; + + /// Burn token and all nested tokens recursievly. + /// + /// * `sender` - The user who owns the token. + /// * `token` - Token id that will burned. + /// * `self_budget` - The budget that can be spent on burning tokens. + /// * `breadth_budget` - The budget that can be spent on burning nested tokens. fn burn_item_recursively( &self, sender: T::CrossAccountId, @@ -1296,43 +1566,95 @@ self_budget: &dyn Budget, breadth_budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Set collection properties. + /// + /// * `sender` - Must be either the owner of the collection or its admin. + /// * `properties` - Properties to be set. fn set_collection_properties( &self, sender: T::CrossAccountId, properties: Vec, ) -> DispatchResultWithPostInfo; + + /// Delete collection properties. + /// + /// * `sender` - Must be either the owner of the collection or its admin. + /// * `properties` - The properties to be removed. fn delete_collection_properties( &self, sender: &T::CrossAccountId, property_keys: Vec, ) -> DispatchResultWithPostInfo; + + /// Set token properties. + /// + /// The appropriate [`PropertyPermission`] for the token property + /// must be set with [`Self::set_token_property_permissions`]. + /// + /// * `sender` - Must be either the owner of the token or its admin. + /// * `token_id` - The token for which the properties are being set. + /// * `properties` - Properties to be set. + /// * `budget` - Budget for setting properties. fn set_token_properties( &self, sender: T::CrossAccountId, token_id: TokenId, - property: Vec, - nesting_budget: &dyn Budget, + properties: Vec, + budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Remove token properties. + /// + /// The appropriate [`PropertyPermission`] for the token property + /// must be set with [`Self::set_token_property_permissions`]. + /// + /// * `sender` - Must be either the owner of the token or its admin. + /// * `token_id` - The token for which the properties are being remove. + /// * `property_keys` - Keys to remove corresponding properties. + /// * `budget` - Budget for removing properties. fn delete_token_properties( &self, sender: T::CrossAccountId, token_id: TokenId, property_keys: Vec, - nesting_budget: &dyn Budget, + budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Set token property permissions. + /// + /// * `sender` - Must be either the owner of the token or its admin. + /// * `token_id` - The token for which the properties are being set. + /// * `properties` - Properties to be set. + /// * `budget` - Budget for setting properties. fn set_token_property_permissions( &self, sender: &T::CrossAccountId, property_permissions: Vec, ) -> DispatchResultWithPostInfo; + + /// Transfer amount of token pieces. + /// + /// * `sender` - Donor user. + /// * `to` - Recepient user. + /// * `token` - The token of which parts are being sent. + /// * `amount` - The number of parts of the token that will be transferred. + /// * `budget` - The maximum budget that can be spent on the transfer. fn transfer( &self, sender: T::CrossAccountId, to: T::CrossAccountId, token: TokenId, amount: u128, - nesting_budget: &dyn Budget, + budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from]. + /// + /// * `sender` - The user who grants access to the token. + /// * `spender` - The user to whom the rights are granted. + /// * `token` - The token to which access is granted. + /// * `amount` - The amount of pieces that another user can dispose of. fn approve( &self, sender: T::CrossAccountId, @@ -1340,6 +1662,17 @@ token: TokenId, amount: u128, ) -> DispatchResultWithPostInfo; + + /// Send parts of a token owned by another user. + /// + /// Before calling this method, you must grant rights to the calling user via [`Self::approve`]. + /// + /// * `sender` - The user who must have access to the token (see [`Self::approve`]). + /// * `from` - The user who owns the token. + /// * `to` - Recepient user. + /// * `token` - The token of which parts are being sent. + /// * `amount` - The number of parts of the token that will be transferred. + /// * `budget` - The maximum budget that can be spent on the transfer. fn transfer_from( &self, sender: T::CrossAccountId, @@ -1347,67 +1680,140 @@ to: T::CrossAccountId, token: TokenId, amount: u128, - nesting_budget: &dyn Budget, + budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + + /// Burn parts of a token owned by another user. + /// + /// Before calling this method, you must grant rights to the calling user via [`Self::approve`]. + /// + /// * `sender` - The user who must have access to the token (see [`Self::approve`]). + /// * `from` - The user who owns the token. + /// * `token` - The token of which parts are being sent. + /// * `amount` - The number of parts of the token that will be transferred. + /// * `budget` - The maximum budget that can be spent on the burn. fn burn_from( &self, sender: T::CrossAccountId, from: T::CrossAccountId, token: TokenId, amount: u128, - nesting_budget: &dyn Budget, + budget: &dyn Budget, ) -> DispatchResultWithPostInfo; + /// Check permission to nest token. + /// + /// * `sender` - The user who initiated the check. + /// * `from` - The token that is checked for embedding. + /// * `under` - Token under which to check. + /// * `budget` - The maximum budget that can be spent on the check. fn check_nesting( &self, sender: T::CrossAccountId, from: (CollectionId, TokenId), under: TokenId, - nesting_budget: &dyn Budget, + budget: &dyn Budget, ) -> DispatchResult; + /// Nest one token into another. + /// + /// * `under` - Token holder. + /// * `to_nest` - Nested token. fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId)); + /// Unnest token. + /// + /// * `under` - Token holder. + /// * `to_nest` - Token to unnest. fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId)); + /// Get all user tokens. + /// + /// * `account` - Account for which you need to get tokens. fn account_tokens(&self, account: T::CrossAccountId) -> Vec; + + /// Get all the tokens in the collection. fn collection_tokens(&self) -> Vec; + + /// Check if the token exists. + /// + /// * `token` - Id token to check. fn token_exists(&self, token: TokenId) -> bool; + + /// Get the id of the last minted token. fn last_token_id(&self) -> TokenId; + /// Get the owner of the token. + /// + /// * `token` - The token for which you need to find out the owner. fn token_owner(&self, token: TokenId) -> Option; + + /// Get the value of the token property by key. + /// + /// * `token` - Token with the property to get. + /// * `key` - Property name. fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option; - fn token_properties(&self, token_id: TokenId, keys: Option>) -> Vec; + + /// Get a set of token properties by key vector. + /// + /// * `token` - Token with the property to get. + /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result), + /// then all properties are returned. + fn token_properties(&self, token: TokenId, keys: Option>) -> Vec; + /// Amount of unique collection tokens fn total_supply(&self) -> u32; - /// Amount of different tokens account has (Applicable to nonfungible/refungible) + + /// Amount of different tokens account has. + /// + /// * `account` - The account for which need to get the balance. fn account_balance(&self, account: T::CrossAccountId) -> u32; - /// Amount of specific token account have (Applicable to fungible/refungible) + + /// Amount of specific token account have. fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128; + /// Amount of token pieces fn total_pieces(&self, token: TokenId) -> Option; + + /// Get the number of parts of the token that a trusted user can manage. + /// + /// * `sender` - Trusted user. + /// * `spender` - Owner of the token. + /// * `token` - The token for which to get the value. fn allowance( &self, sender: T::CrossAccountId, spender: T::CrossAccountId, token: TokenId, ) -> u128; + + /// Get extension for RFT collection. fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions>; } +/// Extension for RFT collection. pub trait RefungibleExtensions where T: Config, { + /// Change the number of parts of the token. + /// + /// When the value changes down, this function is equivalent to burning parts of the token. + /// + /// * `sender` - The user calling the repartition operation. Must be the owner of the token. + /// * `token` - The token for which you want to change the number of parts. + /// * `amount` - The new value of the parts of the token. fn repartition( &self, - owner: &T::CrossAccountId, + sender: &T::CrossAccountId, token: TokenId, amount: u128, ) -> DispatchResultWithPostInfo; } -// Flexible enough for implementing CommonCollectionOperations +/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`]. +/// +/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so. pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo { let post_info = PostDispatchInfo { actual_weight: Some(weight), -- gitstuff