difftreelog
feat track and limit admin amount
in: master
3 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -11,7 +11,8 @@
use nft_data_structs::{
COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
- MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,
+ COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,
+ WithdrawReasons,
};
pub use pallet::*;
use sp_core::H160;
@@ -165,6 +166,13 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ #[pallet::extra_constants]
+ impl<T: Config> Pallet<T> {
+ pub fn collection_admins_limit() -> u32 {
+ COLLECTION_ADMINS_LIMIT
+ }
+ }
+
#[pallet::event]
#[pallet::generate_deposit(pub fn deposit_event)]
pub enum Event<T: Config> {
@@ -265,6 +273,8 @@
TotalCollectionsLimitExceeded,
/// variable_data exceeded data limit.
TokenVariableDataLimitExceeded,
+ /// Exceeded max admin amount
+ CollectionAdminAmountExceeded,
/// Collection settings not allowing items transferring
TransferNotAllowed,
@@ -305,6 +315,14 @@
QueryKind = OptionQuery,
>;
+ #[pallet::storage]
+ pub type AdminAmount<T> = StorageMap<
+ Hasher = Blake2_128Concat,
+ Key = CollectionId,
+ Value = u32,
+ QueryKind = ValueQuery,
+ >;
+
/// List of collection admins
#[pallet::storage]
pub type IsAdmin<T: Config> = StorageNMap<
@@ -419,6 +437,7 @@
<DestroyedCollectionCount<T>>::put(destroyed_collections);
<CollectionById<T>>::remove(collection.id);
+ <AdminAmount<T>>::remove(collection.id);
<IsAdmin<T>>::remove_prefix((collection.id,), None);
<Allowlist<T>>::remove_prefix((collection.id,), None);
Ok(())
@@ -435,9 +454,44 @@
// =========
if allowed {
- <Allowlist<T>>::insert((collection.id, user.as_sub()), true);
+ <Allowlist<T>>::insert((collection.id, user), true);
+ } else {
+ <Allowlist<T>>::remove((collection.id, user));
+ }
+
+ Ok(())
+ }
+
+ pub fn toggle_admin(
+ collection: &CollectionHandle<T>,
+ sender: &T::CrossAccountId,
+ user: &T::CrossAccountId,
+ admin: bool,
+ ) -> DispatchResult {
+ collection.check_is_owner_or_admin(&sender)?;
+
+ let was_admin = <IsAdmin<T>>::get((collection.id, user));
+ if was_admin == admin {
+ return Ok(());
+ }
+ let amount = <AdminAmount<T>>::get(collection.id);
+
+ if admin {
+ let amount = amount
+ .checked_add(1)
+ .ok_or(<Error<T>>::CollectionAdminAmountExceeded)?;
+ ensure!(
+ amount <= Self::collection_admins_limit(),
+ <Error<T>>::CollectionAdminAmountExceeded,
+ );
+
+ // =========
+
+ <AdminAmount<T>>::insert(collection.id, amount);
+ <IsAdmin<T>>::insert((collection.id, user), true);
} else {
- <Allowlist<T>>::remove((collection.id, user.as_sub()));
+ <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));
+ <IsAdmin<T>>::remove((collection.id, user));
}
Ok(())
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -36,8 +36,8 @@
use sp_runtime::{sp_std::prelude::Vec};
use nft_data_structs::{
MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
- VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,
- OFFCHAIN_SCHEMA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
+ FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,
CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
};
@@ -156,7 +156,6 @@
where
origin: T::Origin
{
- const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;
type Error = Error<T>;
fn on_initialize(_now: T::BlockNumber) -> Weight {
@@ -406,12 +405,9 @@
#[transactional]
pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_owner_or_admin(&sender)?;
- <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);
- Ok(())
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, 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.
@@ -430,12 +426,9 @@
#[transactional]
pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_owner_or_admin(&sender)?;
- <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));
- Ok(())
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
}
/// # Permissions
primitives/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use core::convert::{TryFrom, TryInto};45#[cfg(feature = "serde")]6pub use serde::{Serialize, Deserialize};78use sp_core::U256;9use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};10use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};11pub use frame_support::{12 BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,13 dispatch::DispatchResult,14 ensure, fail, parameter_types,15 traits::{16 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,17 Randomness, IsSubType, WithdrawReasons,18 },19 weights::{20 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},21 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,22 WeightToFeePolynomial, DispatchClass,23 },24 StorageValue, transactional,25};26use derivative::Derivative;27use scale_info::TypeInfo;2829pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;30pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;31pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;32pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;3334pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {35 10000036} else {37 1038};39pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {40 204841} else {42 1043};44pub const COLLECTION_ADMINS_LIMIT: u64 = 5;45pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;46pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {47 100000048} else {49 1050};5152// Timeouts for item types in passed blocks53pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;54pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;55pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5657// Schema limits58pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;59pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;60pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;6162pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;63pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;64pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6566/// How much items can be created per single67/// create_many call68pub const MAX_ITEMS_PER_BATCH: u32 = 200;6970parameter_types! {71 pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;72}7374#[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Default, TypeInfo)]75#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]76pub struct CollectionId(pub u32);77impl EncodeLike<u32> for CollectionId {}78impl EncodeLike<CollectionId> for u32 {}7980#[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Default, TypeInfo)]81#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]82pub struct TokenId(pub u32);83impl EncodeLike<u32> for TokenId {}84impl EncodeLike<TokenId> for u32 {}8586impl TokenId {87 pub fn try_next(self) -> Result<TokenId, ArithmeticError> {88 self.089 .checked_add(1)90 .ok_or(ArithmeticError::Overflow)91 .map(Self)92 }93}9495impl From<TokenId> for U256 {96 fn from(t: TokenId) -> Self {97 t.0.into()98 }99}100101impl TryFrom<U256> for TokenId {102 type Error = &'static str;103104 fn try_from(value: U256) -> Result<Self, Self::Error> {105 Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))106 }107}108109pub struct OverflowError;110impl From<OverflowError> for &'static str {111 fn from(_: OverflowError) -> Self {112 "overflow occured"113 }114}115116pub type DecimalPoints = u8;117118#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]119#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]120pub enum CollectionMode {121 NFT,122 // decimal points123 Fungible(DecimalPoints),124 ReFungible,125}126127impl CollectionMode {128 pub fn id(&self) -> u8 {129 match self {130 CollectionMode::NFT => 1,131 CollectionMode::Fungible(_) => 2,132 CollectionMode::ReFungible => 3,133 }134 }135}136137pub trait SponsoringResolve<AccountId, Call> {138 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;139}140141#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]142#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]143pub enum AccessMode {144 Normal,145 WhiteList,146}147impl Default for AccessMode {148 fn default() -> Self {149 Self::Normal150 }151}152153#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]154#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]155pub enum SchemaVersion {156 ImageURL,157 Unique,158}159impl Default for SchemaVersion {160 fn default() -> Self {161 Self::ImageURL162 }163}164165#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]166#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]167pub struct Ownership<AccountId> {168 pub owner: AccountId,169 pub fraction: u128,170}171172#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]173#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]174pub enum SponsorshipState<AccountId> {175 /// The fees are applied to the transaction sender176 Disabled,177 Unconfirmed(AccountId),178 /// Transactions are sponsored by specified account179 Confirmed(AccountId),180}181182impl<AccountId> SponsorshipState<AccountId> {183 pub fn sponsor(&self) -> Option<&AccountId> {184 match self {185 Self::Confirmed(sponsor) => Some(sponsor),186 _ => None,187 }188 }189190 pub fn pending_sponsor(&self) -> Option<&AccountId> {191 match self {192 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),193 _ => None,194 }195 }196197 pub fn confirmed(&self) -> bool {198 matches!(self, Self::Confirmed(_))199 }200}201202impl<T> Default for SponsorshipState<T> {203 fn default() -> Self {204 Self::Disabled205 }206}207208#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]209#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]210pub struct Collection<T: frame_system::Config> {211 pub owner: T::AccountId,212 pub mode: CollectionMode,213 pub access: AccessMode,214 pub name: Vec<u16>, // 64 include null escape char215 pub description: Vec<u16>, // 256 include null escape char216 pub token_prefix: Vec<u8>, // 16 include null escape char217 pub mint_mode: bool,218 pub offchain_schema: Vec<u8>,219 pub schema_version: SchemaVersion,220 pub sponsorship: SponsorshipState<T::AccountId>,221 pub limits: CollectionLimits, // Collection private restrictions222 pub variable_on_chain_schema: Vec<u8>, //223 pub const_on_chain_schema: Vec<u8>, //224 pub meta_update_permission: MetaUpdatePermission,225}226227#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]228#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]229pub struct NftItemType<AccountId> {230 pub owner: AccountId,231 pub const_data: Vec<u8>,232 pub variable_data: Vec<u8>,233}234235#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]236#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]237pub struct FungibleItemType {238 pub value: u128,239}240241#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]242#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]243pub struct ReFungibleItemType<AccountId> {244 pub owner: Vec<Ownership<AccountId>>,245 pub const_data: Vec<u8>,246 pub variable_data: Vec<u8>,247}248249#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)]250#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]251pub struct CollectionLimits {252 pub account_token_ownership_limit: Option<u32>,253 pub sponsored_data_size: Option<u32>,254 /// None - setVariableMetadata is not sponsored255 /// Some(v) - setVariableMetadata is sponsored256 /// if there is v block between txs257 pub sponsored_data_rate_limit: Option<u32>,258 pub token_limit: Option<u32>,259260 // Timeouts for item types in passed blocks261 pub sponsor_transfer_timeout: Option<u32>,262 pub owner_can_transfer: Option<bool>,263 pub owner_can_destroy: Option<bool>,264 pub transfers_enabled: Option<bool>,265}266267impl CollectionLimits {268 pub fn account_token_ownership_limit(&self) -> u32 {269 self.account_token_ownership_limit270 .unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)271 .min(MAX_TOKEN_OWNERSHIP)272 }273 pub fn sponsored_data_size(&self) -> u32 {274 self.sponsored_data_size275 .unwrap_or(CUSTOM_DATA_LIMIT)276 .min(CUSTOM_DATA_LIMIT)277 }278 pub fn token_limit(&self) -> u32 {279 self.token_limit280 .unwrap_or(COLLECTION_TOKEN_LIMIT)281 .min(COLLECTION_TOKEN_LIMIT)282 }283 pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {284 self.sponsor_transfer_timeout285 .unwrap_or(default)286 .min(MAX_SPONSOR_TIMEOUT)287 }288 pub fn owner_can_transfer(&self) -> bool {289 self.owner_can_transfer.unwrap_or(true)290 }291 pub fn owner_can_destroy(&self) -> bool {292 self.owner_can_destroy.unwrap_or(true)293 }294 pub fn transfers_enabled(&self) -> bool {295 self.transfers_enabled.unwrap_or(true)296 }297 pub fn sponsored_data_rate_limit(&self) -> Option<u32> {298 self.sponsored_data_rate_limit299 .map(|v| v.min(MAX_SPONSOR_TIMEOUT))300 }301}302303/// BoundedVec doesn't supports serde304#[cfg(feature = "serde1")]305mod bounded_serde {306 use core::convert::TryFrom;307 use frame_support::{BoundedVec, traits::Get};308 use serde::{309 ser::{self, Serialize},310 de::{self, Deserialize, Error},311 };312 use sp_std::vec::Vec;313314 pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>315 where316 D: ser::Serializer,317 V: Serialize,318 {319 let vec: &Vec<_> = &value;320 vec.serialize(serializer)321 }322323 pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>324 where325 D: de::Deserializer<'de>,326 V: de::Deserialize<'de>,327 S: Get<u32>,328 {329 // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?330 let vec = <Vec<V>>::deserialize(deserializer)?;331 let len = vec.len();332 TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))333 }334}335336#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]337#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]338#[derivative(Debug)]339pub struct CreateNftData {340 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]341 #[derivative(Debug = "ignore")]342 pub const_data: BoundedVec<u8, CustomDataLimit>,343 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]344 #[derivative(Debug = "ignore")]345 pub variable_data: BoundedVec<u8, CustomDataLimit>,346}347348#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]349#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]350pub struct CreateFungibleData {351 pub value: u128,352}353354#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]355#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]356#[derivative(Debug)]357pub struct CreateReFungibleData {358 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]359 #[derivative(Debug = "ignore")]360 pub const_data: BoundedVec<u8, CustomDataLimit>,361 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]362 #[derivative(Debug = "ignore")]363 pub variable_data: BoundedVec<u8, CustomDataLimit>,364 pub pieces: u128,365}366367#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]368#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]369pub enum MetaUpdatePermission {370 ItemOwner,371 Admin,372 None,373}374375impl Default for MetaUpdatePermission {376 fn default() -> Self {377 Self::ItemOwner378 }379}380381#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]382#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]383pub enum CreateItemData {384 NFT(CreateNftData),385 Fungible(CreateFungibleData),386 ReFungible(CreateReFungibleData),387}388389impl CreateItemData {390 pub fn data_size(&self) -> usize {391 match self {392 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),393 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),394 _ => 0,395 }396 }397}398399impl From<CreateNftData> for CreateItemData {400 fn from(item: CreateNftData) -> Self {401 CreateItemData::NFT(item)402 }403}404405impl From<CreateReFungibleData> for CreateItemData {406 fn from(item: CreateReFungibleData) -> Self {407 CreateItemData::ReFungible(item)408 }409}410411impl From<CreateFungibleData> for CreateItemData {412 fn from(item: CreateFungibleData) -> Self {413 CreateItemData::Fungible(item)414 }415}1#![cfg_attr(not(feature = "std"), no_std)]23use core::convert::{TryFrom, TryInto};45#[cfg(feature = "serde")]6pub use serde::{Serialize, Deserialize};78use sp_core::U256;9use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};10use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};11pub use frame_support::{12 BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,13 dispatch::DispatchResult,14 ensure, fail, parameter_types,15 traits::{16 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,17 Randomness, IsSubType, WithdrawReasons,18 },19 weights::{20 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},21 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,22 WeightToFeePolynomial, DispatchClass,23 },24 StorageValue, transactional,25};26use derivative::Derivative;27use scale_info::TypeInfo;2829pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;30pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;31pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;32pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;3334pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {35 10000036} else {37 1038};39pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {40 204841} else {42 1043};44pub const COLLECTION_ADMINS_LIMIT: u32 = 5;45pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;46pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {47 100000048} else {49 1050};5152// Timeouts for item types in passed blocks53pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;54pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;55pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5657// Schema limits58pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;59pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;60pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;6162pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;63pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;64pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6566/// How much items can be created per single67/// create_many call68pub const MAX_ITEMS_PER_BATCH: u32 = 200;6970parameter_types! {71 pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;72}7374#[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Default, TypeInfo)]75#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]76pub struct CollectionId(pub u32);77impl EncodeLike<u32> for CollectionId {}78impl EncodeLike<CollectionId> for u32 {}7980#[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Default, TypeInfo)]81#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]82pub struct TokenId(pub u32);83impl EncodeLike<u32> for TokenId {}84impl EncodeLike<TokenId> for u32 {}8586impl TokenId {87 pub fn try_next(self) -> Result<TokenId, ArithmeticError> {88 self.089 .checked_add(1)90 .ok_or(ArithmeticError::Overflow)91 .map(Self)92 }93}9495impl From<TokenId> for U256 {96 fn from(t: TokenId) -> Self {97 t.0.into()98 }99}100101impl TryFrom<U256> for TokenId {102 type Error = &'static str;103104 fn try_from(value: U256) -> Result<Self, Self::Error> {105 Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))106 }107}108109pub struct OverflowError;110impl From<OverflowError> for &'static str {111 fn from(_: OverflowError) -> Self {112 "overflow occured"113 }114}115116pub type DecimalPoints = u8;117118#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]119#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]120pub enum CollectionMode {121 NFT,122 // decimal points123 Fungible(DecimalPoints),124 ReFungible,125}126127impl CollectionMode {128 pub fn id(&self) -> u8 {129 match self {130 CollectionMode::NFT => 1,131 CollectionMode::Fungible(_) => 2,132 CollectionMode::ReFungible => 3,133 }134 }135}136137pub trait SponsoringResolve<AccountId, Call> {138 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;139}140141#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]142#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]143pub enum AccessMode {144 Normal,145 WhiteList,146}147impl Default for AccessMode {148 fn default() -> Self {149 Self::Normal150 }151}152153#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo)]154#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]155pub enum SchemaVersion {156 ImageURL,157 Unique,158}159impl Default for SchemaVersion {160 fn default() -> Self {161 Self::ImageURL162 }163}164165#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]166#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]167pub struct Ownership<AccountId> {168 pub owner: AccountId,169 pub fraction: u128,170}171172#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]173#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]174pub enum SponsorshipState<AccountId> {175 /// The fees are applied to the transaction sender176 Disabled,177 Unconfirmed(AccountId),178 /// Transactions are sponsored by specified account179 Confirmed(AccountId),180}181182impl<AccountId> SponsorshipState<AccountId> {183 pub fn sponsor(&self) -> Option<&AccountId> {184 match self {185 Self::Confirmed(sponsor) => Some(sponsor),186 _ => None,187 }188 }189190 pub fn pending_sponsor(&self) -> Option<&AccountId> {191 match self {192 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),193 _ => None,194 }195 }196197 pub fn confirmed(&self) -> bool {198 matches!(self, Self::Confirmed(_))199 }200}201202impl<T> Default for SponsorshipState<T> {203 fn default() -> Self {204 Self::Disabled205 }206}207208#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]209#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]210pub struct Collection<T: frame_system::Config> {211 pub owner: T::AccountId,212 pub mode: CollectionMode,213 pub access: AccessMode,214 pub name: Vec<u16>, // 64 include null escape char215 pub description: Vec<u16>, // 256 include null escape char216 pub token_prefix: Vec<u8>, // 16 include null escape char217 pub mint_mode: bool,218 pub offchain_schema: Vec<u8>,219 pub schema_version: SchemaVersion,220 pub sponsorship: SponsorshipState<T::AccountId>,221 pub limits: CollectionLimits, // Collection private restrictions222 pub variable_on_chain_schema: Vec<u8>, //223 pub const_on_chain_schema: Vec<u8>, //224 pub meta_update_permission: MetaUpdatePermission,225}226227#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]228#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]229pub struct NftItemType<AccountId> {230 pub owner: AccountId,231 pub const_data: Vec<u8>,232 pub variable_data: Vec<u8>,233}234235#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]236#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]237pub struct FungibleItemType {238 pub value: u128,239}240241#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]242#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]243pub struct ReFungibleItemType<AccountId> {244 pub owner: Vec<Ownership<AccountId>>,245 pub const_data: Vec<u8>,246 pub variable_data: Vec<u8>,247}248249#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)]250#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]251pub struct CollectionLimits {252 pub account_token_ownership_limit: Option<u32>,253 pub sponsored_data_size: Option<u32>,254 /// None - setVariableMetadata is not sponsored255 /// Some(v) - setVariableMetadata is sponsored256 /// if there is v block between txs257 pub sponsored_data_rate_limit: Option<u32>,258 pub token_limit: Option<u32>,259260 // Timeouts for item types in passed blocks261 pub sponsor_transfer_timeout: Option<u32>,262 pub owner_can_transfer: Option<bool>,263 pub owner_can_destroy: Option<bool>,264 pub transfers_enabled: Option<bool>,265}266267impl CollectionLimits {268 pub fn account_token_ownership_limit(&self) -> u32 {269 self.account_token_ownership_limit270 .unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)271 .min(MAX_TOKEN_OWNERSHIP)272 }273 pub fn sponsored_data_size(&self) -> u32 {274 self.sponsored_data_size275 .unwrap_or(CUSTOM_DATA_LIMIT)276 .min(CUSTOM_DATA_LIMIT)277 }278 pub fn token_limit(&self) -> u32 {279 self.token_limit280 .unwrap_or(COLLECTION_TOKEN_LIMIT)281 .min(COLLECTION_TOKEN_LIMIT)282 }283 pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {284 self.sponsor_transfer_timeout285 .unwrap_or(default)286 .min(MAX_SPONSOR_TIMEOUT)287 }288 pub fn owner_can_transfer(&self) -> bool {289 self.owner_can_transfer.unwrap_or(true)290 }291 pub fn owner_can_destroy(&self) -> bool {292 self.owner_can_destroy.unwrap_or(true)293 }294 pub fn transfers_enabled(&self) -> bool {295 self.transfers_enabled.unwrap_or(true)296 }297 pub fn sponsored_data_rate_limit(&self) -> Option<u32> {298 self.sponsored_data_rate_limit299 .map(|v| v.min(MAX_SPONSOR_TIMEOUT))300 }301}302303/// BoundedVec doesn't supports serde304#[cfg(feature = "serde1")]305mod bounded_serde {306 use core::convert::TryFrom;307 use frame_support::{BoundedVec, traits::Get};308 use serde::{309 ser::{self, Serialize},310 de::{self, Deserialize, Error},311 };312 use sp_std::vec::Vec;313314 pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>315 where316 D: ser::Serializer,317 V: Serialize,318 {319 let vec: &Vec<_> = &value;320 vec.serialize(serializer)321 }322323 pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>324 where325 D: de::Deserializer<'de>,326 V: de::Deserialize<'de>,327 S: Get<u32>,328 {329 // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?330 let vec = <Vec<V>>::deserialize(deserializer)?;331 let len = vec.len();332 TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))333 }334}335336#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]337#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]338#[derivative(Debug)]339pub struct CreateNftData {340 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]341 #[derivative(Debug = "ignore")]342 pub const_data: BoundedVec<u8, CustomDataLimit>,343 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]344 #[derivative(Debug = "ignore")]345 pub variable_data: BoundedVec<u8, CustomDataLimit>,346}347348#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]349#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]350pub struct CreateFungibleData {351 pub value: u128,352}353354#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]355#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]356#[derivative(Debug)]357pub struct CreateReFungibleData {358 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]359 #[derivative(Debug = "ignore")]360 pub const_data: BoundedVec<u8, CustomDataLimit>,361 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]362 #[derivative(Debug = "ignore")]363 pub variable_data: BoundedVec<u8, CustomDataLimit>,364 pub pieces: u128,365}366367#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]368#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]369pub enum MetaUpdatePermission {370 ItemOwner,371 Admin,372 None,373}374375impl Default for MetaUpdatePermission {376 fn default() -> Self {377 Self::ItemOwner378 }379}380381#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]382#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]383pub enum CreateItemData {384 NFT(CreateNftData),385 Fungible(CreateFungibleData),386 ReFungible(CreateReFungibleData),387}388389impl CreateItemData {390 pub fn data_size(&self) -> usize {391 match self {392 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),393 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),394 _ => 0,395 }396 }397}398399impl From<CreateNftData> for CreateItemData {400 fn from(item: CreateNftData) -> Self {401 CreateItemData::NFT(item)402 }403}404405impl From<CreateReFungibleData> for CreateItemData {406 fn from(item: CreateReFungibleData) -> Self {407 CreateItemData::ReFungible(item)408 }409}410411impl From<CreateFungibleData> for CreateItemData {412 fn from(item: CreateFungibleData) -> Self {413 CreateItemData::Fungible(item)414 }415}