From 263bc691fe1aae81266e09b3667860b082ed5488 Mon Sep 17 00:00:00 2001 From: Trubnikov Sergey Date: Fri, 15 Jul 2022 07:47:44 +0000 Subject: [PATCH] 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; + /// Получить реализацию [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,39 @@ use crate::{Pallet, CollectionHandle, Config, CollectionProperties}; +/// Events for etherium 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]; 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, @@ -60,14 +72,17 @@ ) -> Result { let caller = T::CrossAccountId::from_eth(caller); let key = >::from(key) - .try_into() - .map_err(|_| "key too large")?; + .try_into() + .map_err(|_| "key too large")?; let value = value.try_into().map_err(|_| "value too large")?; - + >::set_collection_property(self, &caller, Property { key, value }) - .map_err(dispatch_to_evm::) + .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 +92,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 +109,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 +123,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 +137,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 +183,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 +217,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 +234,25 @@ Ok(()) } + /// Remove collection admin by substrate address. + /// @param admin Substrate administrator address. fn remove_collection_admin_substrate( &self, caller: caller, - new_admin: uint256, + 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) + 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 +260,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 +270,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 +293,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 +342,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,13 +366,19 @@ 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); >::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; 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 +386,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 +426,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 etherium 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 etherium 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 etherium. 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 etherium 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,46 @@ // 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 functions for: +//! +//! - Setting and approving collection soponsor. +//! - 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. +//! - Provides an interface for common collection operations for different collection types. +//! - Provides dispatching for implementations of common collection operations, see [dispatch] module. +//! - Provides functionality of collection into evm, see [erc] and [eth] module. +//! +//! ### 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. +//! +//! **Collection property permissions** - For each property in the collection can be set permission +//! to change, see [PropertyPermission]. +//! +//! **Permissions on token properties** - Similar to _permissions on collection properties_, +//! only restrictions apply to token properties. +//! +//! **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 +128,20 @@ 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. #[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 +150,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 +161,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 +170,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 [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 +191,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 +200,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 +231,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 +241,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 +266,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 +323,34 @@ + TypeInfo + account::Config { + /// Weight info. type WeightInfo: WeightInfo; + + /// Events compatible with [frame_system::Config::Event]. type Event: IsType<::Event> + From>; + /// Currency. type Currency: Currency; + /// Price getter to create the collection. #[pallet::constant] type CollectionCreationPrice: Get< <::Currency as Currency>::Balance, >; + + /// Collection dispatcher. type CollectionDispatch: CollectionDispatch; + /// Treasury account id getter. type TreasuryAccountId: Get; + + /// Contract address getter. type ContractAddress: Get; + /// Mapper for tokens to Etherium addresses. type EvmTokenAddressMapping: TokenAddressMapping; + + /// Mapper for tokens to [CrossAccountId]. type CrossTokenAddressMapping: TokenAddressMapping; } @@ -276,6 +363,7 @@ #[pallet::extra_constants] impl Pallet { + /// Maximum admins per collection. pub fn collection_admins_limit() -> u32 { COLLECTION_ADMINS_LIMIT } @@ -285,94 +373,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), - - CollectionPropertyDeleted(CollectionId, PropertyKey), - - TokenPropertySet(CollectionId, TokenId, PropertyKey), - - TokenPropertyDeleted(CollectionId, TokenId, PropertyKey), - - PropertyPermissionSet(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 + ), + + /// The property has been deleted. + CollectionPropertyDeleted( + /// Id of collection to which property has been deleted. + CollectionId, + /// The property that was deleted. + 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 + ), + + + /// 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 + ), + + /// 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 +570,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 +588,7 @@ QueryKind = OptionQuery, >; - /// Collection properties + /// Storage of collection properties. #[pallet::storage] #[pallet::getter(fn collection_properties)] pub type CollectionProperties = StorageMap< @@ -486,6 +599,7 @@ OnEmpty = up_data_structs::CollectionProperties, >; + /// Storage of collection properties permissions. #[pallet::storage] #[pallet::getter(fn property_permissions)] pub type CollectionPropertyPermissions = StorageMap< @@ -495,6 +609,7 @@ QueryKind = ValueQuery, >; + /// Storage of collection admins count. #[pallet::storage] pub type AdminAmount = StorageMap< Hasher = Blake2_128Concat, @@ -525,7 +640,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 +734,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 +744,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 +775,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 +809,7 @@ Some(effective_limits) } + /// Returns information about the `collection` adapted for rpc. pub fn rpc_collection(collection: CollectionId) -> Option> { let Collection { name, @@ -758,9 +885,14 @@ } 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, + data: CreateCollectionData, is_external: bool, ) -> Result { { @@ -858,6 +990,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 +1022,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 +1045,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, @@ -913,10 +1059,15 @@ properties.try_scoped_set(scope, property.key, property.value) }) .map_err(>::from)?; - + 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 +1081,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, @@ -939,30 +1095,40 @@ for property in properties { Self::set_collection_property(collection, sender, property)?; } - + 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, property_key: PropertyKey, ) -> DispatchResult { collection.check_is_owner_or_admin(sender)?; - + CollectionProperties::::try_mutate(collection.id, |properties| { properties.remove(&property_key) }) .map_err(>::from)?; - + Self::deposit_event(Event::CollectionPropertyDeleted( collection.id, property_key, )); - + 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, @@ -972,11 +1138,16 @@ for key in property_keys { Self::delete_collection_property(collection, sender, key)?; } - + 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 +1159,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 +1194,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 +1212,7 @@ Ok(()) } + /// Get collection property. pub fn get_collection_property( collection_id: CollectionId, key: &PropertyKey, @@ -1038,6 +1220,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 +1232,7 @@ .collect::, DispatchError>>() } + /// Get properties according to given keys. pub fn filter_collection_properties( collection_id: CollectionId, keys: Option>, @@ -1076,6 +1260,7 @@ Ok(properties) } + /// Get property permissions according to given keys. pub fn filter_property_permissions( collection_id: CollectionId, keys: Option>, @@ -1105,6 +1290,7 @@ Ok(key_permissions) } + /// Toggle `user` participation in the `collection`'s allow list. pub fn toggle_allowlist( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1124,6 +1310,7 @@ Ok(()) } + /// Toggle `user` participation in the `collection`'s admin list. pub fn toggle_admin( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1159,6 +1346,7 @@ Ok(()) } + /// Merge set fields from `new_limit` to `old_limit`. pub fn clamp_limits( mode: CollectionMode, old_limit: &CollectionLimits, @@ -1204,20 +1392,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 +1415,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; - fn burn_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 /// - /// 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 /// - /// 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` - 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 +1488,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 +1512,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 +1526,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 +1565,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 +1661,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 has access to the token. + /// * `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 +1679,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 has access to the token. + /// * `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 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 property to get. + /// * `keys` - Vector of 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