--- a/README.md +++ b/README.md @@ -241,6 +241,12 @@ "ReFungible": "CreateReFungibleData" } } + "CollectionLimits": { + "AccountTokenOwnershipLimit": "u32", + "SponsoredDataSize": "u32", + "TokenLimit": "u32", + "SponsorTransferTimeout": "u32" + } } ``` \ No newline at end of file --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -171,7 +171,8 @@ sponsor: get_account_id_from_seed::("Alice"), unconfirmed_sponsor: get_account_id_from_seed::("Alice"), const_on_chain_schema: vec![], - variable_on_chain_schema: vec![] + variable_on_chain_schema: vec![], + limits: CollectionLimits::default() }, )], nft_item_id: vec![], --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -109,6 +109,7 @@ pub offchain_schema: Vec, pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship + pub limits: CollectionLimits, // Collection private restrictions pub variable_on_chain_schema: Vec, // pub const_on_chain_schema: Vec, // } @@ -164,6 +165,27 @@ pub start_block: BlockNumber, } +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CollectionLimits { + pub account_token_ownership_limit: u32, + pub sponsored_data_size: u32, + pub token_limit: u32, + + // Timeouts for item types in passed blocks + pub sponsor_transfer_timeout: u32, +} + +impl Default for CollectionLimits { + fn default() -> CollectionLimits { + CollectionLimits { + account_token_ownership_limit: 0, + token_limit: 0, + sponsored_data_size: 0, + sponsor_transfer_timeout: 0 } + } +} + #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ChainLimits { @@ -321,7 +343,11 @@ /// Unexpected collection type. UnexpectedCollectionType, /// Can't store metadata in fungible tokens. - CantStoreMetadataInFungibleTokens + CantStoreMetadataInFungibleTokens, + /// Collection token limit exceeded + CollectionTokenLimitExceeded, + /// Account token limit exceeded per collection + AccountTokenLimitExceeded } } @@ -537,6 +563,7 @@ unconfirmed_sponsor: T::AccountId::default(), variable_on_chain_schema: Vec::new(), const_on_chain_schema: Vec::new(), + limits: CollectionLimits::default(), }; // Add new collection to map @@ -1380,17 +1407,21 @@ Ok(()) } - // #[cfg(feature = "runtime-benchmarks")] - // #[weight = 0] - // pub fn add_contract_sponsoring_debug( - // origin, - // contract_address: T::AccountId, - // owner: T::AccountId) -> DispatchResult { - // let sender = ensure_signed(origin)?; - // >::insert(contract_address.clone(), owner); - // Ok(()) - // } - + #[weight = 0] + pub fn set_collection_limits( + origin, + collection_id: u64, + limits: CollectionLimits, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::check_owner_permissions(collection_id, sender.clone())?; + + let mut target_collection = >::get(collection_id); + target_collection.limits = limits; + >::insert(collection_id, target_collection); + + Ok(()) + } } } @@ -1399,6 +1430,12 @@ fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult { if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + + // check token limit and account token limit + let total_items: u64 = ItemListIndex::get(collection_id); + let account_items: u32 = >::get(collection_id, sender.clone()).len() as u32; + ensure!(collection.limits.token_limit as u64 > total_items, Error::::CollectionTokenLimitExceeded); + ensure!(collection.limits.account_token_ownership_limit > account_items, Error::::AccountTokenLimitExceeded); ensure!(collection.mint_mode == true, Error::::PublicMintingNotAllowed); Self::check_white_list(collection_id, owner)?; Self::check_white_list(collection_id, sender)?; @@ -1478,7 +1515,6 @@ Self::add_refungible_item(item)?; } }; - // call event Self::deposit_event(RawEvent::ItemCreated(collection_id, ::get(collection_id))); @@ -2211,18 +2247,35 @@ // Determine who is paying transaction fee based on ecnomic model // Parse call to extract collection ID and access collection sponsor let mut sponsor: T::AccountId = match IsSubType::>::is_sub_type(call) { - Some(Call::create_item(collection_id, _properties, _owner)) => { - >::get(collection_id).sponsor + Some(Call::create_item(collection_id, _owner, _properties)) => { + + // check free create limit + if >::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32) + { + >::get(collection_id).sponsor + } else { + T::AccountId::default() + } } Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => { + + let _collection_limits = >::get(collection_id).limits; let _collection_mode = >::get(collection_id).mode; // sponsor timeout let sponsor_transfer = match _collection_mode { CollectionMode::NFT => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().nft_sponsor_transfer_timeout + }; + let basket = >::get(collection_id, _item_id); let block_number = >::block_number() as T::BlockNumber; - let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into(); + let limit_time = basket + limit.into(); if block_number >= limit_time { >::insert(collection_id, _item_id, block_number); true @@ -2232,12 +2285,20 @@ } } CollectionMode::Fungible(_) => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().fungible_sponsor_transfer_timeout + }; + let mut basket = >::get(collection_id, _item_id); let block_number = >::block_number() as T::BlockNumber; if basket.iter().any(|i| i.address == _new_owner.clone()) { let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone(); - let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into(); + let limit_time = item.start_block + limit.into(); if block_number >= limit_time { basket.retain(|x| x.address == item.address); basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() }); @@ -2254,9 +2315,17 @@ } } CollectionMode::ReFungible(_) => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().refungible_sponsor_transfer_timeout + }; + let basket = >::get(collection_id, _item_id); let block_number = >::block_number() as T::BlockNumber; - let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into(); + let limit_time = basket + limit.into(); if block_number >= limit_time { >::insert(collection_id, _item_id, block_number); true