difftreelog
doc: architectural changes
in: master
10 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -42,6 +42,7 @@
#[rpc(server)]
#[async_trait]
pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {
+ /// Get tokens owned by account
#[method(name = "unique_accountTokens")]
fn account_tokens(
&self,
@@ -49,12 +50,14 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+ /// Get tokens contained in collection
#[method(name = "unique_collectionTokens")]
fn collection_tokens(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+ /// Check if token exists
#[method(name = "unique_tokenExists")]
fn token_exists(
&self,
@@ -62,7 +65,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<bool>;
-
+ /// Get token owner
#[method(name = "unique_tokenOwner")]
fn token_owner(
&self,
@@ -70,6 +73,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+ /// Get token owner, in case of nested token - find the parent recursively
#[method(name = "unique_topmostTokenOwner")]
fn topmost_token_owner(
&self,
@@ -77,6 +81,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+ /// Get tokens nested directly into the token
#[method(name = "unique_tokenChildren")]
fn token_children(
&self,
@@ -84,7 +89,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Vec<TokenChild>>;
-
+ /// Get collection properties
#[method(name = "unique_collectionProperties")]
fn collection_properties(
&self,
@@ -92,7 +97,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
-
+ /// Get token properties
#[method(name = "unique_tokenProperties")]
fn token_properties(
&self,
@@ -101,7 +106,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
-
+ /// Get property permissions
#[method(name = "unique_propertyPermissions")]
fn property_permissions(
&self,
@@ -109,7 +114,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<PropertyKeyPermission>>;
-
+ /// Get token data
#[method(name = "unique_tokenData")]
fn token_data(
&self,
@@ -118,9 +123,10 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<TokenData<CrossAccountId>>;
-
+ /// Get amount of unique collection tokens
#[method(name = "unique_totalSupply")]
fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;
+ /// Get owned amount of any user tokens
#[method(name = "unique_accountBalance")]
fn account_balance(
&self,
@@ -128,6 +134,7 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<u32>;
+ /// Get owned amount of specific account token
#[method(name = "unique_balance")]
fn balance(
&self,
@@ -136,6 +143,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
+ /// Get allowed amount
#[method(name = "unique_allowance")]
fn allowance(
&self,
@@ -145,19 +153,21 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
-
+ /// Get admin list
#[method(name = "unique_adminlist")]
fn adminlist(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+ /// Get allowlist
#[method(name = "unique_allowlist")]
fn allowlist(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+ /// Check if user is allowed to use collection
#[method(name = "unique_allowed")]
fn allowed(
&self,
@@ -165,17 +175,20 @@
user: CrossAccountId,
at: Option<BlockHash>,
) -> Result<bool>;
+ /// Get last token ID created in a collection
#[method(name = "unique_lastTokenId")]
fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;
+ /// Get collection by specified ID
#[method(name = "unique_collectionById")]
fn collection_by_id(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<RpcCollection<AccountId>>>;
+ /// Get collection stats
#[method(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
-
+ /// Get number of blocks when sponsored transaction is available
#[method(name = "unique_nextSponsored")]
fn next_sponsored(
&self,
@@ -184,14 +197,14 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<u64>>;
-
+ /// Get effective collection limits
#[method(name = "unique_effectiveCollectionLimits")]
fn effective_collection_limits(
&self,
collection_id: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<CollectionLimits>>;
-
+ /// Get total pieces of token
#[method(name = "unique_totalPieces")]
fn total_pieces(
&self,
@@ -304,6 +317,7 @@
fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;
#[method(name = "rmrk_themeNames")]
+ /// Get Base's theme names
fn theme_names(
&self,
base_id: RmrkBaseId,
@@ -311,6 +325,7 @@
) -> Result<Vec<RmrkThemeName>>;
#[method(name = "rmrk_themes")]
+ /// Get Theme info -- name, properties, and inherit flag
fn theme(
&self,
base_id: RmrkBaseId,
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -299,46 +299,48 @@
///
/// # Arguments
///
- /// * collection_id: Globally unique identifier of collection.
+ /// * collection_id: Globally unique identifier of collection that has been destroyed.
CollectionDestroyed(CollectionId),
/// New item was created.
///
/// # Arguments
///
- /// * collection_id: Id of the collection where item was created.
+ /// * collection_id: ID of the collection where the item was created.
///
- /// * item_id: Id of an item. Unique within the collection.
+ /// * item_id: ID of the item. Unique within the collection.
///
- /// * recipient: Owner of newly created item
+ /// * recipient: Owner of the newly created item.
///
- /// * amount: Always 1 for NFT
+ /// * amount: The amount of tokens that were created (always 1 for NFT).
ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),
/// Collection item was burned.
///
/// # Arguments
///
- /// * collection_id.
+ /// * collection_id: Identifier of the collection to which the burned NFT belonged.
///
/// * item_id: Identifier of burned NFT.
///
- /// * owner: which user has destroyed its tokens
+ /// * owner: Which user has destroyed their tokens.
///
- /// * amount: Always 1 for NFT
+ /// * amount: The amount of tokens that were destroyed (always 1 for NFT).
ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),
- /// Item was transferred
+ /// Item was transferred.
+ ///
+ /// # Arguments
///
- /// * collection_id: Id of collection to which item is belong
+ /// * collection_id: ID of the collection to which the item belongs.
///
- /// * item_id: Id of an item
+ /// * item_id: ID of the item trasnferred.
///
- /// * sender: Original owner of item
+ /// * sender: Original owner of the item.
///
- /// * recipient: New owner of item
+ /// * recipient: New owner of the item.
///
- /// * amount: Always 1 for NFT
+ /// * amount: The amount of tokens that were transferred (always 1 for NFT).
Transfer(
CollectionId,
TokenId,
@@ -347,6 +349,10 @@
u128,
),
+ /// Sponsoring allowance was approved.
+ ///
+ /// # Arguments
+ ///
/// * collection_id
///
/// * item_id
@@ -364,14 +370,53 @@
u128,
),
+ /// Collection property was added or edited.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection, whose property was just set.
+ ///
+ /// * property_key: Key of the property that was just set.
CollectionPropertySet(CollectionId, PropertyKey),
+ /// Collection property was deleted.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection, whose property was just deleted.
+ ///
+ /// * property_key: Key of the property that was just deleted.
CollectionPropertyDeleted(CollectionId, PropertyKey),
+ /// Item property was added or edited.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection, whose token's property was just set.
+ ///
+ /// * item_id: ID of the item, whose property was just set.
+ ///
+ /// * property_key: Key of the property that was just set.
TokenPropertySet(CollectionId, TokenId, PropertyKey),
+ /// Item property was deleted.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection, whose token's property was just deleted.
+ ///
+ /// * item_id: ID of the item, whose property was just deleted.
+ ///
+ /// * property_key: Key of the property that was just deleted.
TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),
+ /// Token property permission was added or updated for a collection.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection, whose permissions were just set/updated.
+ ///
+ /// * property_key: Key of the property of the set/updated permission.
PropertyPermissionSet(CollectionId, PropertyKey),
}
@@ -413,26 +458,26 @@
/// Metadata flag frozen
MetadataFlagFrozen,
- /// Item not exists.
+ /// Item does not exist
TokenNotFound,
- /// Item balance not enough.
+ /// Item is balance not enough
TokenValueTooLow,
- /// Requested value more than approved.
+ /// Requested value is more than the approved
ApprovedValueTooLow,
/// Tried to approve more than owned
CantApproveMoreThanOwned,
/// Can't transfer tokens to ethereum zero address
AddressIsZero,
- /// Target collection doesn't supports this operation
+ /// Target collection doesn't support this operation
UnsupportedOperation,
- /// Not sufficient funds to perform action
+ /// Insufficient funds to perform an action
NotSufficientFounds,
- /// User not passed nesting rule
+ /// User does not satisfy the nesting rule
UserIsNotAllowedToNest,
- /// Only tokens from specific collections may nest tokens under this
+ /// Only tokens from specific collections may nest tokens under this one
SourceCollectionIsNotAllowedToNest,
/// Tried to store more data than allowed in collection field
@@ -447,7 +492,7 @@
/// Property key is too long
PropertyKeyIsTooLong,
- /// Only ASCII letters, digits, and '_', '-' are allowed
+ /// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed
InvalidCharacterInPropertyKey,
/// Empty property keys are forbidden
@@ -460,8 +505,11 @@
CollectionIsInternal,
}
+ /// The number of created collections. Essentially contains the last collection ID.
#[pallet::storage]
pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
+
+ /// The number of destroyed collections
#[pallet::storage]
pub type DestroyedCollectionCount<T> =
StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
@@ -486,6 +534,7 @@
OnEmpty = up_data_structs::CollectionProperties,
>;
+ /// Token permissions of a collection
#[pallet::storage]
#[pallet::getter(fn property_permissions)]
pub type CollectionPropertyPermissions<T> = StorageMap<
@@ -495,6 +544,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of collection admins
#[pallet::storage]
pub type AdminAmount<T> = StorageMap<
Hasher = Blake2_128Concat,
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -44,6 +44,7 @@
pub mod erc;
pub mod weights;
+/// todo:doc?
pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
@@ -78,10 +79,12 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Total amount of fungible tokens inside a collection.
#[pallet::storage]
pub type TotalSupply<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;
+ /// Amount of tokens owned by an account inside a collection.
#[pallet::storage]
pub type Balance<T: Config> = StorageNMap<
Key = (
@@ -92,6 +95,7 @@
QueryKind = ValueQuery,
>;
+ /// todo:doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -56,6 +56,8 @@
pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+/// Token data, stored independently from other data used to describe it.
+/// Notably contains the owner account address.
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ItemData<CrossAccountId> {
@@ -102,13 +104,17 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Total amount of minted tokens in a collection.
#[pallet::storage]
pub type TokensMinted<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+
+ /// Amount of burnt tokens in a collection.
#[pallet::storage]
pub type TokensBurnt<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+ /// Token data, used to partially describe a token.
#[pallet::storage]
pub type TokenData<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -116,6 +122,7 @@
QueryKind = OptionQuery,
>;
+ /// Key-value pairs, describing the metadata of a token.
#[pallet::storage]
#[pallet::getter(fn token_properties)]
pub type TokenProperties<T: Config> = StorageNMap<
@@ -125,6 +132,7 @@
OnEmpty = up_data_structs::TokenProperties,
>;
+ /// Scoped, auxiliary properties of a token, primarily used for on-chain operations.
#[pallet::storage]
#[pallet::getter(fn token_aux_property)]
pub type TokenAuxProperties<T: Config> = StorageNMap<
@@ -138,7 +146,7 @@
QueryKind = OptionQuery,
>;
- /// Used to enumerate tokens owned by account
+ /// Used to enumerate tokens owned by account.
#[pallet::storage]
pub type Owned<T: Config> = StorageNMap<
Key = (
@@ -150,7 +158,7 @@
QueryKind = ValueQuery,
>;
- /// Used to enumerate token's children
+ /// Used to enumerate token's children.
#[pallet::storage]
#[pallet::getter(fn token_children)]
pub type TokenChildren<T: Config> = StorageNMap<
@@ -163,6 +171,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of tokens owned in a collection.s
#[pallet::storage]
pub type AccountBalance<T: Config> = StorageNMap<
Key = (
@@ -173,6 +182,7 @@
QueryKind = ValueQuery,
>;
+ /// todo doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -180,6 +190,7 @@
QueryKind = OptionQuery,
>;
+ /// Upgrade from the old schema to properties.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
pallets/refungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};26use pallet_structure::Pallet as PalletStructure;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};29use core::ops::Deref;30use codec::{Encode, Decode, MaxEncodedLen};31use scale_info::TypeInfo;3233pub use pallet::*;34#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod common;37pub mod erc;38pub mod weights;39pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4041#[struct_versioning::versioned(version = 2, upper)]42#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]43pub struct ItemData {44 pub const_data: BoundedVec<u8, CustomDataLimit>,4546 #[version(..2)]47 pub variable_data: BoundedVec<u8, CustomDataLimit>,48}4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use frame_support::{54 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,55 traits::StorageVersion,56 };57 use frame_system::pallet_prelude::*;58 use up_data_structs::{CollectionId, TokenId};59 use super::weights::WeightInfo;6061 #[pallet::error]62 pub enum Error<T> {63 /// Not Refungible item data used to mint in Refungible collection.64 NotRefungibleDataUsedToMintFungibleCollectionToken,65 /// Maximum refungibility exceeded66 WrongRefungiblePieces,67 /// Refungible token can't be repartitioned by user who isn't owns all pieces68 RepartitionWhileNotOwningAllPieces,69 /// Refungible token can't nest other tokens70 RefungibleDisallowsNesting,71 /// Setting item properties is not allowed72 SettingPropertiesNotAllowed,73 }7475 #[pallet::config]76 pub trait Config:77 frame_system::Config + pallet_common::Config + pallet_structure::Config78 {79 type WeightInfo: WeightInfo;80 }8182 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8384 #[pallet::pallet]85 #[pallet::storage_version(STORAGE_VERSION)]86 #[pallet::generate_store(pub(super) trait Store)]87 pub struct Pallet<T>(_);8889 #[pallet::storage]90 pub type TokensMinted<T: Config> =91 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;92 #[pallet::storage]93 pub type TokensBurnt<T: Config> =94 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9596 #[pallet::storage]97 pub type TokenData<T: Config> = StorageNMap<98 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),99 Value = ItemData,100 QueryKind = ValueQuery,101 >;102103 #[pallet::storage]104 pub type TotalSupply<T: Config> = StorageNMap<105 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),106 Value = u128,107 QueryKind = ValueQuery,108 >;109110 /// Used to enumerate tokens owned by account111 #[pallet::storage]112 pub type Owned<T: Config> = StorageNMap<113 Key = (114 Key<Twox64Concat, CollectionId>,115 Key<Blake2_128Concat, T::CrossAccountId>,116 Key<Twox64Concat, TokenId>,117 ),118 Value = bool,119 QueryKind = ValueQuery,120 >;121122 #[pallet::storage]123 pub type AccountBalance<T: Config> = StorageNMap<124 Key = (125 Key<Twox64Concat, CollectionId>,126 // Owner127 Key<Blake2_128Concat, T::CrossAccountId>,128 ),129 Value = u32,130 QueryKind = ValueQuery,131 >;132133 #[pallet::storage]134 pub type Balance<T: Config> = StorageNMap<135 Key = (136 Key<Twox64Concat, CollectionId>,137 Key<Twox64Concat, TokenId>,138 // Owner139 Key<Blake2_128Concat, T::CrossAccountId>,140 ),141 Value = u128,142 QueryKind = ValueQuery,143 >;144145 #[pallet::storage]146 pub type Allowance<T: Config> = StorageNMap<147 Key = (148 Key<Twox64Concat, CollectionId>,149 Key<Twox64Concat, TokenId>,150 // Owner151 Key<Blake2_128, T::CrossAccountId>,152 // Spender153 Key<Blake2_128Concat, T::CrossAccountId>,154 ),155 Value = u128,156 QueryKind = ValueQuery,157 >;158159 #[pallet::hooks]160 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {161 fn on_runtime_upgrade() -> Weight {162 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {163 <TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {164 Some(<ItemDataVersion2>::from(v))165 })166 }167168 0169 }170 }171}172173pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);174impl<T: Config> RefungibleHandle<T> {175 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {176 Self(inner)177 }178 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {179 self.0180 }181}182impl<T: Config> Deref for RefungibleHandle<T> {183 type Target = pallet_common::CollectionHandle<T>;184185 fn deref(&self) -> &Self::Target {186 &self.0187 }188}189190impl<T: Config> Pallet<T> {191 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {192 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)193 }194 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {195 <TotalSupply<T>>::contains_key((collection.id, token))196 }197}198199// unchecked calls skips any permission checks200impl<T: Config> Pallet<T> {201 pub fn init_collection(202 owner: T::CrossAccountId,203 data: CreateCollectionData<T::AccountId>,204 ) -> Result<CollectionId, DispatchError> {205 <PalletCommon<T>>::init_collection(owner, data, false)206 }207 pub fn destroy_collection(208 collection: RefungibleHandle<T>,209 sender: &T::CrossAccountId,210 ) -> DispatchResult {211 let id = collection.id;212213 if Self::collection_has_tokens(id) {214 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());215 }216217 // =========218219 PalletCommon::destroy_collection(collection.0, sender)?;220221 <TokensMinted<T>>::remove(id);222 <TokensBurnt<T>>::remove(id);223 <TokenData<T>>::remove_prefix((id,), None);224 <TotalSupply<T>>::remove_prefix((id,), None);225 <Balance<T>>::remove_prefix((id,), None);226 <Allowance<T>>::remove_prefix((id,), None);227 <Owned<T>>::remove_prefix((id,), None);228 <AccountBalance<T>>::remove_prefix((id,), None);229 Ok(())230 }231232 fn collection_has_tokens(collection_id: CollectionId) -> bool {233 <TokenData<T>>::iter_prefix((collection_id,))234 .next()235 .is_some()236 }237238 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {239 let burnt = <TokensBurnt<T>>::get(collection.id)240 .checked_add(1)241 .ok_or(ArithmeticError::Overflow)?;242243 <TokensBurnt<T>>::insert(collection.id, burnt);244 <TokenData<T>>::remove((collection.id, token_id));245 <TotalSupply<T>>::remove((collection.id, token_id));246 <Balance<T>>::remove_prefix((collection.id, token_id), None);247 <Allowance<T>>::remove_prefix((collection.id, token_id), None);248 // TODO: ERC721 transfer event249 Ok(())250 }251252 pub fn burn(253 collection: &RefungibleHandle<T>,254 owner: &T::CrossAccountId,255 token: TokenId,256 amount: u128,257 ) -> DispatchResult {258 let total_supply = <TotalSupply<T>>::get((collection.id, token))259 .checked_sub(amount)260 .ok_or(<CommonError<T>>::TokenValueTooLow)?;261262 // This was probally last owner of this token?263 if total_supply == 0 {264 // Ensure user actually owns this amount265 ensure!(266 <Balance<T>>::get((collection.id, token, owner)) == amount,267 <CommonError<T>>::TokenValueTooLow268 );269 let account_balance = <AccountBalance<T>>::get((collection.id, owner))270 .checked_sub(1)271 // Should not occur272 .ok_or(ArithmeticError::Underflow)?;273274 // =========275276 <Owned<T>>::remove((collection.id, owner, token));277 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);278 <AccountBalance<T>>::insert((collection.id, owner), account_balance);279 Self::burn_token(collection, token)?;280 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(281 collection.id,282 token,283 owner.clone(),284 amount,285 ));286 return Ok(());287 }288289 let balance = <Balance<T>>::get((collection.id, token, owner))290 .checked_sub(amount)291 .ok_or(<CommonError<T>>::TokenValueTooLow)?;292 let account_balance = if balance == 0 {293 <AccountBalance<T>>::get((collection.id, owner))294 .checked_sub(1)295 // Should not occur296 .ok_or(ArithmeticError::Underflow)?297 } else {298 0299 };300301 // =========302303 if balance == 0 {304 <Owned<T>>::remove((collection.id, owner, token));305 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);306 <Balance<T>>::remove((collection.id, token, owner));307 <AccountBalance<T>>::insert((collection.id, owner), account_balance);308 } else {309 <Balance<T>>::insert((collection.id, token, owner), balance);310 }311 <TotalSupply<T>>::insert((collection.id, token), total_supply);312 // TODO: ERC20 transfer event313 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(314 collection.id,315 token,316 owner.clone(),317 amount,318 ));319 Ok(())320 }321322 pub fn transfer(323 collection: &RefungibleHandle<T>,324 from: &T::CrossAccountId,325 to: &T::CrossAccountId,326 token: TokenId,327 amount: u128,328 nesting_budget: &dyn Budget,329 ) -> DispatchResult {330 ensure!(331 collection.limits.transfers_enabled(),332 <CommonError<T>>::TransferNotAllowed333 );334335 if collection.permissions.access() == AccessMode::AllowList {336 collection.check_allowlist(from)?;337 collection.check_allowlist(to)?;338 }339 <PalletCommon<T>>::ensure_correct_receiver(to)?;340341 let balance_from = <Balance<T>>::get((collection.id, token, from))342 .checked_sub(amount)343 .ok_or(<CommonError<T>>::TokenValueTooLow)?;344 let mut create_target = false;345 let from_to_differ = from != to;346 let balance_to = if from != to {347 let old_balance = <Balance<T>>::get((collection.id, token, to));348 if old_balance == 0 {349 create_target = true;350 }351 Some(352 old_balance353 .checked_add(amount)354 .ok_or(ArithmeticError::Overflow)?,355 )356 } else {357 None358 };359360 let account_balance_from = if balance_from == 0 {361 Some(362 <AccountBalance<T>>::get((collection.id, from))363 .checked_sub(1)364 // Should not occur365 .ok_or(ArithmeticError::Underflow)?,366 )367 } else {368 None369 };370 // Account data is created in token, AccountBalance should be increased371 // But only if from != to as we shouldn't check overflow in this case372 let account_balance_to = if create_target && from_to_differ {373 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))374 .checked_add(1)375 .ok_or(ArithmeticError::Overflow)?;376 ensure!(377 account_balance_to < collection.limits.account_token_ownership_limit(),378 <CommonError<T>>::AccountTokenLimitExceeded,379 );380381 Some(account_balance_to)382 } else {383 None384 };385386 // =========387388 <PalletStructure<T>>::nest_if_sent_to_token(389 from.clone(),390 to,391 collection.id,392 token,393 nesting_budget,394 )?;395396 if let Some(balance_to) = balance_to {397 // from != to398 if balance_from == 0 {399 <Balance<T>>::remove((collection.id, token, from));400 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);401 } else {402 <Balance<T>>::insert((collection.id, token, from), balance_from);403 }404 <Balance<T>>::insert((collection.id, token, to), balance_to);405 if let Some(account_balance_from) = account_balance_from {406 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);407 <Owned<T>>::remove((collection.id, from, token));408 }409 if let Some(account_balance_to) = account_balance_to {410 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);411 <Owned<T>>::insert((collection.id, to, token), true);412 }413 }414415 // TODO: ERC20 transfer event416 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(417 collection.id,418 token,419 from.clone(),420 to.clone(),421 amount,422 ));423 Ok(())424 }425426 pub fn create_multiple_items(427 collection: &RefungibleHandle<T>,428 sender: &T::CrossAccountId,429 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,430 nesting_budget: &dyn Budget,431 ) -> DispatchResult {432 if !collection.is_owner_or_admin(sender) {433 ensure!(434 collection.permissions.mint_mode(),435 <CommonError<T>>::PublicMintingNotAllowed436 );437 collection.check_allowlist(sender)?;438439 for item in data.iter() {440 for user in item.users.keys() {441 collection.check_allowlist(user)?;442 }443 }444 }445446 for item in data.iter() {447 for (owner, _) in item.users.iter() {448 <PalletCommon<T>>::ensure_correct_receiver(owner)?;449 }450 }451452 // Total pieces per tokens453 let totals = data454 .iter()455 .map(|data| {456 Ok(data457 .users458 .iter()459 .map(|u| u.1)460 .try_fold(0u128, |acc, v| acc.checked_add(*v))461 .ok_or(ArithmeticError::Overflow)?)462 })463 .collect::<Result<Vec<_>, DispatchError>>()?;464 for total in &totals {465 ensure!(466 *total <= MAX_REFUNGIBLE_PIECES,467 <Error<T>>::WrongRefungiblePieces468 );469 }470471 let first_token_id = <TokensMinted<T>>::get(collection.id);472 let tokens_minted = first_token_id473 .checked_add(data.len() as u32)474 .ok_or(ArithmeticError::Overflow)?;475 ensure!(476 tokens_minted < collection.limits.token_limit(),477 <CommonError<T>>::CollectionTokenLimitExceeded478 );479480 let mut balances = BTreeMap::new();481 for data in &data {482 for owner in data.users.keys() {483 let balance = balances484 .entry(owner)485 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));486 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;487488 ensure!(489 *balance <= collection.limits.account_token_ownership_limit(),490 <CommonError<T>>::AccountTokenLimitExceeded,491 );492 }493 }494495 for (i, token) in data.iter().enumerate() {496 let token_id = TokenId(first_token_id + i as u32 + 1);497 for (to, _) in token.users.iter() {498 <PalletStructure<T>>::check_nesting(499 sender.clone(),500 to,501 collection.id,502 token_id,503 nesting_budget,504 )?;505 }506 }507508 // =========509510 <TokensMinted<T>>::insert(collection.id, tokens_minted);511 for (account, balance) in balances {512 <AccountBalance<T>>::insert((collection.id, account), balance);513 }514 for (i, token) in data.into_iter().enumerate() {515 let token_id = first_token_id + i as u32 + 1;516 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);517518 <TokenData<T>>::insert(519 (collection.id, token_id),520 ItemData {521 const_data: token.const_data,522 },523 );524525 for (user, amount) in token.users.into_iter() {526 if amount == 0 {527 continue;528 }529 <Balance<T>>::insert((collection.id, token_id, &user), amount);530 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);531 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(532 &user,533 collection.id,534 TokenId(token_id),535 );536537 // TODO: ERC20 transfer event538 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(539 collection.id,540 TokenId(token_id),541 user,542 amount,543 ));544 }545 }546 Ok(())547 }548549 pub fn set_allowance_unchecked(550 collection: &RefungibleHandle<T>,551 sender: &T::CrossAccountId,552 spender: &T::CrossAccountId,553 token: TokenId,554 amount: u128,555 ) {556 if amount == 0 {557 <Allowance<T>>::remove((collection.id, token, sender, spender));558 } else {559 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);560 }561 // TODO: ERC20 approval event562 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(563 collection.id,564 token,565 sender.clone(),566 spender.clone(),567 amount,568 ))569 }570571 pub fn set_allowance(572 collection: &RefungibleHandle<T>,573 sender: &T::CrossAccountId,574 spender: &T::CrossAccountId,575 token: TokenId,576 amount: u128,577 ) -> DispatchResult {578 if collection.permissions.access() == AccessMode::AllowList {579 collection.check_allowlist(sender)?;580 collection.check_allowlist(spender)?;581 }582583 <PalletCommon<T>>::ensure_correct_receiver(spender)?;584585 if <Balance<T>>::get((collection.id, token, sender)) < amount {586 ensure!(587 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),588 <CommonError<T>>::CantApproveMoreThanOwned589 );590 }591592 // =========593594 Self::set_allowance_unchecked(collection, sender, spender, token, amount);595 Ok(())596 }597598 /// Returns allowance, which should be set after transaction599 fn check_allowed(600 collection: &RefungibleHandle<T>,601 spender: &T::CrossAccountId,602 from: &T::CrossAccountId,603 token: TokenId,604 amount: u128,605 nesting_budget: &dyn Budget,606 ) -> Result<Option<u128>, DispatchError> {607 if spender.conv_eq(from) {608 return Ok(None);609 }610 if collection.permissions.access() == AccessMode::AllowList {611 // `from`, `to` checked in [`transfer`]612 collection.check_allowlist(spender)?;613 }614 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {615 // TODO: should collection owner be allowed to perform this transfer?616 ensure!(617 <PalletStructure<T>>::check_indirectly_owned(618 spender.clone(),619 source.0,620 source.1,621 None,622 nesting_budget623 )?,624 <CommonError<T>>::ApprovedValueTooLow,625 );626 return Ok(None);627 }628 let allowance =629 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);630 if allowance.is_none() {631 ensure!(632 collection.ignores_allowance(spender),633 <CommonError<T>>::ApprovedValueTooLow634 );635 }636 Ok(allowance)637 }638639 pub fn transfer_from(640 collection: &RefungibleHandle<T>,641 spender: &T::CrossAccountId,642 from: &T::CrossAccountId,643 to: &T::CrossAccountId,644 token: TokenId,645 amount: u128,646 nesting_budget: &dyn Budget,647 ) -> DispatchResult {648 let allowance =649 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;650651 // =========652653 Self::transfer(collection, from, to, token, amount, nesting_budget)?;654 if let Some(allowance) = allowance {655 Self::set_allowance_unchecked(collection, from, spender, token, allowance);656 }657 Ok(())658 }659660 pub fn burn_from(661 collection: &RefungibleHandle<T>,662 spender: &T::CrossAccountId,663 from: &T::CrossAccountId,664 token: TokenId,665 amount: u128,666 nesting_budget: &dyn Budget,667 ) -> DispatchResult {668 let allowance =669 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;670671 // =========672673 Self::burn(collection, from, token, amount)?;674 if let Some(allowance) = allowance {675 Self::set_allowance_unchecked(collection, from, spender, token, allowance);676 }677 Ok(())678 }679680 /// Delegated to `create_multiple_items`681 pub fn create_item(682 collection: &RefungibleHandle<T>,683 sender: &T::CrossAccountId,684 data: CreateRefungibleExData<T::CrossAccountId>,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)688 }689690 pub fn repartition(691 collection: &RefungibleHandle<T>,692 owner: &T::CrossAccountId,693 token: TokenId,694 amount: u128,695 ) -> DispatchResult {696 ensure!(697 amount <= MAX_REFUNGIBLE_PIECES,698 <Error<T>>::WrongRefungiblePieces699 );700 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);701 // Ensure user owns all pieces702 let total_supply = <TotalSupply<T>>::get((collection.id, token));703 let balance = <Balance<T>>::get((collection.id, token, owner));704 ensure!(705 total_supply == balance,706 <Error<T>>::RepartitionWhileNotOwningAllPieces707 );708709 <Balance<T>>::insert((collection.id, token, owner), amount);710 <TotalSupply<T>>::insert((collection.id, token), amount);711 Ok(())712 }713714 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {715 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()716 }717}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};26use pallet_structure::Pallet as PalletStructure;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};29use core::ops::Deref;30use codec::{Encode, Decode, MaxEncodedLen};31use scale_info::TypeInfo;3233pub use pallet::*;34#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod common;37pub mod erc;38pub mod weights;39pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4041/// Token data, stored independently from other data used to describe it.42/// Notably contains the token metadata.43#[struct_versioning::versioned(version = 2, upper)]44#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]45pub struct ItemData {46 pub const_data: BoundedVec<u8, CustomDataLimit>,4748 #[version(..2)]49 pub variable_data: BoundedVec<u8, CustomDataLimit>,50}5152#[frame_support::pallet]53pub mod pallet {54 use super::*;55 use frame_support::{56 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,57 traits::StorageVersion,58 };59 use frame_system::pallet_prelude::*;60 use up_data_structs::{CollectionId, TokenId};61 use super::weights::WeightInfo;6263 #[pallet::error]64 pub enum Error<T> {65 /// Not Refungible item data used to mint in Refungible collection.66 NotRefungibleDataUsedToMintFungibleCollectionToken,67 /// Maximum refungibility exceeded68 WrongRefungiblePieces,69 /// Refungible token can't be repartitioned by user who isn't owns all pieces70 RepartitionWhileNotOwningAllPieces,71 /// Refungible token can't nest other tokens72 RefungibleDisallowsNesting,73 /// Setting item properties is not allowed74 SettingPropertiesNotAllowed,75 }7677 #[pallet::config]78 pub trait Config:79 frame_system::Config + pallet_common::Config + pallet_structure::Config80 {81 type WeightInfo: WeightInfo;82 }8384 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8586 #[pallet::pallet]87 #[pallet::storage_version(STORAGE_VERSION)]88 #[pallet::generate_store(pub(super) trait Store)]89 pub struct Pallet<T>(_);9091 /// Total amount of minted tokens in a collection.92 #[pallet::storage]93 pub type TokensMinted<T: Config> =94 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;95 96 /// Amount of tokens burnt in a collection.97 #[pallet::storage]98 pub type TokensBurnt<T: Config> =99 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;100101 /// Token data, used to partially describe a token.102 #[pallet::storage]103 pub type TokenData<T: Config> = StorageNMap<104 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),105 Value = ItemData,106 QueryKind = ValueQuery,107 >;108109 /// Amount of pieces a refungible token is split into.110 #[pallet::storage]111 pub type TotalSupply<T: Config> = StorageNMap<112 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),113 Value = u128,114 QueryKind = ValueQuery,115 >;116117 /// Used to enumerate tokens owned by account.118 #[pallet::storage]119 pub type Owned<T: Config> = StorageNMap<120 Key = (121 Key<Twox64Concat, CollectionId>,122 Key<Blake2_128Concat, T::CrossAccountId>,123 Key<Twox64Concat, TokenId>,124 ),125 Value = bool,126 QueryKind = ValueQuery,127 >;128129 /// Amount of tokens (not pieces) partially owned by an account within a collection.130 #[pallet::storage]131 pub type AccountBalance<T: Config> = StorageNMap<132 Key = (133 Key<Twox64Concat, CollectionId>,134 // Owner135 Key<Blake2_128Concat, T::CrossAccountId>,136 ),137 Value = u32,138 QueryKind = ValueQuery,139 >;140141 /// Amount of pieces of a token owned by an account.142 #[pallet::storage]143 pub type Balance<T: Config> = StorageNMap<144 Key = (145 Key<Twox64Concat, CollectionId>,146 Key<Twox64Concat, TokenId>,147 // Owner148 Key<Blake2_128Concat, T::CrossAccountId>,149 ),150 Value = u128,151 QueryKind = ValueQuery,152 >;153154 /// todo:doc155 #[pallet::storage]156 pub type Allowance<T: Config> = StorageNMap<157 Key = (158 Key<Twox64Concat, CollectionId>,159 Key<Twox64Concat, TokenId>,160 // Owner161 Key<Blake2_128, T::CrossAccountId>,162 // Spender163 Key<Blake2_128Concat, T::CrossAccountId>,164 ),165 Value = u128,166 QueryKind = ValueQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 <TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {174 Some(<ItemDataVersion2>::from(v))175 })176 }177178 0179 }180 }181}182183pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);184impl<T: Config> RefungibleHandle<T> {185 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {186 Self(inner)187 }188 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189 self.0190 }191}192impl<T: Config> Deref for RefungibleHandle<T> {193 type Target = pallet_common::CollectionHandle<T>;194195 fn deref(&self) -> &Self::Target {196 &self.0197 }198}199200impl<T: Config> Pallet<T> {201 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {202 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)203 }204 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {205 <TotalSupply<T>>::contains_key((collection.id, token))206 }207}208209// unchecked calls skips any permission checks210impl<T: Config> Pallet<T> {211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, false)216 }217 pub fn destroy_collection(218 collection: RefungibleHandle<T>,219 sender: &T::CrossAccountId,220 ) -> DispatchResult {221 let id = collection.id;222223 if Self::collection_has_tokens(id) {224 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());225 }226227 // =========228229 PalletCommon::destroy_collection(collection.0, sender)?;230231 <TokensMinted<T>>::remove(id);232 <TokensBurnt<T>>::remove(id);233 <TokenData<T>>::remove_prefix((id,), None);234 <TotalSupply<T>>::remove_prefix((id,), None);235 <Balance<T>>::remove_prefix((id,), None);236 <Allowance<T>>::remove_prefix((id,), None);237 <Owned<T>>::remove_prefix((id,), None);238 <AccountBalance<T>>::remove_prefix((id,), None);239 Ok(())240 }241242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TokenData<T>>::iter_prefix((collection_id,))244 .next()245 .is_some()246 }247248 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {249 let burnt = <TokensBurnt<T>>::get(collection.id)250 .checked_add(1)251 .ok_or(ArithmeticError::Overflow)?;252253 <TokensBurnt<T>>::insert(collection.id, burnt);254 <TokenData<T>>::remove((collection.id, token_id));255 <TotalSupply<T>>::remove((collection.id, token_id));256 <Balance<T>>::remove_prefix((collection.id, token_id), None);257 <Allowance<T>>::remove_prefix((collection.id, token_id), None);258 // TODO: ERC721 transfer event259 Ok(())260 }261 262 pub fn burn(263 collection: &RefungibleHandle<T>,264 owner: &T::CrossAccountId,265 token: TokenId,266 amount: u128,267 ) -> DispatchResult {268 let total_supply = <TotalSupply<T>>::get((collection.id, token))269 .checked_sub(amount)270 .ok_or(<CommonError<T>>::TokenValueTooLow)?;271272 // This was probally last owner of this token?273 if total_supply == 0 {274 // Ensure user actually owns this amount275 ensure!(276 <Balance<T>>::get((collection.id, token, owner)) == amount,277 <CommonError<T>>::TokenValueTooLow278 );279 let account_balance = <AccountBalance<T>>::get((collection.id, owner))280 .checked_sub(1)281 // Should not occur282 .ok_or(ArithmeticError::Underflow)?;283284 // =========285286 <Owned<T>>::remove((collection.id, owner, token));287 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);288 <AccountBalance<T>>::insert((collection.id, owner), account_balance);289 Self::burn_token(collection, token)?;290 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(291 collection.id,292 token,293 owner.clone(),294 amount,295 ));296 return Ok(());297 }298299 let balance = <Balance<T>>::get((collection.id, token, owner))300 .checked_sub(amount)301 .ok_or(<CommonError<T>>::TokenValueTooLow)?;302 let account_balance = if balance == 0 {303 <AccountBalance<T>>::get((collection.id, owner))304 .checked_sub(1)305 // Should not occur306 .ok_or(ArithmeticError::Underflow)?307 } else {308 0309 };310311 // =========312313 if balance == 0 {314 <Owned<T>>::remove((collection.id, owner, token));315 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);316 <Balance<T>>::remove((collection.id, token, owner));317 <AccountBalance<T>>::insert((collection.id, owner), account_balance);318 } else {319 <Balance<T>>::insert((collection.id, token, owner), balance);320 }321 <TotalSupply<T>>::insert((collection.id, token), total_supply);322 // TODO: ERC20 transfer event323 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(324 collection.id,325 token,326 owner.clone(),327 amount,328 ));329 Ok(())330 }331332 pub fn transfer(333 collection: &RefungibleHandle<T>,334 from: &T::CrossAccountId,335 to: &T::CrossAccountId,336 token: TokenId,337 amount: u128,338 nesting_budget: &dyn Budget,339 ) -> DispatchResult {340 ensure!(341 collection.limits.transfers_enabled(),342 <CommonError<T>>::TransferNotAllowed343 );344345 if collection.permissions.access() == AccessMode::AllowList {346 collection.check_allowlist(from)?;347 collection.check_allowlist(to)?;348 }349 <PalletCommon<T>>::ensure_correct_receiver(to)?;350351 let balance_from = <Balance<T>>::get((collection.id, token, from))352 .checked_sub(amount)353 .ok_or(<CommonError<T>>::TokenValueTooLow)?;354 let mut create_target = false;355 let from_to_differ = from != to;356 let balance_to = if from != to {357 let old_balance = <Balance<T>>::get((collection.id, token, to));358 if old_balance == 0 {359 create_target = true;360 }361 Some(362 old_balance363 .checked_add(amount)364 .ok_or(ArithmeticError::Overflow)?,365 )366 } else {367 None368 };369370 let account_balance_from = if balance_from == 0 {371 Some(372 <AccountBalance<T>>::get((collection.id, from))373 .checked_sub(1)374 // Should not occur375 .ok_or(ArithmeticError::Underflow)?,376 )377 } else {378 None379 };380 // Account data is created in token, AccountBalance should be increased381 // But only if from != to as we shouldn't check overflow in this case382 let account_balance_to = if create_target && from_to_differ {383 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))384 .checked_add(1)385 .ok_or(ArithmeticError::Overflow)?;386 ensure!(387 account_balance_to < collection.limits.account_token_ownership_limit(),388 <CommonError<T>>::AccountTokenLimitExceeded,389 );390391 Some(account_balance_to)392 } else {393 None394 };395396 // =========397398 <PalletStructure<T>>::nest_if_sent_to_token(399 from.clone(),400 to,401 collection.id,402 token,403 nesting_budget,404 )?;405406 if let Some(balance_to) = balance_to {407 // from != to408 if balance_from == 0 {409 <Balance<T>>::remove((collection.id, token, from));410 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);411 } else {412 <Balance<T>>::insert((collection.id, token, from), balance_from);413 }414 <Balance<T>>::insert((collection.id, token, to), balance_to);415 if let Some(account_balance_from) = account_balance_from {416 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);417 <Owned<T>>::remove((collection.id, from, token));418 }419 if let Some(account_balance_to) = account_balance_to {420 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);421 <Owned<T>>::insert((collection.id, to, token), true);422 }423 }424425 // TODO: ERC20 transfer event426 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(427 collection.id,428 token,429 from.clone(),430 to.clone(),431 amount,432 ));433 Ok(())434 }435436 pub fn create_multiple_items(437 collection: &RefungibleHandle<T>,438 sender: &T::CrossAccountId,439 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,440 nesting_budget: &dyn Budget,441 ) -> DispatchResult {442 if !collection.is_owner_or_admin(sender) {443 ensure!(444 collection.permissions.mint_mode(),445 <CommonError<T>>::PublicMintingNotAllowed446 );447 collection.check_allowlist(sender)?;448449 for item in data.iter() {450 for user in item.users.keys() {451 collection.check_allowlist(user)?;452 }453 }454 }455456 for item in data.iter() {457 for (owner, _) in item.users.iter() {458 <PalletCommon<T>>::ensure_correct_receiver(owner)?;459 }460 }461462 // Total pieces per tokens463 let totals = data464 .iter()465 .map(|data| {466 Ok(data467 .users468 .iter()469 .map(|u| u.1)470 .try_fold(0u128, |acc, v| acc.checked_add(*v))471 .ok_or(ArithmeticError::Overflow)?)472 })473 .collect::<Result<Vec<_>, DispatchError>>()?;474 for total in &totals {475 ensure!(476 *total <= MAX_REFUNGIBLE_PIECES,477 <Error<T>>::WrongRefungiblePieces478 );479 }480481 let first_token_id = <TokensMinted<T>>::get(collection.id);482 let tokens_minted = first_token_id483 .checked_add(data.len() as u32)484 .ok_or(ArithmeticError::Overflow)?;485 ensure!(486 tokens_minted < collection.limits.token_limit(),487 <CommonError<T>>::CollectionTokenLimitExceeded488 );489490 let mut balances = BTreeMap::new();491 for data in &data {492 for owner in data.users.keys() {493 let balance = balances494 .entry(owner)495 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));496 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;497498 ensure!(499 *balance <= collection.limits.account_token_ownership_limit(),500 <CommonError<T>>::AccountTokenLimitExceeded,501 );502 }503 }504505 for (i, token) in data.iter().enumerate() {506 let token_id = TokenId(first_token_id + i as u32 + 1);507 for (to, _) in token.users.iter() {508 <PalletStructure<T>>::check_nesting(509 sender.clone(),510 to,511 collection.id,512 token_id,513 nesting_budget,514 )?;515 }516 }517518 // =========519520 <TokensMinted<T>>::insert(collection.id, tokens_minted);521 for (account, balance) in balances {522 <AccountBalance<T>>::insert((collection.id, account), balance);523 }524 for (i, token) in data.into_iter().enumerate() {525 let token_id = first_token_id + i as u32 + 1;526 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);527528 <TokenData<T>>::insert(529 (collection.id, token_id),530 ItemData {531 const_data: token.const_data,532 },533 );534535 for (user, amount) in token.users.into_iter() {536 if amount == 0 {537 continue;538 }539 <Balance<T>>::insert((collection.id, token_id, &user), amount);540 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);541 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(542 &user,543 collection.id,544 TokenId(token_id),545 );546547 // TODO: ERC20 transfer event548 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(549 collection.id,550 TokenId(token_id),551 user,552 amount,553 ));554 }555 }556 Ok(())557 }558559 pub fn set_allowance_unchecked(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 spender: &T::CrossAccountId,563 token: TokenId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, token, sender, spender));568 } else {569 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);570 }571 // TODO: ERC20 approval event572 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(573 collection.id,574 token,575 sender.clone(),576 spender.clone(),577 amount,578 ))579 }580581 pub fn set_allowance(582 collection: &RefungibleHandle<T>,583 sender: &T::CrossAccountId,584 spender: &T::CrossAccountId,585 token: TokenId,586 amount: u128,587 ) -> DispatchResult {588 if collection.permissions.access() == AccessMode::AllowList {589 collection.check_allowlist(sender)?;590 collection.check_allowlist(spender)?;591 }592593 <PalletCommon<T>>::ensure_correct_receiver(spender)?;594595 if <Balance<T>>::get((collection.id, token, sender)) < amount {596 ensure!(597 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),598 <CommonError<T>>::CantApproveMoreThanOwned599 );600 }601602 // =========603604 Self::set_allowance_unchecked(collection, sender, spender, token, amount);605 Ok(())606 }607608 /// todo:doc oh look, a precedent. not pub, too. but it has an unclear use-case.609 /// Returns allowance, which should be set after transaction610 fn check_allowed(611 collection: &RefungibleHandle<T>,612 spender: &T::CrossAccountId,613 from: &T::CrossAccountId,614 token: TokenId,615 amount: u128,616 nesting_budget: &dyn Budget,617 ) -> Result<Option<u128>, DispatchError> {618 if spender.conv_eq(from) {619 return Ok(None);620 }621 if collection.permissions.access() == AccessMode::AllowList {622 // `from`, `to` checked in [`transfer`]623 collection.check_allowlist(spender)?;624 }625 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {626 // TODO: should collection owner be allowed to perform this transfer?627 ensure!(628 <PalletStructure<T>>::check_indirectly_owned(629 spender.clone(),630 source.0,631 source.1,632 None,633 nesting_budget634 )?,635 <CommonError<T>>::ApprovedValueTooLow,636 );637 return Ok(None);638 }639 let allowance =640 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);641 if allowance.is_none() {642 ensure!(643 collection.ignores_allowance(spender),644 <CommonError<T>>::ApprovedValueTooLow645 );646 }647 Ok(allowance)648 }649650 pub fn transfer_from(651 collection: &RefungibleHandle<T>,652 spender: &T::CrossAccountId,653 from: &T::CrossAccountId,654 to: &T::CrossAccountId,655 token: TokenId,656 amount: u128,657 nesting_budget: &dyn Budget,658 ) -> DispatchResult {659 let allowance =660 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;661662 // =========663664 Self::transfer(collection, from, to, token, amount, nesting_budget)?;665 if let Some(allowance) = allowance {666 Self::set_allowance_unchecked(collection, from, spender, token, allowance);667 }668 Ok(())669 }670671 pub fn burn_from(672 collection: &RefungibleHandle<T>,673 spender: &T::CrossAccountId,674 from: &T::CrossAccountId,675 token: TokenId,676 amount: u128,677 nesting_budget: &dyn Budget,678 ) -> DispatchResult {679 let allowance =680 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;681682 // =========683684 Self::burn(collection, from, token, amount)?;685 if let Some(allowance) = allowance {686 Self::set_allowance_unchecked(collection, from, spender, token, allowance);687 }688 Ok(())689 }690691 /// Delegated to `create_multiple_items`692 pub fn create_item(693 collection: &RefungibleHandle<T>,694 sender: &T::CrossAccountId,695 data: CreateRefungibleExData<T::CrossAccountId>,696 nesting_budget: &dyn Budget,697 ) -> DispatchResult {698 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)699 }700701 pub fn repartition(702 collection: &RefungibleHandle<T>,703 owner: &T::CrossAccountId,704 token: TokenId,705 amount: u128,706 ) -> DispatchResult {707 ensure!(708 amount <= MAX_REFUNGIBLE_PIECES,709 <Error<T>>::WrongRefungiblePieces710 );711 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);712 // Ensure user owns all pieces713 let total_supply = <TotalSupply<T>>::get((collection.id, token));714 let balance = <Balance<T>>::get((collection.id, token, owner));715 ensure!(716 total_supply == balance,717 <Error<T>>::RepartitionWhileNotOwningAllPieces718 );719720 <Balance<T>>::insert((collection.id, token, owner), amount);721 <TotalSupply<T>>::insert((collection.id, token), amount);722 Ok(())723 }724725 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {726 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()727 }728}pallets/scheduler/src/lib.rsdiffbeforeafterboth--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -258,6 +258,7 @@
/// A Scheduler-Runtime interface for finer payment handling.
pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {
+ /// Reserve the maximum spendings on a call.
fn reserve_balance(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -265,6 +266,7 @@
count: u32,
) -> Result<(), DispatchError>;
+ /// Pay for call dispatch (un-reserve) from the reserved funds, returning the change.
fn pay_for_call(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -280,6 +282,7 @@
TransactionValidityError,
>;
+ /// Release reserved funds.
fn cancel_reserve(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -25,19 +25,19 @@
#[pallet::error]
pub enum Error<T> {
- /// While searched for owner, got already checked account
+ /// While searching for the owner, encountered an already checked account, detecting a loop.
OuroborosDetected,
- /// While searched for owner, encountered depth limit
+ /// While searching for the owner, reached the depth limit.
DepthLimit,
- /// While iterating over children, encountered breadth limit
+ /// While iterating over children, reached the breadth limit.
BreadthLimit,
- /// While searched for owner, found token owner by not-yet-existing token
+ /// Couldn't find the token owner that is a token. Perhaps, it does not yet exist. todo:doc? rephrase?
TokenNotFound,
}
#[pallet::event]
pub enum Event<T> {
- /// Executed call on behalf of token
+ /// Executed call on behalf of the token.
Executed(DispatchResult),
}
@@ -73,11 +73,11 @@
#[derive(PartialEq)]
pub enum Parent<CrossAccountId> {
- /// Token owned by normal account
+ /// Token owned by a normal account.
User(CrossAccountId),
- /// Passed token not found
+ /// Could not find the token provided as the owner.
TokenNotFound,
- /// Token owner is another token (target token still may not exist)
+ /// Token owner is another token (still, the target token may not exist).
Token(CollectionId, TokenId),
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -101,7 +101,7 @@
/// * admin: Admin address.
CollectionAdminAdded(CollectionId, CrossAccountId),
- /// Collection owned was change
+ /// Collection owned was changed
///
/// # Arguments
///
@@ -137,7 +137,7 @@
/// * admin: Admin address.
CollectionAdminRemoved(CollectionId, CrossAccountId),
- /// Address was remove from allow list
+ /// Address was removed from the allow list
///
/// # Arguments
///
@@ -146,7 +146,7 @@
/// * user: Address.
AllowListAddressRemoved(CollectionId, CrossAccountId),
- /// Address was add to allow list
+ /// Address was added to the allow list
///
/// # Arguments
///
@@ -155,13 +155,18 @@
/// * user: Address.
AllowListAddressAdded(CollectionId, CrossAccountId),
- /// Collection limits was set
+ /// Collection limits were set
///
/// # Arguments
///
/// * collection_id: Globally unique collection identifier.
CollectionLimitSet(CollectionId),
+ /// Collection permissions were set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
CollectionPermissionSet(CollectionId),
}
}
@@ -198,7 +203,7 @@
ChainVersion: u64;
//#endregion
- //#region Tokens transfer rate limit baskets
+ //#region Tokens transfer sponosoring rate limit baskets
/// (Collection id (controlled?2), who created (real))
/// TODO: Off chain worker should remove from this map when collection gets removed
pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;
@@ -214,11 +219,14 @@
/// Collection id (controlled?2), token id (controlled?2)
#[deprecated]
pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+ /// Last sponsoring of token property setting // todo:doc rephrase this and the following
pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
- /// Approval sponsoring
+ /// Last sponsoring of NFT approval in a collection
pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+ /// Last sponsoring of fungible tokens approval in a collection
pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
+ /// Last sponsoring of RFT approval in a collection
pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
}
}
@@ -278,9 +286,16 @@
Self::create_collection_ex(origin, data)
}
- /// This method creates a collection
+ /// Create a collection with explicit parameters.
+ /// Prefer it to the deprecated [`created_collection`] method.
+ ///
+ /// # Permissions
+ ///
+ /// * Anyone.
///
- /// Prefer it to deprecated [`created_collection`] method
+ /// # Arguments
+ ///
+ /// * data: explicit create-collection data.
#[weight = <SelfWeightOf<T>>::create_collection()]
#[transactional]
pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
@@ -293,11 +308,11 @@
Ok(())
}
- /// Destroys collection if no tokens within this collection
+ /// Destroy the collection if no tokens exist within.
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -398,7 +413,7 @@
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -424,40 +439,40 @@
target_collection.save()
}
- /// Adds an admin of the Collection.
+ /// Adds an admin of the collection.
/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
/// * collection_id: ID of the Collection to add admin for.
///
- /// * new_admin_id: Address of new admin to add.
+ /// * new_admin: Address of new admin to add.
#[weight = <SelfWeightOf<T>>::add_collection_admin()]
#[transactional]
- pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
+ pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
collection_id,
- new_admin_id.clone()
+ new_admin.clone()
));
- <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)
}
/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
@@ -479,9 +494,12 @@
<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
}
+ /// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
+ ///
/// # Permissions
///
/// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
@@ -507,9 +525,11 @@
target_collection.save()
}
+ /// Confirm own sponsorship of a collection.
+ ///
/// # Permissions
///
- /// * Sponsor.
+ /// * The sponsor to-be
///
/// # Arguments
///
@@ -538,7 +558,7 @@
///
/// # Permissions
///
- /// * Collection owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -560,12 +580,12 @@
target_collection.save()
}
- /// This method creates a concrete instance of NFT Collection created with CreateCollection method.
+ /// Create a concrete instance of NFT Collection created with CreateCollection method.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
/// * Anyone if
/// * Allow List is enabled, and
/// * Address is added to allow list, and
@@ -587,12 +607,12 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))
}
- /// This method creates multiple items in a collection created with CreateCollection method.
+ /// Create multiple items in a collection created with CreateCollection method.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
/// * Anyone if
/// * Allow List is enabled, and
/// * Address is added to allow list, and
@@ -615,6 +635,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))
}
+ /// Add or change collection properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
#[transactional]
pub fn set_collection_properties(
@@ -629,6 +661,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))
}
+ /// Delete specified collection properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * property_keys: a vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_collection_properties(
@@ -643,6 +687,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))
}
+ /// Add or change token properties according to collection's permissions.
+ ///
+ /// # Permissions
+ ///
+ /// * Depends on collection's token property permissions and specified property mutability:
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Token Owner
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token_id.
+ ///
+ /// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
#[transactional]
pub fn set_token_properties(
@@ -659,6 +719,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))
}
+ /// Delete specified token properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Depends on collection's token property permissions and specified property mutability:
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Token Owner
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token_id.
+ ///
+ /// * property_keys: a vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_token_properties(
@@ -675,6 +751,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))
}
+ /// Add or change token property permissions of a collection.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]
#[transactional]
pub fn set_token_property_permissions(
@@ -689,6 +777,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))
}
+ /// Create multiple items inside a collection with explicitly specified initial parameters.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Anyone if
+ /// * Allow List is enabled, and
+ /// * Address is added to allow list, and
+ /// * MintPermission is enabled (see SetMintPermission method)
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection.
+ ///
+ /// * data: explicit item creation data.
#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
#[transactional]
pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
@@ -698,11 +802,11 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))
}
- /// Set transfers_enabled value for particular collection
+ /// Set transfers_enabled value for particular collection.
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -723,13 +827,13 @@
target_collection.save()
}
- /// Destroys a concrete instance of NFT.
+ /// Destroy a concrete instance of NFT.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
- /// * Current NFT Owner.
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Current NFT Owner
///
/// # Arguments
///
@@ -752,7 +856,7 @@
Ok(post_info)
}
- /// Destroys a concrete instance of NFT on behalf of the owner
+ /// Destroy a concrete instance of NFT on behalf of the owner.
/// See also: [`approve`]
///
/// # Permissions
@@ -835,6 +939,7 @@
/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
///
/// # Permissions
+ ///
/// * Collection Owner
/// * Collection Admin
/// * Current NFT owner
@@ -860,6 +965,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
}
+ /// Set specific limits of a collection. Empty, or None fields mean chain default.
+ ///.
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * new_limit: The new limits of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_limits(
@@ -882,12 +999,24 @@
target_collection.save()
}
+ /// Set specific permissions of a collection. Empty, or None fields mean chain default.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * new_permission: The new permissions of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_permissions(
origin,
collection_id: CollectionId,
- new_limit: CollectionPermissions,
+ new_permission: CollectionPermissions,
) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -895,7 +1024,7 @@
target_collection.check_is_owner_or_admin(&sender)?;
let old_limit = &target_collection.permissions;
- target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;
+ target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(
collection_id
@@ -904,6 +1033,19 @@
target_collection.save()
}
+ /// Re-partition a refungible token, while owning all of its parts.
+ ///
+ /// # Permissions
+ ///
+ /// * Token Owner (must own every part)
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token: the ID of the RFT.
+ ///
+ /// * amount: The new number of parts into which the token shall be partitioned.
#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
#[transactional]
pub fn repartition(
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -197,7 +197,6 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum CollectionMode {
NFT,
- // decimal points
Fungible(DecimalPoints),
ReFungible,
}
@@ -252,12 +251,14 @@
pub enum SponsorshipState<AccountId> {
/// The fees are applied to the transaction sender
Disabled,
+ /// Pending confirmation from a sponsor-to-be
Unconfirmed(AccountId),
/// Transactions are sponsored by specified account
Confirmed(AccountId),
}
impl<AccountId> SponsorshipState<AccountId> {
+ /// Get the acting sponsor account, if present
pub fn sponsor(&self) -> Option<&AccountId> {
match self {
Self::Confirmed(sponsor) => Some(sponsor),
@@ -265,6 +266,7 @@
}
}
+ /// Get the sponsor account currently pending confirmation, if present
pub fn pending_sponsor(&self) -> Option<&AccountId> {
match self {
Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),
@@ -272,6 +274,7 @@
}
}
+ /// Is sponsorship set and acting
pub fn confirmed(&self) -> bool {
matches!(self, Self::Confirmed(_))
}
@@ -283,7 +286,7 @@
}
}
-/// Used in storage
+/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version)
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
pub struct Collection<AccountId> {
@@ -324,7 +327,7 @@
pub meta_update_permission: MetaUpdatePermission,
}
-/// Used in RPC calls
+/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version)
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct RpcCollection<AccountId> {
@@ -362,12 +365,15 @@
pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
-/// All fields are wrapped in `Option`s, where None means chain default
+/// Limits and restrictions of a collection.
+/// All fields are wrapped in `Option`s, where None means chain default.
// When adding/removing fields from this struct - don't forget to also update clamp_limits
#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CollectionLimits {
+ /// Maximum number of owned tokens per account
pub account_token_ownership_limit: Option<u32>,
+ /// Maximum size of data of a sponsored transaction
pub sponsored_data_size: Option<u32>,
/// FIXME should we delete this or repurpose it?
@@ -375,13 +381,18 @@
/// Some(v) - setVariableMetadata is sponsored
/// if there is v block between txs
pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,
+ /// Maximum amount of tokens inside the collection
pub token_limit: Option<u32>,
- // Timeouts for item types in passed blocks
+ /// Timeout for sponsoring a token transfer in passed blocks
pub sponsor_transfer_timeout: Option<u32>,
+ /// Timeout for sponsoring an approval in passed blocks
pub sponsor_approve_timeout: Option<u32>,
+ /// Can a token be transferred by the owner
pub owner_can_transfer: Option<bool>,
+ /// Can a token be burned by the owner
pub owner_can_destroy: Option<bool>,
+ /// Can a token be transferred at all
pub transfers_enabled: Option<bool>,
}
@@ -509,6 +520,7 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum SponsoringRateLimit {
SponsoringDisabled,
+ /// Once per how many blocks can sponsorship of a transaction type occur
Blocks(u32),
}
@@ -516,6 +528,7 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derivative(Debug)]
pub struct CreateNftData {
+ /// Key-value pairs used to describe the token as metadata
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub properties: CollectionPropertiesVec,
@@ -524,6 +537,7 @@
#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CreateFungibleData {
+ /// Number of fungible tokens minted
pub value: u128,
}
@@ -534,6 +548,7 @@
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
+ /// Number of pieces the RFT is split into
pub pieces: u128,
}
@@ -553,6 +568,7 @@
ReFungible(CreateReFungibleData),
}
+/// Explicit NFT creation data with meta parameters
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug)]
pub struct CreateNftExData<CrossAccountId> {
@@ -561,6 +577,7 @@
pub owner: CrossAccountId,
}
+/// Explicit RFT creation data with meta parameters
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub struct CreateRefungibleExData<CrossAccountId> {
@@ -570,6 +587,7 @@
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
}
+/// Explicit item creation data with meta parameters, namely the owner
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub enum CreateItemExData<CrossAccountId> {
@@ -617,6 +635,7 @@
}
}
+/// Token's address, dictated by its collection and token IDs
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
// todo possibly rename to be used generally as an address pair
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -43,13 +43,13 @@
accountTokens: fun('Get tokens owned by account', [collectionParam, crossAccountParam()], 'Vec<u32>'),
collectionTokens: fun('Get tokens contained in collection', [collectionParam], 'Vec<u32>'),
- lastTokenId: fun('Get last token id', [collectionParam], 'u32'),
+ lastTokenId: fun('Get last token ID created in a collection', [collectionParam], 'u32'),
totalSupply: fun('Get amount of unique collection tokens', [collectionParam], 'u32'),
- accountBalance: fun('Get amount of different user tokens', [collectionParam, crossAccountParam()], 'u32'),
- balance: fun('Get amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
+ accountBalance: fun('Get owned amount of any user tokens', [collectionParam, crossAccountParam()], 'u32'),
+ balance: fun('Get owned amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
- topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
+ topmostTokenOwner: fun('Get token owner, in case of nested token - find the parent recursively', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
@@ -74,7 +74,7 @@
'UpDataStructsTokenData',
),
tokenExists: fun('Check if token exists', [collectionParam, tokenParam], 'bool'),
- collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
+ collectionById: fun('Get collection by specified ID', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
nextSponsored: fun('Get number of blocks when sponsored transaction is available', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),